堆栈/异步/执行栈连环问

1. 数据类型分别存在哪里?

(1) var a = {name: "前端开发"}; var b = a; a = null,b输出什么?

 1var a = {name: "前端开发"}; 
 2var b = a;                 
 3a = null;                  
 4console.log(b);            

关键点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示意:

 1(Stack)        (Heap)
 2+-------+       +-------------+
 3|   a   | ----> | {b: 1}      |
 4+-------+       +-------------+

⚠️你以为a存的是{b:1}?不,它只存了个“门牌号”!


(3) var a = {b: {c: 1}} 存放在哪里?

ASCII示意:

 1
 2+-------+       +-------------+
 3|   a   | ----> | b: ──────┐  |
 4+-------+       +----------|--
 5
 6                   +-------------+
 7                   | c: 1        |另一个堆对象
 8                   +-------------+

⚠️每层对象都是独立堆内存块!

「Q4: 深拷贝为什么慢?💥」
A4: 因为要递归遍历所有嵌套对象,在堆中新建副本,还要重建指针关系。

「Q5: WeakMap的key为什么只能是对象?🤯」
A5: 因为它用弱引用指向堆对象,不影响垃圾回收,基本类型无法弱引用。


2. 栈和堆的区别?

维度栈(Stack)堆(Heap)
管理方式自动管理(LIFO)手动/GC管理
速度快(连续内存)慢(碎片化)
生命周期与函数执行周期绑定动态,直到无引用
存储内容基本类型、函数参数、局部变量、执行上下文对象、数组、闭包环境
内存分配编译时确定大小运行时动态分配

「Q6: 递归太深为什么会栈溢出?💥」
A6: 每次调用函数都会在栈压入执行上下文,超出内存限制 → Maximum call stack size exceeded

「Q7: 堆内存一定比栈大吗?❓」
A7: 通常如此,但取决于引擎实现和系统资源。


3. 垃圾回收时栈和堆的区别?

  • :函数执行完自动弹出,变量自动销毁,无需GC介入 ✅
  • :需GC(如V8的分代回收)标记-清除/引用计数来回收无用对象
 1function foo() {
 2  var obj = {a: 1}; 
 3}
 4foo(); 

「Q8: 闭包为什么会导致内存泄漏?⚠️」
A8: 内层函数引用外层变量,外层执行完但变量仍被引用 → 堆对象无法回收!

「Q9: WeakSet如何避免内存泄漏?👉继续看?」
A9: 它的元素是弱引用,不影响GC,适合临时存储对象元数据。


4. 数组取第1个和第10万个元素时间差?

几乎无差别!O(1)

因为数组是连续内存块,通过基地址 + 索引 × 元素大小直接计算位置。

 1address = baseAddress + index * sizeof(element)

无论 index=0 还是 index=99999,计算一步到位。

「Q10: 什么数据结构查第n个元素是O(n)?💥」
A10: 链表!必须从头逐个遍历。

「Q11: JS数组真是连续内存吗?❓」
A11: 对密集数组(数值索引连续),V8会优化为连续存储;稀疏数组用哈希表。


5. 栈和堆具体怎么存储?

栈存储结构(函数调用栈)

 1+------------------+
 2| foo() 执行上下文  |
 3| - 变量对象        |
 4| - this           |
 5| - 作用域链        |
 6+------------------+
 7| bar() 上下文      |
 8+------------------+
 9| global 上下文     |
10+------------------+

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. 异步整个执行周期?

 1console.log(1);
 2setTimeout(() => console.log(2), 0);
 3Promise.resolve().then(() => console.log(3));
 4console.log(4);
 5

执行流程:

  1. 同步代码入栈执行 → 1, 4
  2. setTimeout → 丢给Web API,到期后进宏任务队列
  3. Promise.then → 进微任务队列
  4. 同步执行完 → 清空微任务(3)→ 下一轮事件循环取宏任务(2)

「Q14: 微任务一定比宏任务先执行吗?👉继续看?」
A14: 是!每轮事件循环末尾都会清空微任务队列。


8. Async/Await怎么实现?

语法糖 + Promise + 状态机

 1async function fn() {
 2  const res = await fetch('/api');
 3  return res.json();
 4}
 5
 6function fn() {
 7  return Promise.resolve(fetch('/api')).then(res => {
 8    return res.json();
 9  });
10}

Babel转译后本质是 generator + co 模式,自动管理Promise状态。

「Q15: await后面不是Promise会怎样?💥」
A15: 会被 Promise.resolve() 包装,立即resolve。


9. Promise和setTimeout执行先后?

 1setTimeout(() => console.log('宏'), 0);
 2Promise.resolve().then(() => console.log('微'));
 3
  • 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呢?

 1new Promise((resolve) => {
 2  console.log('构造函数立即执行'); 
 3  resolve(1);
 4}).then(res => {
 5  console.log('then是异步', res); 
 6});
 7
  • 构造函数:同步执行
  • 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()); } }

 1```js
 2
 3const events = {};
 4on('click', handler);
 5emit('click'); 

「Q19: Vue2的响应式是哪种?💥」
A19: 观察者模式!Dep是Subject,Watcher是Observer。


13. JS执行过程分哪些阶段?

  1. 语法分析:检查代码是否合法
  2. 预编译:创建GO/AO,变量提升
  3. 执行
    • 创建执行上下文(入栈)
    • 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();
obj.foo();

 1Q21: 箭头函数的this是词法作用域吗?💥」  
 2A21: 箭头函数没有自己的this继承外层函数定义时的作用域
个人笔记记录 2021 ~ 2025