JavaScript中对this的进一步理解
之前在学习this的时候,总是通过死记硬背来区分各种情况下this的指向,最近在看了 《You Dont Know JS》 后,对其有了进一步的理解
Call-site 和 Call stack
即调用点和调用栈。二者并不是一个东西,有一些时机上的差别。
调用栈 CallStack
指的是一个记录到当前执行函数为止,记录其顺序的堆栈。
调用点 Call-site
指的是当前执行函数之前的调用。
1 | function baz() { |
调用点是影响this绑定的唯一因素
默认绑定
当我们在全局作用域下直接执行函数的时候,this 会进行默认绑定。在非严格模式下,绑定的对象是全局对象
,在严格模式下绑定的对象是 undefined
。
但是需要注意一个细节,即:如果函数的调用点环境是严格模式,即使函数内部没有声明严格模式,全局对象也是唯一合法的。
1 | function foo() { |
有时你可能会引用与你的 Strict 模式不同的第三方包,所以对这些微妙的兼容性细节要多加小心
隐含绑定
当函数被声明作为引用属性添加到obj上时,并不代表这个对象真正拥有或包含这个函数,但调用点通过obj环境来引用函数,所以可以说obj对象在函数被调用的事件点上拥有或包含这个函数引用。
当一个方法引用存在一个环境对象时,隐含绑定 规则会说:是这个对象应当被用于这个函数调用的 this 绑定。因为 obj 是 foo() 调用的 this,所以 this.a 就是 obj.a 的同义词,同时,只有对象属性引用链的最后一层是影响调用点的
1 | function foo() { |
隐含丢失
1 | function foo() { |
尽管 bar 似乎是 obj.foo 的引用,但实际上它只是另一个 foo 本身的引用而已。起到绑定作用的是 默认绑定。
上面的情况在我们将函数当作参数传递时,也会发生。因为传递函数的时候,是一个隐含的引用赋值。
明确绑定
明确绑定指的是通过 call、apply
调用函数。二者除了参数格式不同,没什么太大区别,都允许我们传入this要绑定的对象。
如果传递一个简单基本类型值(string,boolean,或 number 类型)作为 this 绑定,那么这个基本类型值会被包装在它的对象类型中(分别是 new String(..),new Boolean(..),或 new Number(..))。这通常称为“封箱(boxing)”。
硬绑定
单独使用 明确绑定 无法解决三方框架覆盖this、丢失自己原本的this绑定等问题。但是有一个 明确绑定 的变种确实可以实现这个技巧。
1 | function foo() { |
用 硬绑定 将一个函数包装起来的最典型的方法,是为所有传入的参数和传出的返回值创建一个通道,通过 apply 绑定this,并将 arguments 参数列表返回。
1 | function foo(something) { |
也可以实现bind函数
1 | // 简单的 `bind` 帮助函数 |
在 ES6 中,bind(..) 生成的硬绑定函数有一个名为 .name 的属性,它源自于原始的 目标函数(target function)
new 绑定 this
需要明确一点,JavaScript通过new
运算符操作的函数并不是严格意义上的构造器,它仅仅是一个函数。可以说是我们通过构造器的方法去调用了这个函数,改变了函数的行为。例如Number()函数,在直接调用和new时会有不同的行为
当在函数前面被加入 new 调用时,也就是构造器调用时,下面这些事情会自动完成:
- 一个全新的对象会凭空创建(就是被构建)
- 这个新构建的对象会被接入原形链([[Prototype]]-linked)
- 这个新构建的对象被设置为函数调用的 this 绑定
- 除非函数返回一个它自己的其他 对象,否则这个被 new 调用的函数将 自动 返回这个新构建的对象。
上述几种规则的优先顺序
当调用点同时满足上述几种规则时,会有如下优先级
new > 硬绑定(明确绑定) > 隐含绑定 > 默认绑定
当然我们无法同时使用 new 和 call/apply ,但是我们能够通过硬绑定去测试二者的优先级
1 | function foo(something){ |
可以看到,new覆盖了硬绑定的this
一些特列
null 和 undefined
当传递 null 或 undefined 作为 call、apply 或 bind 的 this 绑定参数,那么这些值会被忽略掉,取而代之的是 默认绑定 规则将适用于这个调用
间接
当创建函数引用时,默认绑定规则也会适用
1 | function foo() { |
赋值表达式 p.foo = o.foo 的 结果值 是一个刚好指向底层函数对象的引用
箭头函数
箭头函数从封闭它的(函数或全局)作用域采用 this 绑定。一个箭头函数的词法绑定是不能被覆盖的(就连 new 也不行!)
JavaScript中对this的进一步理解