1. 数据类型分别存在哪里?
(1) var a = {name: "前端开发"}; var b = a; a = null
,b输出什么?
js
var a = {name: "前端开发"}; // a指向堆中对象地址
var b = a; // b复制a的地址 → 指向同一对象
a = null; // a被重定向为null,b仍指向原对象
console.log(b); // {name: "前端开发"} ✅
关键点 :b = a
是地址复制,不是深拷贝。a断开连接,b不受影响。
「Q1: 如果 b.name = "改了"
,a再访问会怎样?❓」
A1: a已为null,但若a未被赋null,a.name
也会变成"改了",因为指向同一堆对象!👉继续看?
「Q2: 什么情况下b会变null?💥」
A2: 只有 b = null
才行。或者全局作用域被销毁(如页面关闭)。
「Q3: 这种机制叫什么?🤯」
A3: 按引用传递(pass-by-reference)的错觉 ,其实是按值传递地址(call-by-sharing)。
(2) var a = {b: 1}
存放在哪里?
a
这个变量名:栈中{b: 1}
整个对象:堆中a
的值:指向堆中该对象的指针(内存地址)
ASCII示意:
css
栈(Stack) 堆(Heap)
+-------+ +-------------+
| a | ----> | {b: 1} |
+-------+ +-------------+
⚠️你以为a存的是{b:1}?不,它只存了个"门牌号"!
(3) var a = {b: {c: 1}}
存放在哪里?
js
// 拆解:
// a → 栈
// a.b → 指向堆中另一个对象 {c: 1}
// 所以:两个对象都在堆里,嵌套 = 指针链
ASCII示意:
lua
栈 堆
+-------+ +-------------+
| a | ----> | b: ──────┐ |
+-------+ +----------|--
↓
+-------------+
| c: 1 | ← 另一个堆对象
+-------------+
⚠️每层对象都是独立堆内存块!
「Q4: 深拷贝为什么慢?💥」
A4: 因为要递归遍历所有嵌套对象,在堆中新建副本,还要重建指针关系。
「Q5: WeakMap的key为什么只能是对象?🤯」
A5: 因为它用弱引用指向堆对象,不影响垃圾回收,基本类型无法弱引用。
2. 栈和堆的区别?
维度 | 栈(Stack) | 堆(Heap) |
---|---|---|
管理方式 | 自动管理(LIFO) | 手动/GC管理 |
速度 | 快(连续内存) | 慢(碎片化) |
生命周期 | 与函数执行周期绑定 | 动态,直到无引用 |
存储内容 | 基本类型、函数参数、局部变量、执行上下文 | 对象、数组、闭包环境 |
内存分配 | 编译时确定大小 | 运行时动态分配 |
「Q6: 递归太深为什么会栈溢出?💥」
A6: 每次调用函数都会在栈压入执行上下文,超出内存限制 → Maximum call stack size exceeded
「Q7: 堆内存一定比栈大吗?❓」
A7: 通常如此,但取决于引擎实现和系统资源。
3. 垃圾回收时栈和堆的区别?
- 栈:函数执行完自动弹出,变量自动销毁,无需GC介入 ✅
- 堆:需GC(如V8的分代回收)标记-清除/引用计数来回收无用对象
js
function foo() {
var obj = {a: 1}; // obj在栈,{a:1}在堆
}
foo(); // 函数结束 → obj出栈 → 堆中{a:1}失去引用 → 下次GC被回收
「Q8: 闭包为什么会导致内存泄漏?⚠️」
A8: 内层函数引用外层变量,外层执行完但变量仍被引用 → 堆对象无法回收!
「Q9: WeakSet如何避免内存泄漏?👉继续看?」
A9: 它的元素是弱引用,不影响GC,适合临时存储对象元数据。
4. 数组取第1个和第10万个元素时间差?
几乎无差别!O(1)
因为数组是连续内存块 ,通过基地址 + 索引 × 元素大小直接计算位置。
js
// 伪代码
address = baseAddress + index * sizeof(element)
无论 index=0 还是 index=99999,计算一步到位。
「Q10: 什么数据结构查第n个元素是O(n)?💥」
A10: 链表!必须从头逐个遍历。
「Q11: JS数组真是连续内存吗?❓」
A11: 对密集数组(数值索引连续),V8会优化为连续存储;稀疏数组用哈希表。
5. 栈和堆具体怎么存储?
栈存储结构(函数调用栈)
sql
+------------------+
| foo() 执行上下文 |
| - 变量对象 |
| - this |
| - 作用域链 |
+------------------+
| bar() 上下文 |
+------------------+
| global 上下文 |
+------------------+
LIFO,函数调用即压栈,return即弹栈。
堆存储结构
- 对象以键值对+隐藏类(Hidden Class) 存储,V8用指针指向属性位置
- 数组可能用快速属性(in-object)或慢速字典模式
「Q12: 为什么频繁增删对象属性会变慢?🤯」
A12: 因为会破坏隐藏类,V8无法做内联缓存优化 → 回退到字典查找。
6. JS怎么实现异步?
事件循环(Event Loop) + 回调队列(Callback Queue)
⚡异步不是JS干的,是浏览器/Node帮它托底!
「Q13: Node和浏览器的Event Loop一样吗?💥」
A13: 不同!Node有多个阶段(timers, poll, check等),浏览器更简单。
7. 异步整个执行周期?
js
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
// 输出:1 4 3 2
执行流程:
- 同步代码入栈执行 → 1, 4
setTimeout
→ 丢给Web API,到期后进宏任务队列Promise.then
→ 进微任务队列- 同步执行完 → 清空微任务(3)→ 下一轮事件循环取宏任务(2)
「Q14: 微任务一定比宏任务先执行吗?👉继续看?」
A14: 是!每轮事件循环末尾都会清空微任务队列。
8. Async/Await怎么实现?
语法糖 + Promise + 状态机
js
async function fn() {
const res = await fetch('/api');
return res.json();
}
// 等价于:
function fn() {
return Promise.resolve(fetch('/api')).then(res => {
return res.json();
});
}
Babel转译后本质是 generator + co
模式,自动管理Promise状态。
「Q15: await后面不是Promise会怎样?💥」
A15: 会被 Promise.resolve()
包装,立即resolve。
9. Promise和setTimeout执行先后?
js
setTimeout(() => console.log('宏'), 0);
Promise.resolve().then(() => console.log('微'));
// 输出:微 → 宏
Promise.then
→ 微任务setTimeout
→ 宏任务
微任务优先于宏任务执行
「Q16: 为什么要有微任务?直接都放宏任务不行吗?🤯」
A16: 为了保证异步回调能"同步"执行完,比如Promise链,避免中间被其他宏任务打断。
10. 为什么要区分微任务和宏任务?
为了执行优先级与一致性
- 微任务:当前操作的延续(如Promise链、MutationObserver)
- 宏任务:独立事件(如setTimeout、I/O、UI渲染)
想象你写代码:
then().then().then()
应该一口气执行完,而不是被中间插入一个setTimeout打断。
「Q17: Vue的$nextTick用了哪种任务?💥」
A17: 优先微任务(Promise.then),降级为宏任务(setTimeout)。
11. Promise构造函数是同步还是异步?then呢?
js
new Promise((resolve) => {
console.log('构造函数立即执行'); // 立即输出
resolve(1);
}).then(res => {
console.log('then是异步', res); // 微任务,稍后输出
});
// 输出顺序:构造函数立即执行 → then是异步 1
- 构造函数:同步执行
- then回调:异步(微任务)
「Q18: catch是同步还是异步?👉继续看?」
A18: 同样是微任务,和then一样异步执行。
12. 发布-订阅 vs 观察者模式?
观察者模式(Observer) | 发布-订阅(Pub-Sub) | |
---|---|---|
耦合度 | 高(目标直接管理观察者) | 低(通过事件中心解耦) |
通信方式 | 目标 → 观察者 | 发布者 ⇄ 事件中心 ⇄ 订阅者 |
角色 | Subject + Observer | Publisher + Broker + Subscriber |
js
// 观察者模式
class Subject {
observers = [];
addObserver(o) { this.observers.push(o); }
notify() { this.observers.forEach(o => o.update()); }
}
js
// 发布订阅
const events = {};
on('click', handler);
emit('click'); // 遍历执行
「Q19: Vue2的响应式是哪种?💥」
A19: 观察者模式!Dep是Subject,Watcher是Observer。
13. JS执行过程分哪些阶段?
- 语法分析:检查代码是否合法
- 预编译:创建GO/AO,变量提升
- 执行 :
- 创建执行上下文(入栈)
- this绑定
- 词法环境初始化
- 代码执行
- 上下文出栈
「Q20: let/const为什么有暂时性死区?🤯」
A20: 因为预编译时也提升,但不初始化,直到声明语句才初始化 → 中间区域访问报错。
14. 词法作用域和this的区别?
词法作用域(Lexical Scope) | this(动态绑定) | |
---|---|---|
确定时机 | 函数定义时(写代码时) | 函数调用时(运行时) |
决定因素 | 函数在哪定义 | 如何调用(谁调用、new、call等) |
查找方式 | 向外层作用域链查找 | 根据调用方式动态绑定 |
js
var name = 'window';
function foo() {
console.log(this.name); // 动态
console.log(name); // 词法:沿作用域链找
}
const obj = {name: 'obj', foo};
foo(); // window, window
obj.foo(); // obj, window(词法仍指向全局name)
「Q21: 箭头函数的this是词法作用域吗?💥」
A21: 是!箭头函数没有自己的this,继承外层函数定义时的作用域。