# 执行上下文(EC)

执行上下文可以简单理解为一个对象

  • 它包含三部分

    • 变量对象(VO)
    • 作用域链(词法作用域)
    • this 指向
  • 它的类型

    • 全局执行上下文
    • 函数执行上下文
    • eval 执行上下文
  • 代码执行过程

    • 创建 全局上下文(global EC)
    • 全局执行上下文(caller)自上而下 逐行执行。遇到函数时,函数执行上下文(callee)被 push 到执行栈顶
    • 函数执行上下文被激活,成为 active EC,开始执行函数中的代码,caller 被挂起
    • 函数执行完后,callee 被 pop 移除出执行栈,控制权交还给全局上下文(caller)继续执行

# 变量对象

变量对象是执行上下文中的一部分,可以抽象为一种 数据作用域,其实也可以理解为就是一个简单的对象,他存储遮盖执行上下文中所有的 变量和函数声明(不包含函数表达式)

活动对象(AO):当变量对象所处的上下文为 active EC 时,被称为活动对象

# 作用域

执行上下文中还包含作用域链。

作用域可以理解为该上下文中声明的 变量和声明的作用范围,可分为 块级作用域函数作用域

特性:

  • 声明提前:一个声明在函数体内都是可见的,函数优先于变量
  • 非匿名自执行函数,函数变量为 只读 状态,无法修改
let foo = function() { console.log(1) };
(function foo() {
  foo = 10 // 由于 foo 在函数中是只读的,因此赋值无效
  console.log(foo)
}())
// 结果打印:  ƒ foo() { foo = 10 ; console.log(foo) }

# 作用域链

作用域链使得我们可以在执行上下文中访问父级甚至全局的变量。作用域链可以理解为一组对象列表,包含 父级和自身的变量对象,它由两部分组成:

  • [[scope]] 属性:指向父级变量对象和作用域链,也就是包含了父级的 [[scope]] 和 AO
  • AO:自身活动对象

如此,便形成一条自上而下的 链式作用域

# 闭包

闭包属于一种特殊的作用域,称之为 静态作用域。他的定义可以理解为:父函数被销毁 的情况下,返回的子函数的 [[scope]] 中仍然保留着父级的单变量对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称之为闭包

# 闭包的作用

  • 外部可以读取函数内部的变量
  • 让函数内部的变量一直保持不被销毁

# 经典问题

多个子函数的 [[scope]] 都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响

解决:

  • 变量可以通过 函数参数的形式 传入,避免使用默认的 [[scope]] 向上查找
  • 使用 setTimeout 包裹,通过第三个参数传入
  • 使用 块级作用域,让变量成为自己上下文的属性,避免共享