🔥9道题穿透JS底层:堆栈/异步/执行栈连环问,第5题99%人翻车?📉

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)

graph TB A[JS引擎] --> B[调用栈] B --> C{异步API?} C -->|是| D[Web API环境: DOM, setTimeout, fetch] D --> E[任务队列: Macro/Micro] E --> F[事件循环检查调用栈] F -->|空| G[推任务进调用栈] C -->|否| B style A fill:#f9f,stroke:#333,stroke-width:2px style D fill:#bbf,stroke:#333,stroke-width:2px style E fill:#ff9,stroke:#333,stroke-width:2px style F fill:#9f9,stroke:#333,stroke-width:2px

⚡异步不是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. 同步代码入栈执行 → 1, 4
  2. setTimeout → 丢给Web API,到期后进宏任务队列
  3. Promise.then → 进微任务队列
  4. 同步执行完 → 清空微任务(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执行过程分哪些阶段?

  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();     // window, window
obj.foo(); // obj, window(词法仍指向全局name)

「Q21: 箭头函数的this是词法作用域吗?💥」

A21: 是!箭头函数没有自己的this,继承外层函数定义时的作用域。

相关推荐
文心快码BaiduComate2 分钟前
新增Zulu-CLI、企业版对话支持自定义模型、一键设置自动执行、复用相同终端,8月新能力速览!
前端·后端·程序员
walking9575 分钟前
JavaScript 神技巧!从 “堆代码” 到 “写优雅代码”,前端人必看
前端·面试
前端西瓜哥10 分钟前
图形编辑器开发:基于矩阵的画布缩放和移动实现
前端
walking95710 分钟前
前端 er 收藏!高性价比 JS 工具库,轻量又强大
前端·面试
gongzemin19 分钟前
Vue 项目权限管理 路由 按钮权限
前端·vue.js
walking95727 分钟前
效率党必藏! JavaScript 自动化脚本,覆盖文件管理、天气查询、通知提醒(含详细 demo)
前端·面试
lichenyang45327 分钟前
用React写一个技能冷却的案例,关于节流
前端
walking95730 分钟前
前端进阶必看!藏在浏览器与代码里的技巧
前端
又写一个小bug1 小时前
如何让你的Vue项目支持局域网访问 - 完整指南
前端
我现在不喜欢coding1 小时前
为什么runloop中先处理 blocks source0 再处理timer source1?
ios·面试