今天复习了前端面试中最高频的七道经典题,涵盖 React/Vue 的 key 原理、map(parseInt) 陷阱、防抖节流、ES6 数据结构、遍历与拷贝、继承区别等。以下是问答整理。
第1题:React / Vue 中列表 key 的作用
我的回答:
- key 作为唯一标识,在渲染时避免不必要的重复创建与渲染。没有 key 时默认使用 index?
- 用 index 作为 key,增删改操作会导致索引错乱,列表重新排序时每个 DOM 元素的 key 都会改变。
- 如果两个子组件使用了相同的 key,只会渲染第一个,忽略或覆盖第二个。
补充:
- **key 的核心作用:**帮助框架(React/Vue)识别哪些元素改变了、添加了、删除了,从而优化 DOM 更新。在 diff 算法中,key 用于判断节点是否可复用。如果没有 key,框架会采用"就地复用"策略(如 React 默认会复用相同位置的节点,可能导致状态错乱)。
- **没有 key 时的默认行为:**React 不会自动使用 index,而是警告并采用"就地复用"。Vue 如果不用 key,会尽量复用元素,但可能导致状态问题。所以即使没有警告,也强烈建议总是提供唯一的 key。
- **index 作 key 的问题:**当列表顺序变化(增、删、排)时,index 对应的内容可能发生变化,导致不必要的 DOM 更新或组件状态错乱(比如输入框内容错位)。只有列表是静态的、不会重新排序时才勉强可用。
- **重复 key:**React 会在控制台警告,但实际渲染时,后出现的相同 key 的组件会被忽略或覆盖(具体取决于实现)。总之应避免重复。
第2题:['1','2','3'].map(parseInt)
我的回答:
- 输出 1, NaN, NaN。
- parseInt 接收两个参数:字符串和进制。map 回调传入三个参数:当前值、索引、原数组。因此 parseInt 的第一个参数是元素,第二个参数是索引,第三个参数被忽略。
- 一样吧?只不过一个是转化为整数,一个转化为浮点数。
补充:
- 输出确实是 [1, NaN, NaN]。
- **原因:**parseInt('1', 0) → 0 表示十进制,返回 1;parseInt('2', 1) → 1 不是有效进制(应为 2~36),返回 NaN;parseInt('3', 2) → 二进制中没有数字 3,返回 NaN。
- parseFloat 只接收一个参数,所以结果会是 1, 2, 3。
- parseFloat 只接收一个参数,map 传入的索引会被忽略,所以 parseFloat('1') → 1,parseFloat('2') → 2,parseFloat('3') → 3。
第3题:防抖与节流
我的回答:
- 防抖:多次触发只在最后一次执行;节流:在一段时间内只执行一次。
- 搜索框输入用防抖(最后一次提交),滚动加载用节流(固定频率执行)。
- 实现立即执行版防抖:增加 immediate 参数,第一次触发立即执行,后续延迟。
补充:
- 防抖和节流的实现细节:
- 防抖:每次触发清除定时器,重新计时。立即执行版需要维护一个 called 标志,第一次执行后设置定时器重置标志。
- 节流:时间戳版保证第一次立即执行,最后一次可能被忽略;定时器版保证最后一次一定会执行,但可能延迟。可结合两者实现更精确的节流。
- 实际场景:
- 防抖适用于搜索建议、窗口 resize、表单输入验证等。
- 节流适用于滚动加载、鼠标移动、按钮快速点击等。
第4题:Set、Map、WeakSet、WeakMap
我的回答:
- Set 和 Map 的键/值可以是任意类型(包括对象、基本类型)。WeakSet/WeakMap 的键只能是对象,且是弱引用。
- WeakMap 的弱引用意味着当键对象不再被其他地方引用时,会自动被垃圾回收,适合存储 DOM 元素相关的临时数据,避免内存泄漏。
- WeakMap 不可遍历(没有 keys()、values()、entries() 等方法)因为随时会回收所以遍历没有意义。
补充:
- Set 类似数组,成员唯一;Map 类似对象,键可以是任意类型。
- WeakSet 成员只能是对象,弱引用,不可遍历;WeakMap 键只能是对象,值可以是任意类型,弱引用,不可遍历。
- 弱引用的意义:防止因 Map 中保存了 DOM 元素引用而导致无法被回收。当 DOM 元素被移除时,WeakMap 中的键会自动消失,无需手动清理。
第5题:ES5 / ES6 继承的区别
我的回答:
- 本质上都是语法糖,但 ES5 需要手动复制,ES6 用 extends 直接继承。底层实现需补充。
- ES6 子类可以继承父类的静态方法(static),ES5 需要手动赋值(Child.proto = Parent)。
- ES6 的 super 必须在 this 前调用,因为 ES6 的 class 有"暂时性死区",必须先调用父类构造函数才能初始化 this。
补充:
- **底层实现:**ES5 继承通过原型链(Child.prototype = Object.create(Parent.prototype))和构造函数借用(Parent.call(this))实现。ES6 的 class 本质也是基于原型,但语法更清晰。
- **静态继承:**ES6 中 class Child extends Parent 会自动继承静态方法,即 Child.proto === Parent。ES5 需手动设置Object.setPrototypeOf(Child, Parent) 或直接 Child.proto = Parent。
- **super 的调用时机:**ES6 的 class 中,子类构造函数必须调用 super() 才能使用 this,因为 ES6 的类在构造函数中不会自动创建 this,而是通过 super() 返回父类的 this 并绑定给子类。这与 ES5 中先创建 this 再修改不同。
今日知识点总结
| 题目 | 核心要点 |
| key 的作用 | 帮助 diff 识别节点,避免状态错乱;避免用 index,用稳定唯一 id |
| map(parseInt) | 理解 parseInt 接收进制参数,map 传入索引导致 NaN |
| 防抖与节流 | 防抖:延迟执行且重置;节流:限制频率;实现需注意 this 和参数 |
| Set/Map/Weak... | 存储任意类型键值;Weak 系列弱引用,不可遍历,防止内存泄漏 |
| 继承区别 | ES5 通过原型 + 构造函数;ES6 class 语法糖,自动继承静态方法,super 必须先调用 |
|---|