this绑定机制及特性

223 阅读4分钟

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 的绑定对象。

  1. 由new调用?绑定到新创建的对象。
  2. 由call或者apply(或者bind)调用?绑定到指定的对象。
  3. 由上下文对象方法调用?绑定到那个上下文对象。
  4. 默认(独立函数调用):在严格模式下绑定到undefined,否则绑定到全局对象。

当然ES6中的箭头函数不适用上述规则,因为箭头函数会继承外层非箭头函数的 this绑定。