JavaScript 上下文(Context)

理解JavaScript执行上下文对于理解JavaScript的作用域、变量声明提升、this关键字和闭包等概念非常重要。

当执行一段 JS 代码运行时有两个步骤:编译执行。编译 JS 时会创建上下文,当执行上下文准备就绪时,执行步骤开始,所有可执行的 JavaScript 代码都是逐行运行的。

变量环境与词法环境

在 JavaScript 中,每个执行上下文都有两个与之相关的词法环境:变量环境(Variable Environment)和词法环境(Lexical Environment),它们共同构成了变量和函数在代码执行过程中的作用域和访问规则,从而确保了 JavaScript 代码的正确执行。变量环境和词法环境在执行上下文创建时被创建,并在执行上下文销毁时被销毁。

变量环境:用于存储当前执行上下文中声明的变量和函数声明。它主要用于变量和函数的提升🏗,即在代码执行前编译过程中将声明的变量和函数提前到代码块的顶部。

词法环境:是一种数据结构,用于存储变量和函数在代码中的定义位置以及它们所在的作用域,包括了当前执行上下文的变量环境和当前代码块所在的外部环境。

词法环境和变量环境的区别在于,词法环境不仅包括了当前执行上下文的变量环境,还包括了外部环境中的变量和函数定义。在访问一个变量或函数时,JavaScript 引擎会首先在当前词法环境的变量环境中查找,如果找不到则在外部环境中查找,直到找到或到达全局环境为止。

块级作用域创建的变量是放在词法环境中,退出块级作用域就被释放

var name = "markbuild"
var showName = function () { // 这个会覆盖下面一个
    console.log(name)
}
function showName () {
  console.log(name+'2')
}
showName() // 输出:markbuild

执行上下文本质上是一个调用栈。

当 showName 执行完,showName 执行上下文就会被弹出栈。
上面Call Stack 回调栈里的(anonymous) 就是全局执行上下文。
块级作用域中创建的变量在离开块级作用域时释放,他不会存储在变量环境中,而是存储在词法环境中。

闭包

普通函与箭头函数中this 的指向

箭头函数的执行上下文和普通函数的执行上下文的主要区别在于 this 的绑定。普通函数的 this 是动态绑定的,即它的值取决于函数的调用方式。而箭头函数的 this 是静态绑定的,即它的值取决于函数所在的词法作用域,也就是 this 值在函数定义时确定,怎么调用都不会改变。

普通函数的调用方式有以下几种:

  1. 函数调用:使用函数名直接调用函数,例如 foo(),this 继承父级作用域的 this。
  2. 对象方法调用:将函数作为对象的方法调用,例如 obj.foo(), this 指向调用该方法的对象obj。
  3. 构造函数调用:使用 new 关键字调用函数,例如 new Foo(), this 指向 new 关键字创建的新对象。
  4. apply/call/bind 调用,this 指向传入的第一个参数。

箭头函数的调用方式少一种,就是他不能作为构造函数。

arrow function is not a constructor

我们来测试下普通函数和箭头函数里 this 的区别

这里普通函数 this 指向对象o,而箭头函数的this 指向 window,上面Devtools中调试箭头函数时,它的 this 值显示为 undefined,这是因为箭头函数的 this 值已经在创建时被确定,并且无法在运行时动态地改变,与调试器当前所在的执行上下文无关。如果想要查看箭头函数内部的变量和状态,可以使用调试器提供的其他调试功能,如断点、监视器(Watch)。
这里普通函数this 指向对象 obj,而箭头函数指向 window

注意:箭头函数的 this 并不总是指向 window,是继承父级作用域的 this

箭头函数的this 会继承父级作用域的 this,并不总是指向 window。

此外,箭头函数的 arguments 对象也与普通函数的 arguments 对象不同。箭头函数没有自己的 arguments 对象,它会继承父级作用域中的 arguments 对象

call apply 和 bind

apply、call 和 bind 方法可以用来显式地改变函数执行时的 this 指向(箭头函数除外)。

他们都是JS 中函数的方法,用于更改函数执行时 this 的值及传递参数,区别就是传递参数的方式不同。

foo.call('mark')
foo.apply('ryan')
foo.bind('henry')()

再看一下箭头函数的

this 始终指向父作用域中的Window

严格模式

严格模式下,非箭头函数作为普通函数调用this 指向 undefined。其它情况的调用和非严格模式一样。

"use strict"
var name = "markbuild"
function f1 () {
  console.log(this.name) // 指向 undefined
}

非严格模式下,call / apply / bind 的参数为 undefined 或 null 时,this 指向 window,但在严格模式下,它的值还是 undefined 或 null。