- Published on
前端面试系列二
- Authors
- Name
- Tszkong Cheng
一、分享展示之前学过的一段比较满意或者认为有亮点的代码进行讲解思路
略
二、原型和原型链
一句话总结:
原型是对象的“继承模版”,原型链是对象查找属性时的“链式路径”
问题 | 回答要点 |
---|---|
什么是原型? | 对象的内部 [[Prototype]] 属性,指向另一个对象 |
什么是原型链? | 是对象查找属性时形成的一条链路,从当前对象沿着原型不断向上查找。 |
__proto__ 和 prototype 的区别? | __proto__ 是实例的原型;prototype 是构造函数的原型对象 |
原型链的终点? | null , 即 Object.prototype.__proto__ === null |
如何判断属性来自对象本身? | 使用 Object.hasOwnProperty('prop') |
instanceof 如何工作? | 判断构造函数的 prototype 是否存在于实例的原型链中 |
三、作用域和作用域链
作用域是 JavaScript 在定义变量时所决定的变量可访问范围。
类型 | 说明 |
---|---|
全局作用域 | 在任何函数体外部声明的变量 |
函数作用域 | 在函数内部定义的变量,只能在函数内用 |
块级作用域 | let /const 在花括号中的范围 |
作用域链:当访问一个变量时,JavaScript 引擎会从当前作用域向上查找父作用域,直到找到该变量或到达全局作用域。
问题 | 回答要点 |
---|---|
什么是作用域? | 变量可以被访问的区域范围 |
什么是作用域链? | 查找变量时,逐层往外查找的链条 |
JS 有哪些作用域? | 全局作用域、函数作用域、块级作用域 |
块级作用域和函数作用域的区别? | 块级用 let /const 声明,函数作用域用 var |
闭包和作用域的关系? | 闭包会持久化外层作用域的引用,形成私有变量 |
四、闭包
闭包是函数“记住”它创建时所处的作用域,即使它在当前作用域外执行。 当一个函数在其定义的作用域外被调用,但它依然能访问定义时的作用域中的变量,就形成了闭包。
应用场景 | 示例或说明 |
---|---|
封装私有变量 | 如上例中的 count |
模拟块级作用域 | 在 var 环境下隔离变量 |
函数柯里化 | function add(a){ return b => a+b } |
防抖 / 节流等高阶函数 | 需要保存计时器等中间状态 |
工具函数缓存 | 如 memoization |
问题 | 说明 |
---|---|
内存泄漏 | 闭包会持久化外部变量,注意清理引用 |
调试复杂 | 多层嵌套闭包调试困难,推荐使用命名函数 |
生命周期延长 | 被引用变量无法被 GC(垃圾回收) |
递归是闭包吗?
- 递归和闭包是两种概念,递归是函数的一种实现方式,闭包是一种作用域机制,它们可以结合,但递归本身不是闭包
五、事件循环(Event Loop)
JavaScript 是单线程语言,为了能够同时处理异步任务,需要有一个机制来处理任务,这个机制就是事件循环。
事件循环的核心逻辑是:
不断从任务队列中取出任务,放入主线程执行。
主要流程如下:
- 执行主线程中的同步任务(Script 脚本)。
- 执行完后,开始进入事件循环。
- 检查是否有微任务队列(Microtask Queue)中的任务,有则全部执行。
- 然后从宏任务队列(Macrotask Queue)中取出一个任务执行。
- 重复步骤 3~4,直到宏任务队列为空。
2. 宏任务(Macrotask)
宏任务(Macrotask)指的都是执行环境为浏览器的异步任务,比如:
- setTimeout
- setInterval
- setImmediate(Node.js)
- I/O
- requestAnimationFrame
3. 微任务(Microtask)
微任务(Microtask)指的都是执行环境为 Promise 的异步任务,比如:
- Promise.then / catch / finally
- queueMicrotask(原生 API)
4. 举例子说明执行顺序
console.log('1');
setTimeout(() => {
console.log('2');
Promise.resolve().then(() => {
console.log('3');
});
}, 0);
Promise.resolve().then(() => {
console.log('4');
setTimeout(() => {
console.log('5');
}, 0);
}).then(() => {
console.log('6');
})
setTimeout(() => {
console.log('7');
}, 0);
console.log('8');
执行顺序为: 1 8 4 6 2 3 7 5
六、深拷贝和浅拷贝
- 浅拷贝是复制内存中的地址,拷贝前后的对象,因为引用类型共享同一块内存,修改会相互影响。
- 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址。
- 原始数据类型(string、number、boolean、undefined、null)是值类型,拷贝时直接复制值本身,没有引用关系,不存在“深浅“之分。
七、Vue3 的特性
分类 | 新特性名称 | 简要说明 |
---|---|---|
🌟 核心特性 | Composition API | 组合式 API,增强逻辑复用和组织能力 |
<script setup> 语法糖 | 更简洁的组合式写法,自动引入变量、无需显式 return | |
Fragment | 一个组件可以返回多个根节点 | |
Teleport | 可将子组件渲染到 DOM 的其他位置 | |
Suspense | 支持异步组件的加载占位符 | |
⚙️ 性能优化 | 更快的虚拟 DOM 重写 | 比 Vue 2 更快、更节省内存 |
Tree-shaking 支持 | 未用 API 不会打包进最终代码 | |
编译优化 | 模板编译更智能、更高效 | |
📦 响应系统 | Proxy-based 响应式系统 | 用 Proxy 替代 Object.defineProperty ,支持更多类型(如数组、Map、Set) |
shallowRef 、readonly 等 | 提供更细粒度的响应式控制 | |
🧩 组件系统 | 更好的类型推导(TS 支持) | Vue 3 为 TypeScript 提供了一流的支持 |
多个 v-model 支持 | 同一组件支持多个 v-model 绑定 | |
自定义事件变更 | this.$emit 的使用方式变更,更清晰 | |
🛠 其他特性 | 新的生命周期名 | beforeUnmount / unmounted 替代旧的 destroyed 系列 |
自定义指令的钩子变更 | 生命周期钩子名称变更为更统一的形式 | |
emits 选项 | 显式声明组件可触发的事件,提高代码可维护性 |
Vue2 vs Vue3 自定义指令对比
对比项 | Vue 2 | Vue 3 |
---|---|---|
注册方式 | Vue.directive() 全局注册 / 局部注册 | 同样支持 app.directive() 全局注册 / 局部注册 |
生命周期钩子名称 | bind 、inserted 、update 、componentUpdated 、unbind | 统一为 created 、beforeMount 、mounted 、beforeUpdate 、updated 、beforeUnmount 、unmounted |
指令函数结构 | 可拆成多个钩子函数 | 推荐使用对象形式或函数形式(都支持) |
支持函数式简写 | ✅ 支持 | ✅ 同样支持 |
vnode 、oldVnode | 默认可访问 | 仍可访问,但结构略有不同 |
八、watch 和 computed 的区别
两个都可以监听数据的变化
✅ 最核心区别
特性 | computed 计算属性 | watch 侦听器(观察者) |
---|---|---|
是否有返回值 | ✅ 有,必须返回值(return ) | ❌ 没有返回值,只做副作用(如调用函数) |
是否支持缓存 | ✅ 支持,依赖不变则不会重新计算 | ❌ 不缓存,每次依赖变化就触发 |
适合场景 | 用于基于响应式状态的派生数据(展示、模板) | 用于异步操作或副作用处理(API 调用、DOM 操作) |
是否响应式 | ✅ 是响应式数据,可直接绑定在模板中 | ❌ 不是响应式数据,不能直接用于模板 |
何时触发 | 用作计算值,依赖变化才会重新计算 | 依赖变化就会执行处理函数 |
九、v-for 和 v-if 可以在同一元素上使用吗?
可以但不推荐,因为在同一元素上使用会导致渲染逻辑混乱和性能问题。
在 Vue2 中,v-for 的优先级更高,在 Vue3 中,v-if 的优先级更高。
在 Vue3 中,v-if 做了提升优化,去除了没有必要的计算,同时也带来了一个无法取到 v-for 遍历的 item 问题。
优化
- 不要把 v-if 和 v-for 放在同一元素上,带来性能问题(每次渲染都会先循环再进行条件判断)。
- 可以在外层嵌套 template 元素进行 v-if 判断,在内部进行 v-for 循环。
- 如果条件出现在循环内部,可以通过计算属性 computed 提前过滤掉那些不需要显示的项。