最近正在找工作,结合AI工具把面试中问到的八股记录下来方便复习,也希望给正在找工作的你带来一点帮助。
1、localstorage,sessionstorage,cookies,indexDB的区别
| 特性/名称 | localStorage | sessionStorage | cookies | IndexedDB |
|---|---|---|---|---|
| 生命周期 | 永久(除非主动删除) | 会话级(关闭页面失效) | 自定义过期时间 | 永久(除非主动删除) |
| 存储大小 | ~5MB | ~5MB | 4KB | 几百 MB 甚至更多 |
| 是否随请求发送 | ❌ 不发送 | ❌ 不发送 | ✅ 自动随请求发送 | ❌ 不发送 |
| 可访问范围 | 同源下所有页面共享 | 当前 tab 页面有效 | 同源所有页面共享 | 同源所有页面共享 |
| 接口操作方式 | 同步(简单 API) | 同步(简单 API) | 同步(通过 document.cookie 操作) | 异步(复杂 API,基于事件) |
| 适合场景 | 保存用户偏好、设置等 | 表单暂存、临时数据等 | 登录状态、服务端识别等 | 大量结构化数据,比如缓存文章、图像 |
2、css中如何定义和使用变量
通过 --变量名 来定义的,然后使用 var(--变量名) 来引用。变量是作用在某个元素(通常是 :root)上的
3、js中的时间循环机制
事件循环(Event Loop) 的执行顺序,包括:
-
同步代码
-
async/await的行为 -
Promise的微任务(microtask) -
setTimeout的宏任务(macrotask)下面举个例子来解释时间循环执行顺序和原理:
console.log('script start'); // 同步任务,立即执行,输出:script start async function async2() { console.log('async2 end'); // 调用时会输出:async2 end } async function async1() { await async2(); // 执行 async2() 输出:async2 end,然后暂停 async1,后续代码加入微任务队列 console.log('async1 end'); // 微任务,事件循环本轮同步和微任务执行完后执行 } async1(); // 执行 async1,触发 async2 的同步部分 setTimeout(() => { console.log('setTimeout'); // 宏任务,下一轮事件循环执行,最后输出:setTimeout }, 0); new Promise((resolve, reject) => { console.log('promise'); // 同步任务,立即输出:promise resolve(); // 立即完成,then() 加入微任务队列 }) .then(() => { console.log('then1'); // 微任务,事件循环同步任务后执行,输出:then1 }) .then(() => { console.log('then2'); // 微任务链,then1 后立即执行,输出:then2 }); console.log('script end'); // 同步任务,立即输出:script end /* 执行顺序总结: 1. script start 2. async2 end 3. promise 4. script end --- 微任务开始 --- 5. async1 end 6. then1 7. then2 --- 宏任务开始 --- 8. setTimeout */
4、React中,不通过props显示传参,使用ref方式,让父组件访问子组件的函数和属性
这个场景通常用于操作子组件的某些方法,比如重置表单、调用动画、滚动定位等。
以下是一个完整示例,演示如何使用 forwardRef 和 useImperativeHandle 来实现这个功能。
✅ Step 1:子组件使用 forwardRef 和 useImperativeHandle
// Child.jsx
import React, { useImperativeHandle, forwardRef, useState } from 'react';
const Child = forwardRef((props, ref) => {
const [count, setCount] = useState(0);
// 暴露给父组件的函数和属性
useImperativeHandle(ref, () => ({
increment() {
setCount(prev => prev + 1);
},
reset() {
setCount(0);
},
getCount() {
return count;
}
}));
return (
<div>
<h3>子组件 Count: {count}</h3>
</div>
);
});
export default Child;
✅ Step 2:父组件使用 useRef 调用子组件方法
// Parent.jsx
import React, { useRef } from 'react';
import Child from './Child';
function Parent() {
const childRef = useRef();
const handleClick = () => {
childRef.current.increment(); // 调用子组件的 increment 方法
};
const handleReset = () => {
childRef.current.reset(); // 调用子组件的 reset 方法
};
const handleLog = () => {
console.log('当前 count:', childRef.current.getCount()); // 获取 count 值
};
return (
<div>
<h2>父组件</h2>
<button onClick={handleClick}>加一</button>
<button onClick={handleReset}>重置</button>
<button onClick={handleLog}>打印 Count</button>
<Child ref={childRef} />
</div>
);
}
export default Parent;
💡说明
| 方法/钩子 | 作用 |
|---|---|
forwardRef | 让子组件接受 ref |
useImperativeHandle(ref, () => ({ ... })) | 控制暴露给父组件的实例方法和属性 |
useRef | 父组件中创建 ref 用于访问子组件 |
需要注意的是: 🔸 useImperativeHandle 只会暴露你定义的那部分接口,而不会暴露整个子组件实例,封装更好。 🔸 不推荐滥用 ref,只有在父组件需要直接控制子组件行为时才使用。
5、盒模型的box-sizing
| 属性 | content-box(默认) | border-box(推荐) |
|---|---|---|
width / height 作用范围 | 只包含内容(content)区域 | 包含内容 + padding + border |
| 实际尺寸 | width + padding + border | 正好等于 width,padding 和 border 会“侵占”内容区域 |
| 设置宽高后 | 添加 padding 或 border 会增大盒子总尺寸 | 添加 padding 或 border 不会改变盒子总尺寸 |
| 布局控制难度 | 难控制:需要手动计算 padding/border 导致的尺寸变化 | 易控制:设置多少宽高就是多少 |
| 适合场景 | 精细排版、兼容旧代码 | 响应式布局、现代开发推荐使用 |
| 实际示例 | 200px + 20px*2 padding + 5px*2 border = 250px | 总宽固定为 200px,内容区域变小 |
✅ 建议: 开发时全局设置 box-sizing: border-box 更加好用且好维护:
*::before, *::after {
box-sizing: border-box;
}
6、JS中判断变量是否为数组的方法
| 方法 | 示例代码 | 返回结果 | 是否推荐 | 说明 |
|---|---|---|---|---|
Array.isArray(value) | Array.isArray([1, 2, 3]) | true | ⭐⭐⭐⭐⭐ | ✅ ES5+ 标准方法,语义清晰,兼容性好,最推荐 |
value instanceof Array | [1, 2, 3] instanceof Array | true | ⭐⭐ | ❗ 在不同 iframe/window 中失效,不推荐跨窗口判断 |
Object.prototype.toString.call(value) | Object.prototype.toString.call([1, 2, 3]) | "[object Array]" | ⭐⭐⭐⭐ | ✅ 严谨,可跨 iframe,但写法繁琐 |
typeof value | typeof [1, 2, 3] | "object" | ❌ | ❌ 无法区分数组和普通对象,不能用来判断数组 |
7、类数组对象
类数组对象就是:
有
length属性,且用数字作 key 的对象,但它不具备数组的所有方法(如forEach、push、map等)。
✅ 一个例子
const arrayLike = {
0: 'hello',
1: 'world',
length: 2
};
console.log(arrayLike[0]); // 'hello'
console.log(arrayLike.length); // 2
长得像数组,但你不能直接用数组方法:
arrayLike.forEach(item => console.log(item)); // ❌ 报错:not a function
🔍 常见的类数组对象
| 类数组对象 | 来源 |
|---|---|
arguments | 函数内部的参数集合(非箭头函数) |
NodeList | DOM API,比如 document.querySelectorAll() 返回的 |
HTMLCollection | 比如 document.forms、document.images 等 |
String | 字符串本身也满足类数组条件(可索引 + length) |
🔄 如何把类数组对象变成真正的数组?
✅ 方法一:Array.from()
const arr = Array.from(arrayLike);
适用于 NodeList、arguments、自定义类数组等。
✅ 方法二:...展开运算符
const arr = [...arrayLike]; // 注意:必须可迭代(如 NodeList、字符串),纯对象不行
js复制编辑function test() {
const args = [...arguments]; // ✅ arguments 转数组
}
✅ 方法三:Array.prototype.slice.call()
const arr = Array.prototype.slice.call(arrayLike);
老派写法,在 ES6 之前很常用。
8、useState是同步还是异步,如果想立即调用数据怎么办
useState 本身是 同步的,但它更新 state 后不会立刻反映到下一行代码中,这是为什么很多人觉得它“异步”的原因。
让我们看个例子:
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // 这里打印的还是旧的值
};
虽然 setCount(count + 1) 是同步调用的,但 React 会把更新状态这件事安排到一次“批量更新”流程中,等它重新渲染组件的时候,才会看到更新后的值。
那如果想“立即”拿到最新值怎么办?
有几种方式可以解决这个问题:
✅ 方式 1:用函数式更新拿到最新的值
setCount(prev => {
const newCount = prev + 1;
console.log(newCount); // 拿到最新值
return newCount;
});
✅ 方式 2:放到 useEffect 中监听 count 的变化
useEffect(() => {
console.log("count 改变了:", count);
}, [count]);
✅ 方式 3:用 useRef 存一下中间值
const latestCountRef = useRef(count);
const handleClick = () => {
const newVal = count + 1;
latestCountRef.current = newVal;
setCount(newVal);
console.log("最新值是:", latestCountRef.current);
};
这个办法虽然不能触发组件重新渲染,但你可以拿它当临时变量缓存用。