JavaScript极简体系化面试题
以下若含有代码,是面试的代码阅读题,没对基本是必挂了
1. 数据类型
提问:JS中包含哪些数据类型?
- 基本数据类型:7个,包含Number、String、Boolean、Null、Undefined、Symbol、BigInt
- 引用数据类型:Object,包含(普通对象{}、Array、Date、Function)
常用类型判断
typeof x // 基本类型首选(注意 null)
Array.isArray(x) // 数组
x instanceof Foo // 原型链上是否有 Foo.prototype
Object.prototype.toString.call(x) // 最稳通用:"[object Date]" 等
Number.isNaN(x) // 判断 NaN
Object.is(a, b) // 比 === 更细:NaN、+0/-0
👉 注意点(面试常考)
typeof null === 'object'(历史遗留 Bug)- 基本类型 存储在栈内存 按值访问
- 引用类型 存储在堆内存 ; 栈中保存的是变量的 引用地址
⭐ 变量存在哪个位置为什么重要,最直接的就是你需要知道深/浅拷贝的区别,然后手撕出来;深入后你也需要了解框架里面的浅比较比的是啥。
2. 声明方式
提问:ES6中包含的新特性--const、let和var的区别:
| 区别 | const | let | var |
|---|---|---|---|
| 作用域 | 块级 | 块级 | 函数 |
| 变量提升 | 无(暂时性死区) | 无(暂时性死区) | 有 |
| 是否可重复声明 | ❌ 不可 | ❌ 不可 | ✅ 可 |
| 是否可重新赋值 | ❌ 不可(地址不可变,属性值可变) | ✅ 可 | ✅ 可 |
| --- |
题目:答案在最后
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i)
}, 0)
}
✅ 输出:3 3 3
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i)
}, 0)
}
✅ 输出:0 1 2
3. 闭包(涉及作用域)
什么是闭包: 闭包是:函数 + 定义时的词法作用域
面试回答: 当一个函数访问了其外部函数的变量,并且这个函数被返回或在外部执行,就形成了闭包。
闭包的作用:
- 数据私有化
- 保存变量状态
- 实现模块化
闭包的缺点:
- 内存不能及时释放
- 使用不当可能造成内存泄漏(然后官就要问你了解啥是内存泄漏不?(标记+清除)) ->
题目:
4. 原型链
什么是原型?
- 每个函数都有一个
prototype属性 - 每个对象都有一个
__proto__/Object.getPrototypeOf指向其构造函数的 prototype
什么是原型链?
当访问一个对象的属性时,
如果对象自身没有该属性,就会沿着 __proto__ 指向的原型对象查找,
直到找到为止,或者查找到 null 为止,这条查找路径称为 原型链。
function Foo() {}
const f = new Foo()
Foo.prototype === f.__proto__ // ?
f.__proto__.__proto__ === Object.prototype // ?
✅ 结果:Foo.prototype === f.__proto__ → true f.__proto__.__proto__ === Object.prototype → true
5. 继承:
提问:JS有几种继承方式?(中低频,因为官认为上面的都太简单了)
- 1️⃣ 原型链继承
- 2️⃣ 构造函数继承(借用构造函数)
- 3️⃣ 组合继承(最经典)
- 4️⃣ 原型式继承
- 5️⃣ 寄生组合继承(最优解) ⭐
- 6️⃣ ES6 class 继承(语法糖)(基于原型链实现的,只是语法更清晰)
6. JS是如何执行的?
1️⃣ 源码 → AST(抽象语法树)
2️⃣ AST → 字节码 / 机器码(JIT 编译)引擎生成字节码,热点代码可能被JIT优化成机器码
3️⃣ 运行时创建 执行上下文(Execution Context) ,压入调用栈
执行上下文包括:
-
Lexical Environment(词法环境) :
let/const、块级绑定、TDZ -
Variable Environment(变量环境) :
var、函数声明等 -
Scope Chain(作用域链)
-
this 绑定(由调用方式决定;箭头函数是词法捕获)
this 绑定(补一段超高频)
fn():严格模式undefined,非严格是globalThis(浏览器是window)obj.fn():this 指向objnew Fn():this 指向新实例call/apply/bind:显式绑定(bind 返回新函数)- 箭头函数:没有自己的 this,this 来自定义时外层作用域(与调用无关)
JS 执行模型
- 单线程
- 事件循环(Event Loop)
- 任务队列: 同步任务 & 异步任务(宏任务 / 微任务)
- 执行顺序(浏览器典型)
- 执行一段同步代码(一个宏任务)
- 清空所有微任务队列
- 取下一个宏任务……
(循环往复)
题目:
//最简单的
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
Promise.resolve().then(() => {
console.log(3)
})
console.log(4)
✅ 输出:1 4 3 2
console.log(1);
setTimeout(() => console.log(2), 0);
(async () => {
console.log(5);
await new Promise((r) => setTimeout(r, 0)); // 等一个宏任务
console.log(6);
})();
Promise.resolve().then(() => console.log(3));
console.log(4);
//最难的(有循环引用
let p; //一旦 then 的回调返回的值“最终等于它要 resolve 的那个 promise 自己” ,这个 promise 必须立刻 reject(抛出 `TypeError`),
p = Promise.resolve()
.then(() => {
return p; // 返回 p 本身
})
.then(() => {
console.log("B"); // 不会执行
})
.catch(() => {
console.log("C"); // 会执行
});
✅ 结果:只会打印 "C"(不会打印 "B"
//包含node的事件循环 `process.nextTick`(优先于 Promise 微任务)
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
process.nextTick(() => console.log("N")); // Node nextTick 队列
(async () => {
console.log(5);
await 0; // 等价 Promise.resolve(0),续写进 Promise 微任务队列
console.log(6);
})();
console.log(4);
✅在 Node 中典型输出:1 5 4 N 3 6 2
拓展:React的tsx是如何渲染成DOM的?VUE是如何变成真实DOM的?
React(JSX / TSX → DOM)
- 编译阶段
JSX/TSX 会被编译成 React Element 的创建表达式
- 旧:`React.createElement(...)`
- 新(automatic runtime):`jsx/jsxs`(不一定显式出现 createElement)
2. Render 阶段(构建 Fiber 树)
- React 根据 Element 生成/更新 **Fiber Tree**
- 做 **Reconciliation(协调)** :对比新旧 Fiber,计算需要的更新(diff)
- React 的 diff 关键点:
- 同层比较(按层级)
- 列表靠 `key` 辅助复用/移动(没有 key 会导致性能/状态问题)
3. Commit 阶段(真正改 DOM)
- 把计算好的变更提交到宿主环境(浏览器 DOM)
- 执行生命周期/副作用(如 `useEffect` 在 commit 后调度)
4. 调度与更新(React 18 常被问)
- 更新会被调度(Scheduler),可能进行批处理(batching)
- 并发特性(Concurrent)让渲染可被中断/恢复(概念上,不必展开太深)
Vue(Template → DOM)
-
编译阶段(SFC / Template)
- Template 会被编译成 `render()` 函数(内部调用 `h` 创建 VNode) - Vue 3 编译器会做很多优化: - 静态提升(static hoist) - Patch Flags(标记动态节点) - Block Tree(减少 diff 范围) -
响应式系统驱动更新
- Vue 3 用 `Proxy`(Vue 2 用 `Object.defineProperty`) - 组件渲染时会“依赖收集”:读取到的响应式数据会被 track - 数据变更时触发 trigger,把对应组件的更新放进调度队列 -
渲染与 Patch
- `render()` 生成 VNode - runtime 做 patch(对比新旧 VNode) - 最终更新真实 DOM - `nextTick` 本质是等待这轮 DOM patch 完成后的回调(通常基于微任务调度)
两种框架本质都是相同的,都是Virtual DOM + Diff 。