前端核心知识点 01
闭包
闭包 是指一个函数能够访问并记住其外层作用域中的变量,即使外城函数已经执行完毕。
示例
function outer() {
let x = 10
function inner() {
console.log(x)
}
return inner
}
const fn = outer()
fn()核心机制
- 词法作用域确定访问权限
- 函数创建时绑定
[[Environment]] - 外部函数执行完毕,执行上下文出栈
- 内部函数调用时查找变量
总结
闭包时函数与其创建时词法环境的绑定。 JS 引擎通过 [[Environment]] 内部属性实现这一机制。 当外部函数执行完毕,其执行上下文出栈,但只要内部函数存在,它就会通过 [[Environment]] 保持对外层变量的引用, 防止被 GC 回收。后续调用内部函数时,变量查找沿作用域链完成。一旦内部函数也被释放,GC才会回收这些变量。
词法作用域
词法作用域 是指变量和函数的作用域由他们在源代码中的位置决定的,而不是在运行时动态决定的。
词法作用域=静态作用域=基于代码结构的作用域
词法作用域的执行机制
构建作用域链
- 作用域链是一条由
[[Environment]]连接的词法环境链 - 每个函数都有
[[Environment]]指向它定义时的词法环境 - 变量查找时,沿着这条链向上找
决定 [[Environment]] 指向
- 当函数被创建时,引擎根据词法作用域设置它的
[[Environment]]属性 - 这个过程是静态的, 发生在解析的阶段,而不是执行阶段
影响执行上下文的创建
[[LexicalEnvironment]] 词法环境 [[ThisBinding]] 当前函数的 this 绑定 [[Environment]] 指向外层词法环境
执行上下文
执行上下文是用于跟踪代码执行的抽象结构
三个核心组件
- 词法环境 处理作用域和标识符解析,决定变量从哪里来
- 变量环境 处理变量声明 (var function)的绑定
- this 绑定 确定当前上下文中 this 的值
执行上下文的生命周期
创建阶段
- 确定 This Binding 根据调用方式设置 this
- 创建词法环境 用于 let const function
- 创建变量环境 用于 var 函数声明
- 初始化环境记录 为参数 变量分配内存 var 初始化为 undefined
执行阶段
代码逐行执行,变量被赋值,函数被调用
调用栈
- 一个后进先出的栈结构
- 用于管理函数的调用顺序
- 每个栈帧就是一个执行上下文
this的本质
this 的本质是 [[ThisBinding]]
在执行上下文创建阶段根据调用方式确定
- 对象中 指向对象
- 函数中 严格模式下指向 udefined
- new 中 指向新创建的实例
- call 中指向传入的对象
- 箭头函数 继承外层的
[[ThisBinding]]
new 操作符
- 一个函数能否被调用 ,取决于它是否实现了
[[Constructor]] - 箭头函数没有
[[Constructor]] - calss 方法默认不可构造
new 操作符的四步协议
第一步 创建一个空对象 第二步 将该对象的 【[[Prototype]]】 指向构造函数的 prototype 第三部 将 this 绑定到新创建的空对象 第四部 返回新创建的对象
事件循环
调用栈
调用栈是 JS 引擎 用来跟踪函数调用的栈结构
任务队列
任务队列存储着等待执行的异步任务 ,分为宏任务和微任务队列
宏任务
- 整体代码块
- setTimeout 和 setInterval
- I/ O 操作
- UI 渲染
- setImmediate
- MessageChannel
微任务
- Promise.then/catch/finally
- queueMicrotask
- MutationObserver
- process.nextTick
执行优先级
- 执行一个宏任务 (调用栈为空的时候从红任务队列取任务)
- 执行所有微任务 (清空微任务队列)
- 如果需要,执行 UI 渲染
- 重复以上步骤
每个宏任务执行完后,会立即清空当前微任务队列 微任务在当前宏任务上下文中具有更高调度优先级
栈帧
调用栈
一个后进先出的栈结构,用于管理函数的的调用顺序 确保函数执行完后能正确返回调用点
栈帧
调用栈中的每一个单元,对应一次函数调用 函数被调用时创建(push) ,函数返回时销毁(pop) 存储位置 栈内存 (Stack Memory),分配和释放极快
关键字段详解
- function 当前执行的函数对象。引擎知道现在在跑谁
- receiver 这就是 this 的物理存在 谁调用这个函数 receiver 就指向谁
- argv 函数参数的地址。 引擎通过它读取 argument
- context 指向堆上的 context 对象, 存储 let/const/var 变量
this的真相
this 是动态的 - 错 this 是 receiver 的值 - 对
所以 this 的绑定 ,发生在栈帧创建时,是静态的物理操作
Context 对象
把变量存在堆上,用 Context 对象统一管理
作用域链的物理实现
一个由 previous_ 指针连接 的 context 链表
闭包的本质
- 闭包 = 函数 +
[[Environment]] - 指针指向堆上的 context
- 只要函数存在 context 就不会被回收
核心结论
变量对象不在栈上,而在堆上的 Context 对象中
- let/const/var 声明的变量, 最终都存储在 Context 的 variable_ 数组中
- 闭包之所以能记住外层变量,是因为它持有
[[Environment]]的指针,指向外层的 Context - Context 的声明周期由GC管理,只有当所有的引用断开后才会被回收
ES6 带来的语言范式的变革
- 模块化编程
- 强作用域支持
- 异步编程新范式
V8 引擎优化
Hidden Classes 是 V8 用来优化对象属性访问的核心机制,在传统解释型语言中,每次访问对象属性都需要进行哈希查抄,这会带来性能开销。 V8 通过创建隐藏类来优化这一过程。
- Class 语法的结构更明确,使得 V8 能够更好地预测对象的形状,从而创建更有效的隐藏类。
- 块级作用域可以帮助 V8 更好地进行变量声明周期管理,减少内存占用。
- 箭头函数的词法绑定 this 特性使得 V8 可以做出更准确的优化假设。
- Proxy 对象无法像普通对象一样被 V8 优化 ,因为它们的属性防卫行为是动态的。
临时死区
TDZ的工作机制
TDZ 并不是因为变量不存在,而是因为变量在初始化之前不能被访问。JS 引擎在解析代码时知道这些变量的存在,但在它们被初始化之前,不允许被访问。
const 的不可变性边界
const 只保证变量的绑定是不可变的,而不是变量所引用的值不可变。 对于基本类型,这确实意味着值不可变,但对于对象和数组,它只防止重新赋值整个对象或数组,而步防止修改其内容。
箭头函数的 this 绑定机制
- 箭头函数不具有自己的 this,而是从外层作用域继承 this 值,这种机制被称为词法绑定。
- 没有 arguments 对象
- 不能用作构造函数
- 没有 prototype 属性
Promise 的状态机模型
Promise 的核心是一个状态机模型,具有三种互斥的状态:pending(待定)、fulfilled(已成功)和 rejected(已失败)
pending 是 Promise 的初始状态,此时异步操作正在进行中,Promise 既没有被兑现也没有被拒绝。
当 Promise 被成功兑现时,它会进入 fulfilled 状态,并携带一个值(value)。
当 Promise 被拒绝时,它会进入 rejected 状态,并携带一个原因(reason),通常是一个 Error 对象。
Promise 的状态转换是单向且不可逆的,一旦从 pending 转换为 fulfilled 或 rejected,就不能再改变状态。
Promise 的状态转换由 resolve 和 reject 函数触发,这两个函数作为参数传递给 Promise 的执行器函数。
async/await 在底层是通过生成器(Generator)和 Promise 实现的
Map 的基本用法
Set 的基本用法
理解 ES6 Class 的本质
ES5 中的构造函数和原型
// ES5 中的构造函数和原型
function PersonES5(name, age) {
this.name = name;
this.age = age;
}
PersonES5.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
PersonES5.prototype.getAge = function() {
return this.age;
};
// 创建实例
const person1 = new PersonES5('Alice', 30);
console.log(person1.greet()); // Hello, I'm Alice
// 检查原型链
console.log(person1.__proto__ === PersonES5.prototype); // true
console.log(PersonES5.prototype.constructor === PersonES5); // trueES6 Class 语法
// ES6 Class 语法
class PersonES6 {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
getAge() {
return this.age;
}
}
// 创建实例
const person2 = new PersonES6('Bob', 25);
console.log(person2.greet()); // Hello, I'm Bob
// 检查原型链
console.log(person2.__proto__ === PersonES6.prototype); // true
console.log(PersonES6.prototype.constructor === PersonES6); // true读取对象的原型
Object.getPrototypeOf(obj)
作用域
作用域是指变量和函数的有效访问范围
- 全局作用域
- 函数作用域
- 块级作用域
getter/setter
class Person {
constructor(firstName,lastName) {
this.firstName = firstName
this.lastName = lastName
this._age = 0
}
get fullName () {
return `${this.firstName} ${this.lastName}`
}
set fullName(value) {
const parts = value.split(' ')
this.firstName = parts[0]
this.lastName = parts[1]
}
get age () {
return this._age
}
set age (value) {
if (value < 0 || value > 150) {
console.log('Invalid age')
return
}
this._age = value
}
}
const person = new Person('John', 'dog')
console.log(person.fullName)
person.fullName = 'Jane Smith'
console.log(person.firstName)
console.log(person.lastName)
person.age = 30
console.log(person.age)
person.age = -5