理解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
执行上下文本质上是一个调用栈。
闭包
普通函与箭头函数中this 的指向
箭头函数的执行上下文和普通函数的执行上下文的主要区别在于 this 的绑定。普通函数的 this 是动态绑定的,即它的值取决于函数的调用方式。而箭头函数的 this 是静态绑定的,即它的值取决于函数所在的词法作用域,也就是 this 值在函数定义时确定,怎么调用都不会改变。
普通函数的调用方式有以下几种:
- 函数调用:使用函数名直接调用函数,例如
foo()
,this 继承父级作用域的 this。 - 对象方法调用:将函数作为对象的方法调用,例如
obj.foo()
, this 指向调用该方法的对象obj。 - 构造函数调用:使用
new
关键字调用函数,例如new Foo()
, this 指向 new 关键字创建的新对象。 - apply/call/bind 调用,this 指向传入的第一个参数。
箭头函数的调用方式少一种,就是他不能作为构造函数。
我们来测试下普通函数和箭头函数里 this 的区别
注意:箭头函数的 this 并不总是指向 window,是继承父级作用域的 this
此外,箭头函数的 arguments 对象也与普通函数的 arguments 对象不同。箭头函数没有自己的 arguments 对象,它会继承父级作用域中的 arguments 对象。
call apply 和 bind
apply、call 和 bind 方法可以用来显式地改变函数执行时的 this 指向(箭头函数除外)。
他们都是JS 中函数的方法,用于更改函数执行时 this 的值及传递参数,区别就是传递参数的方式不同。
foo.call('mark')
foo.apply('ryan')
foo.bind('henry')()
再看一下箭头函数的
严格模式
严格模式下,非箭头函数作为普通函数调用,this
指向 undefined
。其它情况的调用和非严格模式一样。
"use strict"
var name = "markbuild"
function f1 () {
console.log(this.name) // 指向 undefined
}
非严格模式下,call / apply / bind 的参数为 undefined 或 null 时,this 指向 window,但在严格模式下,它的值还是 undefined 或 null。