前言
如果要问javascript中哪两个知识点容易混淆,作用域查询和this
机制绝对名列前茅。所以这篇文章开始将介绍javascript中this
的四个绑定规则,下面来一起看看吧。
绑定规则
1. 默认绑定
独立函数调用时,this
指向全局对象,如果使用严格模式,那么全局对象无法使用默认绑定, this
绑定至 undefined
。
严格模式时:
2. 隐式绑定
当函数引用有上下文对象时(即函数作为引用属性被添加到对象中),隐式绑定规则会把函数调用中的 this
绑定到这个上下文对象。
2.1 隐式丢失
隐式丢失指的是函数中的 this 丢失绑定对象,即它会应用第 1 条的默认绑定规则,从而将 this
绑定到全局对象或者 undefined
上,取决于是否在严格模式下运行。以下情况会发生隐式丢失:
绑定至上下文对象的函数被赋值给一个新的函数,然后调用这个新的函数时:
传入回调函数时:
其实这就是第一种情况的变种,实际上参数传递就是一种隐式赋值。除了开发人员自定义的函数,在将函数传入语言内置的函数比如 setTimeout
时,同样会发生隐式丢失的情况。
3. 显式绑定
显式绑定的核心是 JavaScript 内置的 call(..)
和 apply(..)
方法,这两个方法在 JavaScript 提供的绝大多数函数以及开发者自己创建的所有函数上都可以使用。
call(..)
和 apply(..)
的第一个参数是一个对象(二者区别在后面传入的参数形式,这里不是重点,不讨论),他们会将 this 绑定到这个对象上。因为你可以直接指定 this
绑定的对象,所以这条规则被称为显式绑定。
如果 call 或者 apply 传入的第一个参数是原始值(字符串类型、布尔类型或者数字类型),那么该原始值会被转换成它的对象形式(new String()
、new Boolean()
或 new Number()
),俗称“装箱”。
显式绑定仍然无法解决丢失绑定问题。
3.1 硬绑定
作为显式绑定的一个变种,硬绑定可以解决丢失绑定问题。
在一个新的函数内部强制绑定 this
到某个对象上,无论之后如何调用这个新的函数,其 this
都不会丢失。
典型应用场景为创建一个包裹函数,传入所有的参数并返回接收到的所有值:
或者将绑定的对象改为可配置,这样就成了一个辅助绑定函数:
由于硬绑定实在太过常见,所以 ES5 提供了内置的 Function.prototype.bind
,其用法如下:
3.2 API 调用的“上下文”
JavaScript 自身以及许多第三方库的函数都提供了一个可选的参数,通常被称为“上下文”,其作用和 bind(..)
一样,确保回调函数使用指定的 this
。
实际上这些函数背后还是调用了 call()
或者 apply()
,只不过这样开发者需要写的代码就少了一些。
4. new 绑定
1、创建一个全新的对象
2、这个新对象会被执行 [[原型]] 连接
4、如果函数没有返回其他对象,那么 new
表达式中的函数调用会自动返回这个新对象
举例如下:
使用 new
来调用 foo(..)
时,会构造一个新对象并把它绑定到 foo(..)
调用中的 this
上。
优先级
具体推断细节不再表述,以上四种绑定规则的使用先后推断如下:
1、函数是否在 new
中调用(new
绑定)?如果是的话 this
绑定的是新创建的对象。
2、函数是否通过 call
、apply
(显示绑定)或者硬绑定?如果是的话,this
绑定的是指定的对象。
3、函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this
绑定的是那个上下文对象。
4、如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined
,否则绑定到全局对象。
绑定例外
如果把 null
或者 undefined
作为 this
的绑定对象传入 call
、apply
或者 bind
,那么这些值在调用时会被忽略,实际应用的是默认绑定规则。(书中推荐使用一个空对象来绑定 this)。
间接引用。这种情况容易在赋值时发生:
p.foo()
实际上引用了 foo()
,如此,会应用默认绑定。
另外ES6 对改变 this
的混乱绑定作了相应的努力,诞生了箭头函数,其根据当前词法作用域来决定 this
而非上面的四条规则,具体来说,箭头函数会继承外层函数调用的 this
绑定(这其实和 ES6 之前代码中的 self = this
是一个道理)。