关于 this
当一个函数被调用时,会创建一个活动记录(执行上下文、执行环境、context)。这个活动记录会包含函数在哪里被调用、函数的调用方法、传入的参数等信息。this
就是活动记录中的一个属性,会在函数执行的过程中用到。
this
是在运行时绑定的,它的绑定取决于函数调用时的各种方式。this
的绑定和函数的声明位置、函数的调用位置没有任何关系,只取决于函数的调用方式。
关于 this
是什么有很多说法:函数本身、函数作用域、函数的执行上下文等,这里有的说法是错误的,有的说法不够准确。我觉得就不要纠结 this
到底是什么东西了,this
就是 this
,你只要知道它的绑定规则和用法,就 ok 了!
绑定规则
因为 this
的绑定取决于函数的调用方式,所以下面我们从函数的调用方式分析 this
的绑定规则。
默认绑定
首先要介绍的是最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则。
默认绑定是指当函数被调用时,是直接使用不带任何修饰的函数引用进行调用的,这种情况下,如果运行在非严格模式中,this
会绑定到全局对象上。如果运行在严格模式下,this
会绑定到 undefined
上。
1 | function foo () { |
隐式绑定
在调用函数的时候,如果以对象方法的形式进行调用,这个时候this
就会绑定到这个方法所属的对象上。
ps:如果对象的某个属性是函数,就称这个属性为对象的方法。
1 | function foo () { |
一定要区分开默认绑定和隐式绑定,下面的例子里包含了让人容易忽略的情况。再次强调 this
的绑定和函数的声明位置、函数的调用位置没有任何关系,只取决于函数的调用方式。
1 | function foo () { |
显示绑定
通过 apply()
和 call()
方法来调用函数时,this
的绑定为显示绑定,this
会绑定到传给 apply()
或 call()
方法的第一个参数上。
1 | function foo () { |
如果传给 apply()
或 call()
方法的第一个参数为基本数据类型,那么这个基本类型值会被转换为对应的对象形式(new String()、 new Boolean()等)。
硬绑定是显示绑定的一个变种,硬绑定是使用 bind()
方法强制指定 this
,该方法返回一个新的函数实例。
apply()
和call()
是要指定函数运行时的this
并运行函数,而bind()
是返回一个this
已经绑定完的函数实例。并且bind()
是遵守就近原则的。- 如果用来指定
this
值的参数是基本数据类型,那么这个基本类型值会被转换为对应的基本包装类型(new String()、 new Boolean()等)。
1 | function print () { |
new 绑定
使用 new
来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
- 创建一个全新的对象。
- 这个新对象会被执行[[原型]]连接。
- 这个新对象会绑定到函数调用的
this
。 - 如果函数没有返回其他对象,那么
new
表达式中的函数调用会自动返回这个新对象。
1 | function foo (a) { |
优先级
new > 显示 > 隐式 > 默认
bind 的 Polyfill
1 | if (!Function.prototype.bind) { |
意料之外的绑定
被忽略的 this
如果你把 null
或者 undefined
作为 this
的绑定对象传入 call
、apply
或者 bind
,这些值在调用时会被忽略,实际应用的是默认绑定规则。一般都是在函数并不关心 this
的情况下才会传入 null
或者 undefined
作为参数。
但是如果某个函数确实使用了 this
,那么这种做法就会产生一些副作用(如修改全局对象)。
所以比较推荐的做法是传入 Object.create(null)
替代 null
和 undefined
。
间接引用
另一个需要注意的是,你有可能(有意或者无意地)创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。
1 | function foo () { |
赋值表达式 p.foo = o.foo
的返回值是目标函数的引用,因此 (p.foo = o.foo)()
等价于 foo()
,所以会应用默认绑定。
软绑定
软绑定的原理类似于硬绑定,直接上代码。
1 | if (!Function.prototype.softBind) { |
可以看到,软绑定版本的 foo()
可以手动将 this
绑定到 obj2
或者 obj3
上,但如果应用默认绑定,则会将 this
绑定到 obj
。
箭头函数中的 this
ES6 的箭头函数并不使用 function
关键字定义,而是使用被称为“胖箭头”的操作符 =>
定义的。箭头函数不使用 this
的四种标准规则,而是根据词法作用域来决定 this
。
1 | function foo () { |
foo()
内部创建的箭头函数会捕获调用时 foo()
的 this
。由于 foo()
的 this
绑定到 obj1
,bar
(引用箭头函数)的 this
也会绑定到 obj1
,箭头函数的绑定无法被修改
箭头函数可以像 bind()
一样确保函数的 this
被绑定到指定对象,此外,其重要性还体现在它用更常见的词法作用域取代了传统的this机制。