
JavaScript 核心知识体系全景总结
以下从运行机制、变量访问、执行主体、对象模型四大维度,系统性梳理原型链、继承、闭包、作用域、执行上下文、this指向、call/apply/bind 七大核心概念,并明确各知识点的内在关联与底层逻辑。
一、执行上下文:代码运行的环境基石
核心定义:JavaScript 引擎执行代码时的运行环境单元,包含代码执行所需的全部信息(变量、函数、作用域链、this 等),是 JS 运行机制的最小单位。
1. 三类执行上下文
- 全局执行上下文:程序启动时唯一创建,全局代码均在此环境运行。浏览器中对应
window,Node 中对应globalThis。 - 函数执行上下文:函数被调用时动态创建,每次调用生成独立的新上下文。
- Eval 执行上下文:
eval()函数执行时创建,存在安全与性能问题,工程中极少使用。
2. 执行栈(调用栈)
- 遵循后进先出(LIFO)原则,管理所有执行上下文的生命周期。
- 程序启动时全局上下文入栈;函数调用时函数上下文入栈;函数执行完毕后上下文出栈,控制权回到上一层。
3. 执行上下文的生命周期
分为创建阶段和执行阶段两个步骤:
(1)创建阶段(函数调用后、代码执行前)
- 变量对象(VO):收集当前上下文中的变量声明、函数声明、形参。函数上下文中的变量对象称为活动对象(AO),进入执行阶段后激活。
变量提升、函数提升的本质:创建阶段 VO 已完成声明收集,因此代码执行前即可访问声明。
- 作用域链:由当前 AO 与上层上下文的 VO 组成,负责变量查找。
- this 绑定:确定当前上下文的 this 指向。
(2)执行阶段
完成变量赋值、函数引用与逐行代码执行;执行完毕后上下文出栈,等待垃圾回收。
二、作用域与作用域链:变量访问的静态规则
核心定义:作用域是变量和函数的可访问范围,是一套静态语法规则,在代码定义时就已确定,与执行位置无关。
1. 三类作用域
- 全局作用域:最外层作用域,全局变量随处可访问,页面关闭才销毁。
- 函数作用域:函数内部声明的变量仅在函数内部可访问,外部无法直接读取。
- 块级作用域(ES6):由
{}包裹,通过let/const声明的变量仅在当前代码块内生效,存在暂时性死区(TDZ),不存在变量提升。
2. 词法作用域(静态作用域)
JavaScript 采用词法作用域:函数的作用域在函数定义时就已固化,和调用位置、调用方式无关。与之相对的是动态作用域(如 bash 脚本),作用域在调用时才确定。
3. 作用域链
- 定义:访问变量时,JS 引擎从当前作用域开始,逐级向上追溯上层作用域,直到全局作用域,这个链式查找结构就是作用域链。
- 查找规则:就近原则,内层可访问外层,外层不可访问内层;同名变量内层覆盖外层。
- 本质:作用域链是执行上下文的核心组成部分,其结构由词法作用域静态决定。
三、闭包:作用域与生命周期的交叉产物
核心定义:闭包是函数与其词法作用域的组合。当内部函数引用了外部函数的变量,且内部函数在外部函数执行完毕后依然存活(被返回/被外部持有),就形成了闭包。
1. 形成闭包的三个必要条件
- 函数嵌套函数;
- 内部函数引用外部函数的变量或参数;
- 内部函数被返回或被外部环境持有(如赋值给全局变量、作为回调函数)。
2. 闭包的底层本质
外部函数执行完毕后,其执行上下文会出栈,但外部函数的活动对象(AO)不会被垃圾回收——因为内部函数的作用域链仍然持有对它的引用。
3. 典型应用场景
- 数据私有化/模块化:模拟私有变量,避免全局污染
function createCounter() { let count = 0; return { increment: () => count++, getCount: () => count }; } - 函数柯里化:参数复用、延迟执行;
- 防抖与节流:保存上一次执行的状态;
- 循环变量保留:解决
var声明下循环变量共享作用域的问题。
4. 注意事项
- 内存泄漏风险:闭包会长期持有外部作用域引用,若不当使用(如循环引用、持有大对象)会导致内存无法释放;
- 箭头函数同样可以形成闭包,且其 this 继承自外层词法作用域。
四、this 指向:执行上下文的动态主体
核心定义:this 是执行上下文的内置属性,指向当前代码的执行主体。this 的指向在函数调用时动态确定,与函数定义位置无关。
1. 六大场景的 this 指向
| 调用场景 | this 指向 | 补充说明 |
|---|---|---|
| 全局环境直接执行 | 全局对象(浏览器 window / Node globalThis) |
严格模式下为 undefined |
| 普通函数直接调用 | 全局对象 | 严格模式下为 undefined |
| 对象方法调用 | 调用该方法的对象 | 谁调用,就指向谁 |
构造函数调用(new) |
新创建的实例对象 | new 会创建空对象并绑定 this |
| 箭头函数 | 定义时外层词法作用域的 this | 无自身 this,不可被修改 |
| DOM 事件处理函数 | 绑定事件的 DOM 元素 | addEventListener 回调默认指向元素 |
2. new 操作符的执行过程
- 创建一个全新的空对象;
- 将空对象的
__proto__指向构造函数的prototype; - 将构造函数的 this 绑定到该空对象;
- 执行构造函数内部代码;
- 若构造函数返回对象则返回该对象,否则返回这个新对象。
3. this 绑定优先级(从高到低)
new 绑定 > bind 绑定 > call/apply 绑定 > 对象方法调用 > 默认绑定(普通函数调用)
4. 箭头函数的 this 特性
- 没有自身的 this、
prototype、arguments,不能作为构造函数; - this 继承自定义时外层作用域的 this,且无法通过 call/apply/bind 改变。
五、call / apply / bind:this 的手动控制
三者都是 Function.prototype 上的原生方法,核心作用是手动修改函数内部的 this 指向,是 this 动态绑定的核心 API。
1. 三者核心对比
| 方法 | 执行时机 | 参数形式 | 返回值 | 核心特点 |
|---|---|---|---|---|
call(thisArg, arg1, arg2, ...) |
立即执行 | 参数逐个传递 | 函数执行结果 | 改变 this 并立即调用 |
apply(thisArg, argsArray) |
立即执行 | 参数以数组/类数组传递 | 函数执行结果 | 适合参数数量不确定的场景 |
bind(thisArg, arg1, arg2, ...) |
不立即执行,返回新函数 | 参数可分批传递(柯里化) | 绑定了 this 的新函数 | 永久绑定 this,支持延迟执行 |
2. 关键注意点
- 若
thisArg传入null/undefined,非严格模式下 this 会指向全局对象,严格模式下为传入的值; - 箭头函数调用三者无效,无法修改其 this;
- 多次
bind只有第一次生效,后续绑定不会覆盖。
3. 经典应用场景
- 类数组借用数组方法:
Array.prototype.slice.call(arguments); - 构造函数继承:子类构造函数中借调父类构造函数;
- 精准判断数据类型:
Object.prototype.toString.call(obj); - 固定回调 this:如 React 类组件事件回调绑定。
六、原型与原型链:对象的属性查找机制
核心定义:原型是 JavaScript 实现面向对象的核心机制,原型链是对象属性/方法的查找规则,也是继承的底层基础。
1. 核心概念与三角关系
所有对象与函数都遵循原型三角关联:
- 构造函数:用于创建对象的函数,自带
prototype属性; - 原型对象:构造函数
prototype指向的对象,用于存放所有实例共享的属性和方法; - 实例对象:通过
new构造函数创建的对象,自带__proto__(内部属性[[Prototype]]),指向构造函数的原型对象; - constructor 属性:原型对象的
constructor指回对应的构造函数。
核心公式:
实例.__proto__ === 构造函数.prototype
构造函数.prototype.constructor === 构造函数
2. 原型链
- 定义:原型对象本身也是普通对象,同样拥有自己的
__proto__,指向其构造函数的原型;层层向上追溯,直到Object.prototype.__proto__ === null,这条链式结构就是原型链。 - 属性查找规则:访问对象属性时,先查找对象自身;找不到则沿
__proto__向上遍历原型链;到顶端仍未找到则返回undefined。 - 属性遮蔽:对象自身属性与原型属性同名时,优先读取自身属性。
3. 标准 API 与误区
__proto__是浏览器实现的非标准属性,工程中推荐使用:Object.getPrototypeOf(obj):获取对象原型Object.setPrototypeOf(obj, proto):设置对象原型
- 所有函数的默认原型都是
new Object(),因此原型链最终都会指向Object.prototype。
七、继承:基于原型链的代码复用方案
JavaScript 的继承本质是基于原型链的代码复用,经历了多个演进阶段,最终形成成熟的最优方案。
1. 七种继承方案演进
(1)原型链继承
- 实现:
Child.prototype = new Parent() - 缺陷:父类引用属性被所有子类实例共享;创建子类时无法向父类传参。
(2)构造函数继承(借调)
- 实现:子类构造函数内执行
Parent.call(this, ...args) - 优点:可传参、避免引用共享;
- 缺陷:只能继承实例属性,无法继承父类原型方法,方法无法复用。
(3)组合继承
- 实现:原型链继承方法 + 构造函数继承属性
- 缺陷:调用两次父类构造函数,子类原型上存在多余的父类实例属性。
(4)原型式继承
- 实现:基于已有对象创建新对象,对应 ES6
Object.create() - 缺陷:引用属性仍然被所有新对象共享。
(5)寄生式继承
- 实现:在原型式基础上封装函数,为对象新增方法
- 缺陷:方法无法复用,每个对象都有独立副本。
(6)寄生组合式继承(ES5 最优方案)
- 核心:子类原型继承父类原型的副本,不重复调用父类构造函数
function inherit(Child, Parent) { const proto = Object.create(Parent.prototype); proto.constructor = Child; Child.prototype = proto; } - 优势:仅调用一次父类构造函数,无冗余属性,完整保留原型链结构。
(7)ES6 Class 继承(语法糖)
- 语法:
class Child extends Parent { constructor() { super(); } } - 本质:底层仍然是寄生组合式继承,
super()对应构造函数借调,extends对应原型链关联。 - 特性:class 必须通过
new调用,无变量提升;静态方法通过static声明,继承自父类本身。
八、知识体系整体关联
- 底层运行层:执行上下文是代码运行的最小环境单元,执行栈管理生命周期;每个上下文包含变量对象、作用域链、this 三大核心要素。
- 变量访问层:作用域是静态语法规则,决定作用域链的结构;闭包是作用域与执行上下文生命周期不一致的产物,让函数可以“记住”定义时的环境。
- 执行主体层:this 是执行上下文的动态属性,由调用方式决定;call/apply/bind 提供了手动控制 this 的能力。
- 对象模型层:原型链是对象的属性查找机制,是 JS 面向对象的核心;从原型链到 ES6 Class,本质都是基于原型的代码复用。