文章目录
- 前言
-
- [1. Vue3 和 Vue2 的响应式原理](#1. Vue3 和 Vue2 的响应式原理)
- [2. TS 的 declare 关键字](#2. TS 的 declare 关键字)
- [3. TS 中 type 和 interface 的区别](#3. TS 中 type 和 interface 的区别)
- [4.JS 的 map 详解](#4.JS 的 map 详解)
- [5. 前端跨页面通信](#5. 前端跨页面通信)
- [6. JS 脚本延迟加载的方式](#6. JS 脚本延迟加载的方式)
- [7. Vue 模板是如何编译的](#7. Vue 模板是如何编译的)
- [8. Vue3 相比 Vue2 在编译阶段的改进](#8. Vue3 相比 Vue2 在编译阶段的改进)
- [9. Vue 的 scoped 和 CSS 原理](#9. Vue 的 scoped 和 CSS 原理)
- [10.什么是虚拟 DOM?如何实现?](#10.什么是虚拟 DOM?如何实现?)
前言
大家好,今天是冲击中级工程师第三天,让我们来完成今天的面试题打卡。
1. Vue3 和 Vue2 的响应式原理
答案:vue2基于 Object.defineProperty,在组件初始化时,遍历 data 中的所有属性,使用 Object.defineProperty 把它们转为 getter/setter。它的缺点是
1.无法检测对象属性的添加或删除(所以有了 this.$set)。
2.对数组支持不佳:需要重写数组的原型方法(push, pop等)。
3.性能开销:必须递归遍历整个对象树。
vue3是基于proxy来进行实现的,直接监听整个对象,而不是属性。不再需要特殊的set和get方法。性能方面也会更强,因为只有在访问某个嵌套对象时,才会按需进行下一层的响应式代理(懒处理)。
2. TS 的 declare 关键字
答案:核心作用是:告诉编译器,某个东西已经存在了,别报错(声明这个东西)。
当使用第三方 JS 库,或者在 HTML 中引入了全局变量时,TS 并不认识它们。
用法:
declare var/let/const:声明全局变量。
declare function:声明全局函数。
declare module:声明一个模块(常用于 .d.ts 文件)
3. TS 中 type 和 interface 的区别
直接看表格:
| 特性 | interface(接口) | type(类型别名) |
|---|---|---|
| 定义范围 | 只能定义对象/函数结构 | 可以定义任意类型(联合类型、元组、原始类型等) |
| 合并能力 | 同名自动合并(Declaration Merging) | 不允许重名 |
| 继承方式 | 通过 extends 继承 |
通过 &(交叉类型)模拟继承 |
| 适用场景 | 库、框架的 API 定义,方便扩展 | 复杂的组合类型、联合类型 |
4.JS 的 map 详解
答案:需要知道的第一件事:map有三个参数
Array.prototype.map((value, index, array) => { ... })
第一个参数:value (当前值):当前正在处理的数组元素。
第二个参数:index (下标):当前元素的索引(从 0 开始)。
第三个参数:array (原数组):调用 map 方法的数组对象本身。
经典面试题:
javascript
console.log(['1', '2', '3'].map(parseInt));
输出:[ 1, NaN, NaN ]
解析:parseInt有两个参数,第一个参数是,目标值,第二个参数是进制。
javascript
parseInt(string, radix)
所以等价于
parseInt('1', 0) // index = 0
parseInt('2', 1) // index = 1
parseInt('3', 2) // index = 2
第一条,radix传入0,采用默认进制,输出10,第二条,进制是1,radix再2-36之间,所以报错输出NaN,第三条,不存在三进制,同样输出NaN
5. 前端跨页面通信
答案:
1.localStorage + storage 事件,最经典的方式。
javascript
// A 页面
localStorage.setItem('msg', JSON.stringify({ text: 'hello' }));
// B 页面
window.addEventListener('storage', (e) => {
if (e.key === 'msg') {
console.log(JSON.parse(e.newValue));
}
});
2.BroadcastChannel
javascript
const channel = new BroadcastChannel('my-channel');
// 发送
channel.postMessage({ text: 'hello' });
// 接收
channel.onmessage = (e) => console.log(e.data);
3.SharedWorker
javascript
// shared-worker.js
onconnect = (e) => {
const port = e.ports[0];
port.onmessage = (ev) => {
port.postMessage(ev.data);
};
};
4.Service Worker
javascript
navigator.serviceWorker.controller.postMessage('hello');
6. JS 脚本延迟加载的方式
答案:主要分为以下几种
javascript
<script defer>
异步加载,但在 DOM 解析完成后、DOMContentLoaded 事件前执行。
多个 defer 脚本按顺序执行。
javascript
<script async>
异步加载,一旦加载完立即执行(会中断 DOM 解析)。
执行顺序不确定,谁先下好谁执行。
动态创建 DOM:通过 document.createElement('script') 插入,默认行为类似 async。
放在 body 底部:最原始的方式,确保 HTML 先显示。
7. Vue 模板是如何编译的
解析:Vue 的模板编译流程是:先将模板字符串解析成抽象语法树(AST),分析指令和绑定关系;然后根据 AST 生成渲染函数(render function);运行时渲染函数会创建虚拟 DOM(VNode),并通过虚拟 DOM 对比(diff)更新真实 DOM,实现高效渲染。整个过程把模板转换为可执行函数,保证数据变化能快速映射到页面。
8. Vue3 相比 Vue2 在编译阶段的改进
也是直接看对比
| 改进点 | 说明 |
|---|---|
| 静态节点提升(Hoisting) | 将不会变化的静态节点在编译阶段提升到渲染函数外,避免每次渲染重复创建,提高性能。 |
| 静态属性标记(Patch Flag) | 编译器会为动态节点打标记(Patch Flag),运行时只更新变化部分,减少 diff 开销。 |
| 更轻量的 AST | Vue 3 编译器生成的 AST 更精简,减少解析和生成成本。 |
| Fragment 支持 | 模板可返回多个根节点(Fragment),编译器会生成对应的 VNode 列表。 |
| v-once / v-memo 优化 | 提前处理一次性渲染或条件渲染,避免重复计算。 |
| Tree-shaking 更友好 | 编译输出函数更模块化,有助于去掉未使用指令/API,减小包体积。 |
9. Vue 的 scoped 和 CSS 原理
解析:给组件内的所有 DOM 加上一个独一无二的动态属性(如 data-v-f3f3eg)。这样就保证了组件内的css的独立,编译后的例子如下:
javascript
HTML: <div class="title" data-v-f3f3eg></div>
CSS: .title[data-v-f3f3eg] { color: red; }
追问:那么如果使用了深度选择器,会怎么样?
答案:深度选择会穿透这个样式,去修改里面的子组件。因此,如果你使用了深度选择时,会修改到相同类名的样式,这个在使用框架时要注意。
10.什么是虚拟 DOM?如何实现?
解析:通俗来讲,虚拟DOM实际上就是一个普通的js对象,是对真实DOM的一种轻量级描述。使用它配合diff算法能够减少不必要的真实DOM操作。
实现步骤:
1创建 (h 函数):接收 tag, props, children,返回一个 JS 对象。
渲染 (render 函数):递归遍历这个 JS 对象,用 document.createElement 创建真实节点并挂载。
更新 (diff/patch):对比新旧两个 JS 对象,找出差异,只给真实 DOM 补丁。
#总结
今天面试题就到这里,让我们每天学习几个题,一起成长。管他难易,获得成长就好。明天见。