[译] 理解Javascript中的"This"

433 阅读4分钟

原文链接:

www.codementor.io/dariogarcia…

This

当一个函数被创建出来,this这个关键字也会一起创建出来,它和函数运行的对象关联在一起。

this关键字本身与函数并没有关系,而如何调用函数则决定了它的值。

This默认指向

// define a function
var myFunction = function () {
  console.log(this);
};

// call it
myFunction();

我们应该期待this的值是什么呢?在默认的情况下,this总是指向window对象,它会指向根部 —— 全局作用域。除非script运行在严格模式下(use strict),this将会是undefined。

Object literals

var myObject = {
  myMethod: function () {
    console.log(this);
  }
};

这里的this指向哪里呢?

  • this === myObject?
  • this === window?
  • this === anything else?

答案是我们不知道

请记住:

this关键字本身与函数并没有关系,而如何调用函数则决定了它的值。

Ok,让我们改变一下代码

var myMethod = function () {
  console.log(this);
};

var myObject = {
  myMethod: myMethod
};

现在这样清晰了吗?

当然,这个取决于我们如何调用这个函数。

myObject有一个属性叫做myMethod, 它指向myMethod函数。

myMethod函数在全局作用域下调用,this会指向window对象。

当它作为myObject的方法被调用,this会指向myObject

myObject.myMethod() // this === myObject
myMethod() // this === window

这就叫做隐式绑定

显式绑定

显式绑定是我们明确的将上下文绑定到函数中。它是通过call()或者apply()完成的。

var myMethod = function () {
  console.log(this);
};

var myObject = {
  myMethod: myMethod
};

myMethod() // this === window
myMethod.call(myObject, args1, args2, ...) // this === myObject
myMethod.apply(myObject, [array of args]) // this === myObject

那么谁的优先级高呢?隐式绑定还是显式绑定?

var myMethod = function () { 
  console.log(this.a);
};

var obj1 = {
  a: 2,
  myMethod: myMethod
};

var obj2 = {
  a: 3,
  myMethod: myMethod
};

obj1.myMethod(); // 2
obj2.myMethod(); // 3

obj1.myMethod.call( obj2 ); // ?????
obj2.myMethod.call( obj1 ); // ?????

显式绑定的优先级比隐式绑定的高,这意味着如果有显式绑定存在则应该第一时间看它绑定的对象。

obj1.myMethod.call( obj2 ); // 3
obj2.myMethod.call( obj1 ); // 2

Hard binding

这是由bind()(ES5)完成的,bind()返回一个新的函数,它是经过原函数绑定了你特定的this指向之后的函数。

myMethod = myMethod.bind(myObject);

myMethod(); // this === myObject

Hard binding的优先级比显式绑定的高。

var myMethod = function () { 
  console.log(this.a);
};

var obj1 = {
  a: 2
};

var obj2 = {
  a: 3
};

myMethod = myMethod.bind(obj1); // 2
myMethod.call( obj2 ); // 2

New binding

function foo(a) {
  this.a = a;
}

var bar = new foo( 2 );
console.log( bar.a ); // 2

当函数被new调用时,this会指向它所创建的新的实例。

当函数是被new调用时,这和隐式、显示或者hard 绑定都没有关系。它只是创建了一个新的上下文 —— 一个新的实例对象。

function foo(something) {
  this.a = something;
}

var obj1 = {};

var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2

var baz = new bar( 3 );
console.log( obj1.a ); // 2
console.log( baz.a ); // 3

API calls

有时候,我们会使用一个库或者辅助对象来做一些事情(例如:Ajax、事件处理等等),然后它会返回一个回调。这时候我们需要十分的谨慎,看看下面的例子:

myObject = {
  myMethod: function () {
    helperObject.doSomethingCool('superCool',
      this.onSomethingCoolDone);
    },

    onSomethingCoolDone: function () {
      /// Only god knows what is "this" here
    }
};

看看这个代码,你可能会觉得:因为我们把this.onSomethingCollDone作为回调,那么作用域是myObject对这个方法的引用而不是对这个方法的引用。

为了解决这个问题,有下面一些方法:

  • 通常这些库会提供另一个参数,这样你可以通过这个参数传递你想要返回的作用域。

    myObject = {
      myMethod: function () {
        helperObject.doSomethingCool('superCool', this.onSomethingCoolDone, this);
      },
    
      onSomethingCoolDone: function () {
        /// Now everybody know that "this" === myObject
      }
    };
    
    
  • 你可以通过hard bind绑定你需要的作用域(ES5)

    myObject = {
      myMethod: function () {
        helperObject.doSomethingCool('superCool', this.onSomethingCoolDone.bind(this));
      },
    
      onSomethingCoolDone: function () {
        /// Now everybody know that "this" === myObject
      }
    };
    
    
  • 你可以通过闭包将this缓存到me中,例如:

    myObject = {
      myMethod: function () {
        var me = this;
    
        helperObject.doSomethingCool('superCool', function () {
          /// Only god knows what is "this" here, but we have access to "me"
        });
      }
    };
    
    

    我并不推荐这种方法,因为这会导致内存泄漏并且这可能会导致你忘记了真正的作用域还有对变量的依赖。你会发现你的作用域会变的非常的混乱。

    这个问题也同样会在事件监听器、超时、forEach等等中出现。


译者注: 第一次翻译文章,有些标题仍是英文是想不到合适的词汇进行代替...可能有些地方翻译不妥也希望可以多多指出、进行交流。

这篇文章是在找call、apply、bind这几个函数中无意看到的,感觉说的很清晰而且原文难度也不大,所以想要尝试进行翻译。

文章有些地方,像API calls这一小节也还需要消化消化。如果你有任何问题或意见,请给我留言!