对于很多初学者来说,this就是谜之存在,这一方面源于对this的误解,另一方面是因为this和很多语法都有着千丝万缕的关系,这些语法的细微变化都有可能影响this值的绑定。因此要清晰准确的理解JS中this的指向,确实需要付出一番努力。
基本规则
this的基本规则就是:this对象是在运行时基于函数的执行环境绑定的。
这是《Javascript高级程序设计》中对this对象的描述,这里有一个非常重要的概念:执行环境。js函数在执行的时候会创建执行环境,也就是说,this的值是在函数执行的时候绑定的,而不是定义的时候,这是理解this的前提。
this值的绑定
明白了this值是在函数执行的时候绑定的,接下来只需要知道执行环境就可以确定this值了。然而,执行环境的确定并没有想象的那么简单。
总的来说,this值的绑定可以分为4类:默认绑定、隐式绑定、明确绑定和构造函数绑定。
默认绑定
在全局环境中,直接调用函数,this始终指向全局对象,无论是不是在严格模式下。
隐式绑定
在函数环境中,this的值取决于函数被调用的方式。
如果函数被直接调用,this的值有可能是全局对象或者undefined,这取决于是否在严格模式下。在严格模式下,this将保持进入执行环境时的值,如果this没有被执行环境定义,它将保持默认值undefined。
如果函数作为对象的方法被调用时,this指向调用函数的对象。这里有一些细节需要说明,考虑以下代码:
var foo = {
name: 'foo',
getName: function() {
console.log(this.name)
}
}
var bar = {
name: 'bar',
foo: foo
}
foo.getName(); // 'foo'
bar.foo.getName(); // 'foo'
getName函数既是foo对象的属性也是bar对象的属性,但是this的绑定只受最靠近的成员引用的影响。
当函数和赋值表达式同时出现的时候,this的值就会发生微妙的变化。
function foo() {
console.log(this.name)
}
var bar = {
name: 'bar',
foo: foo
}
bar.foo(); // 'bar'
var fun = bar.foo;
fun(); // undefined
第一次调用foo的时候,foo函数是被bar对象引用的,也就是说foo函数被调用的时候,它是作为bar对象的属性被调用的,所以,this指向的就是bar对象。 第二次调用foo的时候,foo函数是通过赋值表达式赋值给了fun变量,fun变量本质上就是对foo函数的引用,调用fun()的时候其实就等同于直接调用foo(),自然this指向的是全局对象,全局对象没有name属性,便返回了undefined。
还有另外一种情况:回调函数
function foo() {
console.log(this.name)
}
var bar = {
name: 'bar',
foo: foo
}
function fun(callBack) {
callBack();
}
fun(bar.foo); // undefined
将bar.foo作为参数传递给了fun函数,也就是将bar.foo对foo函数的引用赋值给了callBack,调用callBack就等同于直接调用foo函数,this指向全局对象,打印的结果便是undefined。
这两种情况下,虽然foo函数都是bar对象的属性,但是foo被调用的时候,bar对象并没有在它的执行环境中,这就进一步的证明了this值是基于函数的执行环境绑定的,而与其在哪里定义的没有任何关系。
明确绑定
这里的明确绑定指的是调用一个函数的时候强制将this值绑定到某个对象上。实现的方式是通过在函数上调用call或applay方法。
考虑下面的例子:
function foo() {
console.log(this.name)
}
var bar = {
name: 'bar',
foo: foo
}
var obj = {
name: 'obj'
}
bar.foo.call(obj); // 'obj'
call方法有两个参数,this要绑定的对象和可选的要传入调用函数的参数。上面的代码通过call方法将this值强行绑定到了obj对象上。
如果想让foo函数在任何时候被调用时this值都绑定到obj对象的话,可以考虑封装一个可复用的方法。
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
}
}
var fun = bind(foo, obj);
fun(); // 'obj'
bind方法给apply调用包了一层函数并将其返回,每次调用包装函数都是间接的调用apply将foo函数的this强制绑定到obj。
apply和call的区别是apply的第二个参数是包含多个参数的数组,call则接收的是一个参数列表。
ES5实现了bind方法,调用foo.bind(obj)的时候,会返回一个和foo具有相同函数体和作用域的函数,这个新函数的this值绑定的永远都是obj对象。
构造函数绑定
使用new关键字调用函数,这个函数就是构造函数。构造函数调用的时候,经历了以下四个步骤:
- 创建一个新对象
- 将构造函数的作用域赋值给新对象(因此this就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
因此,构造函数调用将this值绑定到了构造函数生成的实例对象上。
实践
讨论完4种绑定方式之后,在实践中应该遵循下面的步骤判断this的值:
- 函数是通过new调用的,this就是实例对象。
- 函数是被apply、call或者bind调用,this就是被绑定的对象。
- 函数是作为对象的属性被调用,this就是那个对象。
- 函数被直接调用,this就是全局对象。或者在严格模式下,函数在局部作用域内调用,this就是undefined。
以上就是this绑定的主要内容。