Appearance
前言
重新理一下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