Skip to content

前端核心知识点 01

闭包

闭包 是指一个函数能够访问并记住其外层作用域中的变量,即使外城函数已经执行完毕。

示例

js
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 的值

执行上下文的生命周期

创建阶段

  1. 确定 This Binding 根据调用方式设置 this
  2. 创建词法环境 用于 let const function
  3. 创建变量环境 用于 var 函数声明
  4. 初始化环境记录 为参数 变量分配内存 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

执行优先级

  1. 执行一个宏任务 (调用栈为空的时候从红任务队列取任务)
  2. 执行所有微任务 (清空微任务队列)
  3. 如果需要,执行 UI 渲染
  4. 重复以上步骤

每个宏任务执行完后,会立即清空当前微任务队列 微任务在当前宏任务上下文中具有更高调度优先级

栈帧

调用栈

一个后进先出的栈结构,用于管理函数的的调用顺序 确保函数执行完后能正确返回调用点

栈帧

调用栈中的每一个单元,对应一次函数调用 函数被调用时创建(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 中的构造函数和原型

js
// 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); // true

ES6 Class 语法

js
// 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

js
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