一、cookie、sessionStorage 和 localStorage 的区别,以及在实际开发中你是如何选择使用它们的?比如用户登录状态、保存用户偏好设置这类场景,你会怎么处理?
-
Cookie:
- 容量小(4KB左右);
- 每次请求都会自动带到服务器(适合做用户身份识别);
- 可以设置过期时间;
- 常用于服务端读取,比如存储
token
、session id
等; - 可通过
document.cookie
操作,但不太方便。
-
localStorage:
- 容量大(5MB左右);
- 只存在于浏览器端,不会自动随请求发送;
- 数据持久化,除非手动清除;
- 常用于保存用户设置、主题偏好等不敏感数据。
-
sessionStorage:
- 类似 localStorage,但生命周期是"当前标签页";
- 页面关闭就被清除;
- 多个标签页互相隔离;
- 适合临时数据,比如表单填写进度。
举例说明(加分项 🔥):
比如在一个用户登录系统中,我通常会用:
- Cookie 来存储
token
,这样如果后台需要身份验证,可以从请求头中拿到; - localStorage 存用户的偏好设置(比如主题是深色还是浅色),因为这种数据希望在用户回访时还存在;
- sessionStorage 可能会用来暂存一些非持久性的表单数据,比如用户填写多步表单,还没提交就刷新页面,数据还能保留。
二、逻辑题-一副扑克拿掉大小王问最多几个人能够抽到相同的两个花色
题目简化:
一副扑克牌去掉大小王,即只剩下 52 张牌 (4 种花色,每种花色 13 张)。 问:最多有多少个人 ,每人抽 两张牌且颜色相同?
- 每个花色有 13 张牌。
公式:C(n, k) 表示从 n 个元素中选 k 个,不考虑顺序。 (也就是说,问题简化-从 13 张同花色的牌里选出 2 张,不管顺序(♠A + ♠K 跟 ♠K + ♠A 是一样的组合),有多少种选法?)
-
那么一个花色中,最多能组成几对牌?
-
从 13 张中选 2 张:
C(13,2)=13×12/2=78
-
所以一个花色能抽出 78 个"同花色的两张牌"。
-
总共 4 种花色:
4×78=312 对
-
问题问的是"最多几个人能够抽到两个相同花色的牌",每人抽一对
最多可以满足312个人。
三、逻辑题-如何计算黄河一天流多少吨水
核心公式:流量(吨/天)=流速(m/s)×断面面积(m²)×86400(秒/天)×1000(kg/m³)÷1000
⚠️ 注意:因为 1 立方米水 ≈ 1000 千克 = 1 吨,所以最后可以直接乘 86400 × 断面流量。
🚀步骤拆解:
1️⃣ 假设/查到黄河某一段的参数(比如兰州段):
- 平均流速:约 2 米/秒
- 断面面积 :比如河宽 200 米、平均水深 3 米
→ 面积 = 200 × 3 = 600 m²
2️⃣ 算每秒流量(单位:立方米/秒):
Q=2 (m/s)×600 (m²)=1200 m³/s
3️⃣ 算每天总流量(单位:吨/天):
每天秒数=24×60×60=86400 秒
每日总吨数=1200×86400=103,680,000 吨
🎯答案(估算):
黄河某一段(如兰州)大概一天流 1亿吨左右的水。
四、跨页面通信-比如说我在商品页有一个id我在其他页面怎么拿到
- 父传子props, 子传父 自定义事件 <math xmlns="http://www.w3.org/1998/Math/MathML"> e m i t 、 emit、 </math>emit、on
- 获取父组件、子组件实例 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a r e n t 、 parent、 </math>parent、children
- ref 可以获取组件实例或者dom元素
- vuex
- event bus
- localStorage / sessionStorage / cookie
比如说我在商品页有一个id我在其他页面怎么拿到?
- URL 传参(最常见)
js
// 跳转时:
window.location.href = `/checkout?productId=123`;
// 新页面获取:
const urlParams = new URLSearchParams(window.location.search);
const productId = urlParams.get("productId");
2.localStorage / sessionStorage 存取
js
// 存入
localStorage.setItem("productId", "123"); // 或 sessionStorage
// 获取
const productId = localStorage.getItem("productId");
3.状态管理
js
// 例如在 Zustand 中设置:
store.setState({ productId: "123" });
// 在其他页面组件中读取:
const productId = useStore((state) => state.productId);
4.通过路由 params(前端路由,比如 React Router、Vue Router)
js
// 商品页-路由
/product/:id
// 在跳转页面用 useParams 获取
const { id } = useParams();
五、vue组件通信
- 父传子props, 子传父 自定义事件 <math xmlns="http://www.w3.org/1998/Math/MathML"> e m i t 、 emit、 </math>emit、on
- 获取父组件、子组件实例 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a r e n t 、 parent、 </math>parent、children
- ref 可以获取组件实例或者dom元素
- vuex
- event bus
六、http状态码
200 请求成功 204 没有返回内容
301 被请求的资源永久的移动到了新的地址(永久重定向) 302 被请求的资源临时移动到新地址(临时重定向) 304 被请求的资源未被更新
400 请求出错 401 请求权限未验证 403 没有权限 404 被访问的资源不存在 405 不允许的http请求
500 服务器内部错误 503 服务器无法处理该请求
七、h5语义化-好处
header、nav、main、section、article、aside、footer
好处:
- 提升代码可读性
- 有利于SEO优化
八、webpack和vite的区别
Webpack 是打包为主,构建时加载;Vite 是原生 ESM 开发,按需加载。Vite 更快、配置更轻,适合现代开发。
九、vue2有用过吗和 vue3的区别
- vue3采用ts编写源码
- vue3采用组合式API,vue2采用响应式API
- 响应式原理不同,vue3采用proxy代理,vue2采用Object.definedProproty
- vue3支持多个根标签
- vue3采用了setup代替了vue2中的beforeCreate、created
十、vue响应式
Vue2 用 Object.defineProperty
,只能劫持已有属性;Vue3 用 Proxy
,可以完全代理对象。
vue3使用proxy优势:可以深度监听数组和对象。
十一、前端实现动画的方式有哪些?
实现方式 | 示例/工具 | 优点 | 场景推荐 |
---|---|---|---|
CSS 动画 | transition , animation |
简洁、性能好、硬件加速 | 简单的过渡、hover 动效等 |
JS 动画(手动) | setInterval , requestAnimationFrame |
控制灵活 | 复杂逻辑、与数据联动的动画 |
CSS-in-JS | React/Vue 中的 @keyframes 或 style 动画 |
框架集成方便 | React/Vue 等 SPA 应用中常见 |
第三方库 | GSAP、Anime.js、Velocity.js | 高级控制、跨浏览器兼容 | 高质量动画、交互复杂动画 |
Canvas 动画 | canvas API |
可绘制图形、帧控制好 | 游戏、粒子动画、数据可视化等 |
SVG 动画 | animate , SMIL 或 JS 操作 |
可缩放、适合矢量图 | 图标动效、路径动画 |
WebGL 动画 | Three.js、Babylon.js | 超强性能,适合 3D 动画 | 游戏、3D 可视化、高性能场景 |
Framer Motion(React) | React 动画库 | 简洁语法、组件化动画 | React 项目中现代、响应式动画 |
用过 requestAnimationFrame
吗?跟 setTimeout
有什么区别?
requestAnimationFrame
告诉浏览器在下一帧 执行你提供的回调函数,用来创建平滑、高性能动画。- 浏览器优化性能,不活跃 tab 不执行,省资源
- 相比
setTimeout(fn, 16)
更流畅、不抖动
十二、rem/em/px/rpx/vw/vh/%的区别
单位 | 参考基准 | 特点 | 常见场景 |
---|---|---|---|
px |
固定像素(绝对单位) | 不随设备缩放,最常用 | 常规布局、精细控件 |
em |
相对于当前元素的字体大小 | 会继承叠加,不易控 | 字体、间距(嵌套注意) |
rem |
相对于根元素(html)的字体大小 | 易于统一管理,推荐用于响应式 | 移动端、字体大小 |
% |
相对父元素 | 灵活、常配合布局使用 | 宽高、padding |
vw |
相对于视口宽度(1vw = 1%) | 适配宽度,响应式好 | 宽度适配、全屏模块 |
vh |
相对于视口高度(1vh = 1%) | 适配高度,满屏容器 | 高度满屏组件 |
rpx |
相对于设备宽度(750 = 100%) | 微信小程序专用 | 小程序布局 |
十三、css选择器
类型 | 示例 | 说明 |
---|---|---|
基础选择器 | .class #id div |
按类名、ID、标签选择 |
组合选择器 | div p div > p |
选择多个元素之间的关系 |
属性选择器 | [type="text"] |
根据属性选择元素 |
伪类选择器 | :hover :first-child |
根据状态或结构选择 |
伪元素选择器 | ::before ::after |
选中元素的一部分(虚拟元素) |
群组选择器 | h1, h2, h3 |
多个元素统一设置样式 |
否定选择器 | :not(.active) |
排除某些元素 |
十四、css隐藏元素
方法 | 是否占位 | 是否可点击 | 是否渲染在 DOM 中 | 说明 |
---|---|---|---|---|
display: none |
❌ 不占位 | ❌ 不可点击 | ✅ 在 DOM 中 | 最常用,彻底移除布局 |
visibility: hidden |
✅ 占位 | ❌ 不可点击 | ✅ 在 DOM 中 | 看不到但占空间 |
opacity: 0 |
✅ 占位 | ✅ 可点击 | ✅ 在 DOM 中 | 透明但仍可交互 |
width: 0; height: 0 |
✅ 占位 | ✅ 可点击 | ✅ 在 DOM 中 | 通常用于隐藏输入框等 |
position: absolute; left: -9999px |
❌ 不占位 | ✅ 可点击 | ✅ 在 DOM 中 | 移出视口,仍存在 |
clip-path: inset(0 0 0 0) + opacity: 0 |
✅ | ❌ | ✅ | 高级隐藏,配合动画 |
hidden 属性(HTML) |
❌ 不占位 | ❌ 不可点击 | ✅ 在 DOM 中 | 类似 display: none |
十五、行内元素和块级元素的区别,和分别有哪些?
属性 | 块级元素(Block) | 行内元素(Inline) |
---|---|---|
是否独占一行 | ✅ 是 | ❌ 否,和其他元素同行 |
是否可设置宽高 | ✅ 可以 | ❌ 不可以(width/height 无效) |
默认宽度 | 100%(占满父容器) | 内容撑开的宽度 |
包含内容 | 块级元素和行内元素 | 行内元素 |
常见用途 | 结构布局、容器 | 内容修饰、小部件 |
十六、position:fixed 和 positon: absolute 区别点
position: absolute
相对于最近的"定位祖先"定位,而 position: fixed
是**相对于视口(viewport)**定位,滚动页面时不会移动。
有一个父元素盒子 div设置transfrom:translate,然后子元素是position:fixed,这时候子元素相对于哪个位置定位的?
当父元素设置了 transform
(如 transform: translate(...)
),它会创建一个新的"包含块(containing block)" ,此时子元素的 position: fixed
会相对于这个父元素进行定位 ,而不是视口
-
通常
position: fixed
是**相对于浏览器视口(viewport)**定位。 -
但如果它的祖先元素创建了新的"图形上下文(containing block) ",比如设置了:
transform
filter
perspective
will-change
contain
backdrop-filter
translate
(新版语法)
👉 那么子元素的 fixed 会"脱离视口",变成相对于这个祖先元素定位!
十七、Object的静态方法有哪些?
Object
的静态方法主要用于对象的创建、拷贝、属性定义、遍历等,常见的有 assign
、keys
、values
、entries
、create
、defineProperty
、freeze
、is
等等。
获取属性相关:
方法名 | 作用 |
---|---|
Object.keys(obj) |
获取可枚举自身属性名数组 |
Object.values(obj) |
获取可枚举自身属性值数组 |
Object.entries(obj) |
获取可枚举自身属性的键值对 |
Object.getOwnPropertyNames(obj) |
获取所有自身属性名(包括不可枚举) |
Object.getOwnPropertySymbols(obj) |
获取所有 Symbol 类型属性名 |
判断类:
方法名 | 作用 |
---|---|
Object.is(val1, val2) |
判断是否"严格相等"(类似 === ,但处理 NaN 和 -0 更精准) |
Object.hasOwn(obj, key) |
检查对象自身是否含有某属性(ES2022,推荐) |
创建类的方法:
方法名 | 作用 |
---|---|
Object.create(proto) |
以指定原型创建新对象 |
Object.assign(target, ...) |
浅拷贝、合并对象 |
Object.fromEntries(entries) |
把键值对数组转成对象(ES10) |
属性操作类:
方法名 | 作用 |
---|---|
Object.defineProperty(obj, key, descriptor) |
定义单个属性(可控制 writable , enumerable 等) |
Object.defineProperties(obj, descriptors) |
批量定义属性 |
Object.getOwnPropertyDescriptor(obj, key) |
获取某属性的描述符 |
Object.getOwnPropertyDescriptors(obj) |
获取所有属性的描述符(ES8) |
控制对象状态类:
方法名 | 作用 |
---|---|
Object.freeze(obj) |
冻结对象(不可增删改) |
Object.seal(obj) |
密封对象(不可新增,可改值) |
Object.preventExtensions(obj) |
禁止新增属性 |
Object.isFrozen(obj) |
检查是否被冻结 |
Object.isSealed(obj) |
检查是否被密封 |
Object.isExtensible(obj) |
检查是否可扩展 |
原型链操作类:
方法名 | 作用 |
---|---|
Object.getPrototypeOf(obj) |
获取对象原型 |
Object.setPrototypeOf(obj, proto) |
设置对象原型(不推荐频繁使用) |
十八、Object.assign
Object.assign()
用于将一个或多个源对象的可枚举属性 复制到目标对象中,返回目标对象。常用于对象合并、浅拷贝、默认值合并等场景。
基本语法:
js
Object.assign(target, ...sources);
target
:目标对象(被修改并返回)sources
:一个或多个源对象
示例
- 合并对象
js
const a = { name: '小明' };
const b = { age: 20 };
const result = Object.assign({}, a, b);
console.log(result); // { name: '小明', age: 20 }
- 修改目标对象
js
const target = { a: 1 };
const source = { b: 2 };
Object.assign(target, source);
console.log(target); // { a: 1, b: 2 }(target 被改了)
- 浅拷贝
js
const obj1 = { nested: { x: 1 } };
const obj2 = Object.assign({}, obj1);
obj2.nested.x = 999;
console.log(obj1.nested.x); // 999 ❗️(浅拷贝,嵌套引用的是同一个对象)
- 会覆盖同名属性,右边覆盖左边
js
Object.assign({ a: 1 }, { a: 2 }); // { a: 2 }
- 会跳过不可枚举和原型属性
js
const obj = Object.create({ inherited: 123 });
obj.visible = 1;
Object.assign({}, obj); // 只复制 visible,不复制 inherited
十九、浅拷贝和深拷贝的区别。怎么实现浅拷贝、深拷贝
- 浅拷贝:只拷贝对象的第一层属性,如果属性值是引用类型,拷贝的是引用地址。
- 深拷贝:不仅拷贝对象本身,也递归拷贝所有嵌套的引用类型属性 ,两个对象完全独立。
浅拷贝
js
const obj = {
a: 1,
b: {
c: 2
}
}
// 实现方法1
const resObj = { ...obj }
// 实现方法2
const resObj = Object.assign({}, obj)
// 实现方法3
function light_copy(obj) {
let resObj = {}
Object.keys(obj).forEach(t => resObj[t] = obj[t])
return resObj
}
深拷贝
js
const obj = {
a: 1,
b: {
c: 2
}
}
// 实现方法1
const resObj = JSON.parse(JSON.stringify(obj))
// 实现方法2
import _ from 'lodash';
const deepCopy = _.cloneDeep(obj);
// 实现方法3
function deep_copy1(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
let resObj = Array.isArray(obj) ? [] : {}
for (const key in obj) {
resObj[key] = deep_copy1(obj[key])
}
return resObj
}
// 实现方法4 - 支持循环引用(坑)
const obj = { a: 1, b: 2 };
obj.c = obj; // c 指向 obj 自己
此时使用方法3,会死循环 💥!函数一直递归下去,因为:
- `obj.c = obj`,它指向自己。
- `deep_copy1(obj)` → `deep_copy1(obj.c)` → `deep_copy1(obj)` → ... 无限递归。
function deep_copy2(obj, cache = new WeakMap()) {
if (typeof obj !== 'object' || obj === null) return obj;
// 如果这个对象已经被拷贝过,直接返回这个对象
if (cache.has(obj)) return catch.get(obj);
let resObj = Array.isArray(obj) ? [] : {}
catch.set(obj, res) // 缓存
for (const key in obj) {
resObj[key] = deep_copy2(obj[key], cache)
}
return resObj
}
对数组浅拷贝,有哪些方式?
方法 | 写法(const arr = [1, 2, 3];) | 说明 |
---|---|---|
slice() |
const copy = arr.slice(); | 常规经典 |
[...arr] |
const copy = [...arr]; | 最推荐、最简洁 |
Array.from() |
const copy = Array.from(arr); | 用于类数组也很好 |
concat() |
const copy = [].concat(arr); | ES5 写法 |
map(x => x) |
const copy = arr.map(x => x); | 不推荐,仅供了解 |
二十、setTimeout
/ setInterval
准时么 为什么?
不准时。它们的定时机制是基于 JavaScript 的事件循环(Event Loop) , 并不是"到了时间点就执行",而是"等主线程空了,才有机会执行。
二十一、Promise
Promise
是 JavaScript 异步编程的核心部分,它的主要作用是处理异步操作,让代码更加清晰、可维护。
Promise 的三个状态:
-
Pending(待定) :
- 初始状态。即
Promise
创建时的状态,表示异步操作正在进行中。
- 初始状态。即
-
Fulfilled(已兑现) :
- 当异步操作成功执行时,
Promise
进入此状态。此时可以通过then()
方法获取到结果。
- 当异步操作成功执行时,
-
Rejected(已拒绝) :
- 当异步操作失败时,
Promise
进入此状态。此时可以通过catch()
方法获取到错误信息。
- 当异步操作失败时,
Promise
构造函数是同步还是异步?
Promise
的构造函数(new Promise()
) 是同步执行的,但它的执行内容是异步的。
Promise.all
Promise.all()
方法用于将多个 Promise 对象包装成一个新的 Promise
,当所有的 Promise
都变成 fulfilled 状态时,它才会触发 then()
,如果有一个 Promise
被 rejected ,整个 Promise.all()
会立即进入 rejected 状态。
js
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
let results = [];
let completed = 0;
for (let i = 0; i < promises.length; i++) {
// 确保每个 promise 执行的顺序和传入的顺序一致
promises[i].then((value) => {
results[i] = value;
completed++;
if (completed === promises.length) {
resolve(results);
}
}).catch((error) => {
reject(error); // 一旦有一个失败,直接 reject
});
}
});
}
Promise实现红绿灯不断交替亮灯的逻辑
用Promise实现红绿灯不断交替亮灯的逻辑
红灯3秒亮一次,绿灯1秒亮一次,黄灯2秒亮一次;
js
function red() {
console.log('红灯亮');
}
function green() {
console.log('绿灯亮');
}
function yellow() {
console.log('黄灯亮');
}
function changeLight(light, time) {
return new Promise(resolve => {
// 亮灯
light()
// 等待指定时间后 resolve
setTimeout(() => {
resolve()
}, time * 1000)
})
}
async function run(count = 100) {
let i = 0;
while (i < count) {
i++
await changeLight(red, 3)
await changeLight(yellow, 2)
await changeLight(green, 1)
}
}
run()
二十二、解析 URL 参数,带嵌套结构
给定如下 URL:
js
const url = "https://www.test.com/index?name=Tom&age=18&hobby=reading&hobby=coding&location[city]=Beijing&location[country]=China";
要求:
请你解析出 URL 中 ?
后的内容,返回一个结构如下的对象:
js
{
name: "Tom",
age: "18",
hobby: ["reading", "coding"],
location: {
city: "Beijing",
country: "China"
}
}
解答:
js
function parseUrlParams(url) {
// 按照?截取后面的内容
const str = url.split("?")[1];
// 按照&拆分
const arr = str.split("&");
// 构建一个obj
let obj = {};
// 循环处理
for (let i = 0; i < arr.length; i++) {
// 解析每一项的key和value
const [rawKey, rawVal] = arr[i].split("=");
const key = decodeURIComponent(rawKey);
const val = decodeURIComponent(rawVal);
// 判断是否为对象结构(单层结构)(例如 location[city]=Beijing)
// if (key.includes("[") && key.includes("]")) {
// const start = key.indexOf("[");
// const end = key.indexOf("]");
// const parentKey = key.slice(0, start); // 比如 location
// const childKey = key.slice(start + 1, end); // 比如 city
// if (!obj[parentKey]) obj[parentKey] = {};
// obj[parentKey][childKey] = val;
// }
// 判断是否为对象结构(多层结构)
if (key.includes("[") && key.includes("]")) {
const keys = key.split(/\[|\]/).filter(Boolean); // 将 "location[address][city]" 拆解成 ["location", "address", "city"]
let current = obj; // 使用一个临时变量来遍历 obj
for (let j = 0; j < keys.length - 1; j++) { // 遍历 keys 数组,逐层创建对象
if (!current[keys[j]]) {
current[keys[j]] = {}; // 创建嵌套对象
}
current = current[keys[j]]; // 深入到下一层
}
current[keys[keys.length - 1]] = val;// 最后将值赋给最终的子属性
}
else if (obj[key]) {
// 判断数组(例如 hobby=reading&hobby=coding)
if (!Array.isArray(obj[key])) {
obj[key] = [obj[key]]; // 将原来的值变为数组
}
obj[key].push(val);
} else {
obj[key] = val;
}
}
return obj;
}
parseUrlParams(url);
二十三、React children
在 React 中,每个组件都可以有 children
作为其内容,children
可以是任何有效的 React 元素(如字符串、数字、JSX 元素、其他 React 组件等)。
children
的常见用法:
-
组件嵌套
你可以在一个组件内部使用其他组件作为
children
,这使得组件可以非常灵活地组合和重用。jsfunction Wrapper({ children }) { return ( <div className="wrapper"> {children} </div> ); } function App() { return ( <Wrapper> <h1>This is inside a wrapper</h1> </Wrapper> ); }
-
默认值
如果没有传递
children
,你可以为它设置默认值:jsfunction Box({ children = "Default content" }) { return ( <div className="box"> {children} </div> ); }
-
React.Children
工具:React 提供了一些工具来处理
children
,例如React.Children.map()
和React.Children.toArray()
等,可以让你更方便地操作children
(特别是当children
是一个数组或多个元素时)。jsfunction List({ children }) { return ( <ul> {React.Children.map(children, (child) => ( <li>{child}</li> ))} </ul> ); } function App() { return ( <List> <span>Item 1</span> <span>Item 2</span> <span>Item 3</span> </List> ); }
-
条件渲染:
children
也可以用来进行条件渲染:jsfunction Box({ children }) { return ( <div className="box"> {children ? children : <p>No content available</p>} </div> ); }
-
React Fragment 和 Children:
如果你需要返回多个元素而不引入额外的 DOM 节点,可以使用
React.Fragment
来包裹多个children
元素。jsfunction App() { return ( <React.Fragment> <h1>Title</h1> <p>Description</p> </React.Fragment> ); }
二十四、什么时候在react必须使用key
循环/动态渲染列表时
js
const items = ['apple', 'banana', 'orange'];
function FruitList() {
return (
<ul>
{items.map((item, index) => (
<li key={item}>{item}</li>
))}
</ul>
);
}
二十五、setState
-
setState
是 React 类组件中用来更新状态的函数。 -
setState
是异步的,因此在更新状态后直接访问state
可能不会立即得到更新后的值。 -
使用
setState
时可以传递回调函数,确保在状态更新完成后执行某些操作。 -
当状态更新依赖于先前的状态时,使用函数式的
setState
。 -
在函数组件中,
useState
替代了setState
。
二十六、归并排序
归并排序(Merge Sort)是一种有效的排序算法,采用了分治法(Divide and Conquer)思想。它通过将数组分成两个子数组,分别对这两个子数组进行排序,最后将它们合并成一个有序的数组。
归并排序的基本步骤:
- 分解:将待排序的数组分成两半,分别对两个子数组进行递归排序。
- 合并:合并两个已排序的子数组,得到一个有序的数组。
归并排序的工作原理:
- 首先将数组不断地分割成两个子数组,直到每个子数组只有一个元素或为空。
- 然后通过合并两个已排序的子数组来得到更大的有序数组。
- 递归地合并,直到得到一个完整的有序数组。
js
// 归并排序的主函数
function mergeSort(arr) {
// 如果数组长度小于2,直接返回
if (arr.length <= 1) {
return arr;
}
// 找到数组的中间位置
const mid = Math.floor(arr.length / 2);
// 将数组分成两部分
const left = mergeSort(arr.slice(0, mid)); // 左半部分
const right = mergeSort(arr.slice(mid)); // 右半部分
// 合并两个已排序的数组
return merge(left, right);
}
// 合并两个已排序的数组
function merge(left, right) {
let result = [];
let i = 0;
let j = 0;
// 比较两个数组中的元素,逐个添加到结果数组中
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i]);
i++;
} else {
result.push(right[j]);
j++;
}
}
// 如果左数组还有剩余元素,添加到结果数组中
while (i < left.length) {
result.push(left[i]);
i++;
}
// 如果右数组还有剩余元素,添加到结果数组中
while (j < right.length) {
result.push(right[j]);
j++;
}
return result;
}
// 测试
const arr = [38, 27, 43, 3, 9, 82, 10];
console.log(mergeSort(arr)); // 输出已排序的数组
二十七、怎么获取微信小程序用户信息,设备信息
要获取微信小程序用户的个人信息,需要使用 wx.getUserProfile()
或者 wx.getUserInfo()
(不推荐使用) API。
wx.getUserInfo()
(不推荐使用)
wx.getUserInfo()
已经被微信废弃,不推荐使用,主要是因为此方法获取的信息不如 wx.getUserProfile()
那么详细,并且没有用户授权的提示框,容易导致隐私问题。微信官方建议使用 wx.getUserProfile()
替代。
获取设备信息
微信小程序提供了多种方法来获取设备的硬件信息,包括操作系统、屏幕尺寸、网络状态等。常见的获取设备信息的 API 有:
获取系统信息:wx.getSystemInfoSync()
或 wx.getSystemInfo()
wx.getSystemInfoSync()
:同步获取系统信息。wx.getSystemInfo()
:异步获取系统信息。
获取网络类型:wx.getNetworkType()
获取当前设备的网络状态,例如 Wi-Fi、4G、3G、2G、无网络等。
获取设备存储信息:wx.getStorageInfoSync()
或 wx.getStorageInfo()
wx.getStorageInfoSync()
:同步获取本地存储的相关信息。wx.getStorageInfo()
:异步获取本地存储的相关信息。
二十八、float使用注意
-
清除浮动 :使用
clearfix
或clear
来防止父元素塌陷。 -
浮动脱离文档流:浮动元素脱离文档流,不会影响其他元素的布局。
-
父元素高度塌陷:如果父元素只包含浮动元素,可能会出现父元素高度为零的情况,需使用清除浮动。
-
浮动元素宽度 :浮动元素的宽度默认是它内容的宽度,需显式设置
width
。 -
多列布局 :浮动元素有可能导致布局不适应,推荐使用
flexbox
或grid
。
二十九、flex
flex
布局的核心思想是:容器的子元素(flex items)能够自动适应父容器的大小并根据可用空间进行分配。
三十、ajax 和 fetch区别
特性 | AJAX(XMLHttpRequest) | Fetch API |
---|---|---|
支持的技术 | 基于 XMLHttpRequest ,回调函数模式 |
基于 Promise ,更加简洁和现代 |
异步操作 | 使用回调函数处理异步操作,容易导致回调地狱 | 使用 Promise 处理,避免回调地狱 |
默认错误处理 | 会自动处理 HTTP 错误(如 404、500),但是只能通过 onerror 等来处理 |
不会自动处理 HTTP 错误,需要手动检查 response.ok |
数据处理 | 数据处理方式复杂,通常需要手动解析返回数据 | response.json() 、response.text() 等方法支持解析返回数据 |
跨域支持 | 需要设置 CORS 头部,且在一些浏览器中可能会遇到问题 | 默认支持 CORS,简单的跨域处理非常方便 |
请求配置 | 需要手动设置请求头、请求方法等 | 提供了更灵活的配置选项,如 mode 、headers 、body 等 |
兼容性 | 所有现代浏览器和老版本浏览器支持 | 现代浏览器支持,老版本浏览器需要 polyfill |
二十四、继承
继承是面向对象编程(OOP)中的一个核心概念,它允许一个类继承另一个类的属性和方法。 JavaScript 也支持继承机制,允许一个对象(子类)继承另一个对象(父类)的属性和方法。
二十五、多列等高的布局
- flex布局
js
<div class="container">
<div class="item">Column 1</div>
<div class="item">Column 2</div>
<div class="item">Column 3</div>
</div>
.container {
display: flex; /* 激活 flexbox 布局 */
justify-content: space-between; /* 子元素均匀分布 */
}
.item {
flex: 1; /* 让每个 item 均等分配可用空间 */
margin: 0 10px; /* 可以根据需求添加间隔 */
background: #f0f0f0;
padding: 20px;
border: 1px solid #ddd;
}
- Grid布局
js
<div class="grid-container">
<div class="grid-item">Column 1</div>
<div class="grid-item">Column 2</div>
<div class="grid-item">Column 3</div>
</div>
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 设置3列,每列占1份空间 */
gap: 20px; /* 设置列和行的间隔 */
}
.grid-item {
background: #f0f0f0;
padding: 20px;
border: 1px solid #ddd;
}
二十六、横线命名字符串转为驼峰命名字符串
js
// 输入:str = "zhang-wsd-sd-s"
// 输出:zhangWsdSdS
function parseFn(str) {
// 按照-把字符串截取成数组
let arr = str.split("-");
// 数组从下标1开始首字母转化成大写
for (let i = 1; i < arr.length; i++) {
arr[i] = arr[i].slice(0, 1).toUpperCase() + arr[i].slice(1);
}
// 拼接字符串
return arr.join("");
}
parseFn("zhang-wsd-sd-s");
二十七、合并名称
js
// 合并名称
// 书写一个函数,接受一个数组作为参数,数组内部由各个对象组成,
// 每个对象有age和name属性,将age一致的对象合并到一起,并且返回
// 输入: [ { age:1, name:"网" },{ age:2, name:"双" },{ age:1, name:"撒" }, { age:2, name:"旦" } ]
// 输出: [ { age:1,name: ["网", "撒"] }, { age:2,name: ["双", "旦"] }]
function fn(arr) {
let obj = {};
for (let i = 0; i < arr.length; i++) {
if (obj.hasOwnProperty(arr[i].age)) {
if (Array.isArray(obj[arr[i].age])) {
obj[arr[i].age] = [...obj[arr[i].age], arr[i].name];
} else {
obj[arr[i].age] = [obj[arr[i].age], arr[i].name];
}
} else {
obj[arr[i].age] = arr[i].name;
}
}
let resArr = [];
for (let i in obj) {
resArr.push({ age: Number(i), name: obj[i] });
}
return resArr;
}
fn(arr);