this在面向对象编程中非常重要,也是javascript中最复杂的机制之一,它会被自动定义在所有函数(箭头函数除外)的作用域中,它实际上是在函数被调用时发生绑定,它的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式,本文将基于this的四种调用方式来了解它的绑定机制。
- 函数调用
- 对象方法调用
- new调用
- call、apply、bind调用
一、函数调用
this的绑定规则完全取决于调用位置,如果独立函数在全局进行调用,那么this会被绑定到全局对象。
function run(){
console.log(this.a)
}
var a = 2;
run(); // 2
我们知道声明在全局作用域中的变量a就是全局对象(window)的一个同名属性,当我们调用run()时,this指向了全局对象(window),所以得到了结果:2。
注意:只有在非严格模式下,this才会绑定到全局对象,严格模式下会被绑定到undefined。
function run(){
console.log(this === window);
}
run(); // true
二、对象方法调用
当一个函数保存为对象的属性时,我们称它为一个方法,当一个方法被调用时,this会被绑定到该对象。
var myObject = {
name:'张三',
run:function(){
console.log('我叫'+this.name);
}
}
myObject.run(); // 我叫张三
从上面的例子可以看到通过this可以取得所属对象的上下文。
三、new调用
在javascript中,有一个new关键字,通过new关键字调用的函数,我们称之为构造函数,当使用new调用函数的时候会自动执行下面的操作:
- 创建一个全新的对象
- 这个新对象会执行原型连接。
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new关键字中的函数调用会自动返回这个新对象
function Foo(a) {
this.a = a;
}
var bar = new Foo(2);
console.log( bar.a ); // 2
使用new来调用Foo(..) 时,我们会构造一个新对象并把它绑定到Foo(..)调用中的 this上。
四、call、apply、bind调用
上面介绍的三种this绑定机制,都是javascript默认的隐式绑定,那么如果我们不想在对象内部包含函数或者使用new关键字调用,而想在某个对象上强制调用函数,该怎么做呢?我们都知道所有函数(箭头函数除外)都可以使用 call(..) 和 apply(..) 及bind(...)方法,它们的第一个参数是一个对象,它们会把这个对象绑定到 this,接着在调用函数时指定这个 this。因为可以直接指定 this 的绑定对象,因此称之为显式绑定。
注意:从 this 绑定的角度来说,call(..)和apply(..)、bind是一样的,call和apply第二个参数分别是参数列表和数组,bind和call、apply不同的是:在于bind方法返回值是函数。
function foo(){
console.log(this.a)
}
var myObject = {
a:15,
};
foo.call(myObject); // 15
上面的代码通过 foo.call(..),我们可以在调用 foo 时强制把它的 this 绑定到 myObject 上,通过这些方法,我们也很容易模拟出一个new绑定,上面一节我们提到了new绑定的执行过程,现在我们来实现一个。
function newCreate(fn){
// 创建一个全新的对象
var newObj = new Object();
// 新对象会执行原型连接到函数原型
newObj.__proto__ = fn.protype;
// 将 arguments 对象转为数组,并把fn函数从参数列表中移除
var args = [].slice.call(arguments,1);
// 新对象绑定到函数调用的this
var result = fn.apply(newObj,args);
// 如果函数没有返回其他对象,那么new关键字中的函数调用会自动返回这个新对象
if(Object.prototype.toString.call(result) == "[object Object]" ) {
return result
} else {
return newObj;
}
}
小结
如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断 this 的绑定对象。
- 由new调用?绑定到新创建的对象。
- 由call或者apply(或者bind)调用?绑定到指定的对象。
- 由上下文对象方法调用?绑定到那个上下文对象。
- 默认(独立函数调用):在严格模式下绑定到undefined,否则绑定到全局对象。
当然ES6中的箭头函数不适用上述规则,因为箭头函数会继承外层非箭头函数的 this绑定。