再也不彷徨:徹底弄懂JavaScript中的this(譯文總結)

其實this是一個老生常談的問題了。關於this的文章很是多,其實我本覺得本身早弄明白了它,不過昨天在作項目的過程當中,仍是出現了一絲疑惑,想到大概以前在JavaScript weekly裏收藏待看的一篇詳解this的文章(後有連接,也附上了稀土上的中文譯文)和另外一篇一位前輩推薦的文章,就把它們看了看,對this的認識確實提高了一些。javascript

JavaScript 中的’this‘是動態的,它在函數運行時被肯定而非在函數聲明時被肯定。全部的函數均可以調用'this',這無關於該函數是否屬於某個對象。關於this,主要有如下四種狀況。java

1.被當作對象的方法被調用

若是該函數是被當作某一個對象的方法,那麼該函數的this指向該對象;數組

var john = {
      firstName: "John"
    }
    function func() {
      alert(this.firstName + ": hi!")
    }
    john.sayHi = func
    john.sayHi()  // this = john

這裏有一點值得注意,當一個對象的方法被取出來賦值給一個變量時,該方法變爲函數觸發,this指向window或underfind(嚴格模式)。瀏覽器

2.函數以內調用

當函數中有 this,其實就意味着它被當作方法調用,之間調用至關於把他當作window對象的方法,this指向window,值得注意的是ES5實際上是規定這種狀況this=undefined的,只瀏覽器大多仍是按照老的方法執行(本人在最新版的Chrome,Safari,Firefox中測試都指向window(201607)),在火狐下使用嚴格模式指向undefined;app

func()
    function func() {
        alert(this) // [object Window] or [object global] or kind of..
    }

爲了傳遞this,()以前應該爲引用類型,相似於obj.a 或者 obj['a'],不能是別的了。函數

這裏還存在一個小坑,當對象的方法中還存在函數時,該函數實際上是當作函數模式觸發,因此其this默認爲window(嚴格模式下爲undefined)解決辦法是給該函數綁定this。測試

var numbers = {  
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       // this is window or undefined in strict mode
       console.log(this === numbers); // => false
       return this.numberA + this.numberB;
     }
     return calculate();
   }
};
numbers.sum(); // => NaN or throws TypeError in strict mode
var numbers = {  
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       console.log(this === numbers); // => true
       return this.numberA + this.numberB;
     }
     // use .call() method to modify the context
     return calculate.call(this);
   }
};
numbers.sum(); // => 15

3.在new中調用

一個引用對象的變量實際上保存了對該對象的引用,也就是說變量實際保存的是對真實數據的一個指針。
使用new關鍵字時this的改變其實有如下幾步:this

  • 建立 this = {}.prototype

  • new執行的過程當中可能改變this,而後添加屬性和方法;指針

  • 返回被改變的this.

function Animal(name) {
        this.name = name
        this.canWalk = true
    }
    var animal = new Animal("beastie")
    alert(animal.name)

須要注意的是若是構造函數返回一個對象,那麼this指向返回的那個對象;

function Animal() {
        this.name = 'Mousie';
        this.age = '18';
        return {
            name: 'Godzilla'
        } // <-- will be returned
    }

    var animal = new Animal()
    console.log(animal.name) // Godzilla
    console.log(animal.age)//undefined

這裏須要注意的是不要忘記使用new,不然不會建立一個新的函數。而是隻是執行了函數,至關於函數調用,this其實指向window

function Vehicle(type, wheelsCount) {  
  this.type = type;
  this.wheelsCount = wheelsCount;
  return this;
}
// Function invocation
var car = Vehicle('Car', 4);  
car.type;       // => 'Car'  
car.wheelsCount // => 4  
car === window  // => true

4.明確調用this,使用callapply

這是最具JavaScript特點的地方。
以下代碼:

func.call(obj, arg1, arg2,...)

第一個參數將做爲this的指代對象,以後的參數將被做爲函數的參數,解決方法是使用bind。

function Animal(type, legs) {  
  this.type = type;
  this.legs = legs;  
  this.logInfo = function() {
    console.log(this === myCat); // => true
    console.log('The ' + this.type + ' has ' + this.legs + ' legs');
  };
}
var myCat = new Animal('Cat', 4);  
// logs "The Cat has 4 legs"
setTimeout(myCat.logInfo.bind(myCat), 1000); 
// setTimeout??
var john = {
    firstName: "John",
    surname: "Smith"
  }
  function func(a, b) {
    alert( this[a] + ' ' + this[b] )
  }
  func.call(john, 'firstName', 'surname')  // "John Smith"

至於apply,其只是以數組的方傳入參數,其它部分是同樣的,以下:

func.call(john, 'firstName', 'surname')
  func.apply(john, ['firstName', 'surname'])

它們也可用於在 ES5 中的類繼承中,調用父級構造器。

function Runner(name) {  
      console.log(this instanceof Rabbit); // => true
      this.name = name;  
    }
    function Rabbit(name, countLegs) {  
      console.log(this instanceof Rabbit); // => true
      // 間接調用,調用了父級構造器
      Runner.call(this, name);
      this.countLegs = countLegs;
    }
    var myRabbit = new Rabbit('White Rabbit', 4);  
    myRabbit; // { name: 'White Rabbit', countLegs: 4 }

5..bind()

對比方法 .apply() 和 .call(),它倆都當即執行了函數,而 .bind() 函數返回了一個新方法,綁定了預先指定好的 this ,並能夠延後調用。
.bind() 方法的做用是建立一個新的函數,執行時的上下文環境爲 .bind() 傳遞的第一個參數,它容許建立預先設置好 this 的函數。

var numbers = {  
  array: [3, 5, 10],
  getNumbers: function() {
    return this.array;    
  }
};
// Create a bound function
var boundGetNumbers = numbers.getNumbers.bind(numbers);  
boundGetNumbers(); // => [3, 5, 10]  
// Extract method from object
var simpleGetNumbers = numbers.getNumbers;  
simpleGetNumbers(); // => undefined or throws an error in strict mode

使用.bind()時應該注意,.bind() 建立了一個永恆的上下文鏈並不可修改。一個綁定函數即便使用 .call() 或者 .apply()傳入其餘不一樣的上下文環境,也不會更改它以前鏈接的上下文環境,從新綁定也不會起任何做用。
只有在構造器調用時,綁定函數能夠改變上下文,然而這並非特別推薦的作法。

6.箭頭函數

箭頭函數並不建立它自身執行的上下文,使得 this 取決於它在定義時的外部函數。
箭頭函數一次綁定上下文後便不可更改,即便使用了上下文更改的方法:

var numbers = [1, 2];  
    (function() {  
      var get = () => {
        console.log(this === numbers); // => true
        return this;
      };
      console.log(this === numbers); // => true
      get(); // => [1, 2]
      // 箭頭函數使用 .apply() 和 .call()
      get.call([0]);  // => [1, 2]
      get.apply([0]); // => [1, 2]
      // Bind
      get.bind([0])(); // => [1, 2]
    }).call(numbers);

這是由於箭頭函數擁有靜態的上下文環境,不會由於不一樣的調用而改變。所以不要使用箭頭函數定義方法

function Period (hours, minutes) {  
      this.hours = hours;
      this.minutes = minutes;
    }
    Period.prototype.format = () => {  
      console.log(this === window); // => true
      return this.hours + ' hours and ' + this.minutes + ' minutes';
    };
    var walkPeriod = new Period(2, 30);  
    walkPeriod.format(); // => 'undefined hours and undefined minutes'

參考

強烈推薦以爲沒弄明白的同窗看看上面三篇文章,其中第三篇是第二篇的譯文。若是你們對this還有疑問,也歡迎你們一塊兒討論,交流促進思考,共同進步。

相關文章
相關標籤/搜索