Skip to content

前言

重新理一下js的中执行上下文和作用域的底层逻辑,以及this的中一些误区

执行上下文

js引擎在执行代码时会创建三种上下文,全局上下文,函数上下文和eval上下文。上下文以栈的形式保存,最开始,js引擎会创建一个全局上下文,推入栈。当执行到一个函数时,会创建一个函数上下文,入栈,再执行上下文的初始化。函数中又会一层一层的创建上下文,持续的推入栈形成上下文栈,当函数执行完成后,栈会弹出销毁。 上下文包含三个要素,变量对象,作用域和this推导。变量对象(VO),函数中又称为AO,是在函数执行时才激活的对象。函数执行时先分析生成AO,包括形参,函数声明和变量声明(注意有先后顺序),如果变量声明和函数以及形参冲突,则变量声明取消。分析完后进入函数执行过程,这时候又会动态的调整AO。

其中let和const不会在上下文初始化时进行初始化,而是延迟初始化,在初始化之前访问都会报错。

函数在创建的时候会将当前的上下文的scoped保存到函数的[[scoped]]对象中,当函数上下文初始时,函数的scoped = object.assign([[scoped]], AO)。这样形成了作用域链。

js
function a(b, c) {
    console.log(d)  // function
    function d() {

    }
    var d = 23;
    console.log(d)  // 23
    var e = 'e'
}
a(2, 5)

// 上面执行a时的AO 和 作用域
functionContext = {
    AO: {
        arguments: {
            length: 2
            [0]: 2
            [1]: 5
        },
        b: 2,
        c: 5,
        d: function,
        d: undefined // 变量申明不会覆盖形参和函数申明
    },
    scoped: {
        ...[[scoped]]
        ...AO,
    },
    this
}

this的绑定

简单理解就是谁调用this指向谁,如果没有调用方,this指向undefined,非严格模式下,undefined就会指向全局, 箭头函数的this在创建时绑定,绑定的是创建时上下文中的this,如果使用了call,apply调用直接指向参数,如果是bind绑定的,this也是在函数创建时绑定。

初始化上下文时,如果this已经绑定到值,不会执行this的初始化,bind、call、apply也是同理,如果发现函数在初始化阶段已经绑定了this,重新绑定不会生效。

需要注意的是js中(foo.bar)和(false || foo.bar) 不一致,前者会直接返回foo.bar的引用,后者会返回一个新的值时foo.bar的拷贝

js
function bar() {
    console.log(this.a)
}
const foo = {
    bar,
    a: 1
}
const a = 2;
foo.bar() // 1
(foo.bar)() // 1 括号不会影响旧值
(false || foo.bar)() // 2   // 表达式会返回一个新值
var c = foo.bar
c() // 2