JavaScript中關於this關鍵字的魔幻現實 一篇文章搞懂他

不得不承認JS中存在一些“歷史的遺產”,比如基於全局變量的編程模型。
如果寫一個函數,如:

    function test(name){
        var namex = name
        console.log(this.namex)
    }
    test('jack')

方法調用之後,this.namex=undefined,爲何呢?因爲此處的this是全局對象window,這就令人比較頭大。
不過彆着急,慢慢往下看,關於this還有很多魔幻的現實,我們來一一解鎖。

1、完整的函數調用

還是上面的代碼,其實他的完整調用寫法是這樣的:

    function test(name){
        var namex = name
        console.log(this.namex)
    }
    test.call(undefined, 'jack')

test是一個函數對象--即Function對象,Function.prototypecall方法。call()會有兩個參數,第一個參數是this上下文對象,第二個參數是函數入參列表。
如果call()傳入的this上下文是undefinednull,那麼window對象將成爲默認的this上下文。這也就解釋了開頭例子中this爲啥爲window的原因了

2、對象中的this

const obj = {
    name: 'Jack',
    greet: function() {
        console.log(this.name)
    }
}
obj.greet()  //簡寫調用
obj.greet.call(obj) //完整調用

obj.greet()中的this無疑就是obj對象

3、對象方法中嵌套函數的this

對2中的代碼進行修改:

const obj = {
    name: 'Jack',
    greet: function() {
        retufn function(){console.log(this.name)}
    }
}
obj.greet()()	  //輸出undefined

需要注意的是嵌套函數中的this依然是window,爲什麼呢?可以拆分來看:

var greet = obj.greet()
greet()		// = greet.call(undefined)

嵌套函數被調用的時候,真實的調用者上下文是undefined,也就是window

4、原型與this

    function Clt() {
    }

    Clt.prototype.x = 10
    Clt.prototype.test = function () {
        console.log(this)
        this.y = this.x + 1
    }
    let bean = new Clt()
    bean.test();
    console.log(bean.y)

test方法中輸出的this是一個Clt對象。
這裏需要引入一個新的概念:構造器函數。
new關鍵字就是構造器函數,它的作用是:

一旦函數被new來調用,就會創建一個鏈接到該函數的prototype屬性的新對象,同時this會被綁定到那個新對象上

理解了構造器函數,我們就理解了原型與this的關係了,因爲new會重新指定this上下文

5、箭頭函數與this

ECMAScript6出現了箭頭函數的用法,關於箭頭函數中的this,需要先記住一句話:

函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。

看代碼(引自阮一峯老師的教程:https://es6.ruanyifeng.com/#docs/function#%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0):

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// id: 42

此處的this指向了foo的this上下文,即定義時的this對象。

箭頭函數與非箭頭函數的this區別:

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭頭函數
  setInterval(() => this.s1++, 1000);
  // 普通函數
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0

箭頭函數的this指向了定義時所在對象的this;非箭頭函數的this指向了運行時的作用域,即全局域window

this的指向固定化,也算是解決了一些歷史舊賬,無疑帶來的很大好處,比如封裝調用:

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};

一個嵌套的例子:

function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1

最裏層的this也綁定到了定義時所在對象的—即foo的this

總結

ECMAScript6的箭頭函數也算是解決了一些歷史問題。不過正如HTML5離不開HTML4一樣,因爲只有保持了繼承與兼容,新技術的推廣纔會更加的迅速。
享受語言新特性之餘,也要搞清楚舊版本的一些細節,這有助於更全面的掌握知識。
希望這篇文章對您有用!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章