this - 想說愛你不容易

wmwy8455 7年前發布 | 6K 次閱讀 JavaScript開發 JavaScript

前言

javascript中的this是啥東西?為啥我們經常被他搞得暈頭轉向不知所以?他是惡魔?是天使 ?是怪胎?讓我們一起來揭開它那神秘的面紗。

他是個啥

首先 this 是Javascript語言的關鍵字之一,指函數 運行 時的當前對象。那既然和函數運行有關,js中函數有哪些調用模式呢?

  1. 純粹的函數調用

  2. 對象的方法調用

  3. 構造函數調用

  4. apply、call調用

我擦,有木有一千只草泥馬在心里蹦騰不息,人家是要弄懂this,你這又是整的哪一出

我們慢慢來,一步步從這些調用模式中探究this這個神奇的遠古神獸

純粹的函數調用

函數調用 即 functionName () 模式,這也是我們使用的最多的一種方式,其屬于全局調用,瀏覽中默認情況下函數內部的this指向 window ,當然是在非嚴格模式下。

this.name = 'qianlong';

function showName () { console.log(this.name); console.log(this === window); }

showName()

// qianlong // true</code></pre>

對象的方法調用

當一個函數作為對象的某個屬性方法被調用的時候

var obj = {
  name: 'qianlong',
  showName: function () {
    console.log(this.name);
  }
};

obj.showName(); // qianlong</code></pre>

可以看出 this 指向是obj這個對象,其實本質上講函數調用形式內部 this 就是指向調用它的那個對象

上面的例子相當于

window.showName()

這也是為什么可以讀取到全局定義的name屬性的原因。

再來

var showName = function () {
    console.log(this.name);
  },
  obj = {
    name: 'qianlong',
    showName: showName
  };

obj.showName();</code></pre>

這個時候輸出的是什么呢

結果是不變的,在js中,一切都是對象,而這里也只是將,obj的showName屬性指向,showNmae函數的引用地址。

繼續

當我們把 showName 方法賦值給了一個變量,又會有什么事情發生呢?

var obj = {
  name: 'qianlong',
  showName: function () {
    console.log(this.name);
  }
};

var tempShowName = obj.showName;

tempShowName()

// undefined</code></pre>

為什么不是期望的那樣輸出 qianlong呢。obj的showName方法是一個對象,當把它賦值給了tempShowName變量,此時便和obj沒有什么關系了,而這個時候的調用和下面是等價的。

window.tempShowName()

window上此事并沒有 name 屬性,自然輸出是 undefined 。

構造函數調用

當使用 new 去調用一個構造函數的時候,內部的this,指向的是實例化出來的對象。

var Person = function (name, sex) {
  this.name = name;
  this.sex = sex;
  console.log(this);
};

var p1 = new Person('qianlong', 'boy');

// Person {name: 'qianlong', sex: 'boy'};</code></pre>

構造函數也是函數,所以當你用普通調用方式調用時

var Person = function (name, sex) {
  this.name = name;
  this.sex = sex;
  console.log(this);
};

Person('qianlong', 'boy');

// 這個時候相當于給window對象添加了name和sex兩個屬性。

window.name // 'qianlong' window.sex // 'boy'</code></pre>

apply、call調用

使用call和apply方式去調用一個函數的時候,內部的this指向的是傳進來的第一個參數,當第一個參數是 undefined 或者 null 的時候,依舊指向 window

var showName = function () {
       console.log(this);
 };
 showName() // window
 showName.call(undefined) // window
 showName.call(null) // window
 showName.call({name: 'qianlong'}) // {name: 'qianlong'}

箭頭函數

在 ES6 的新規范中,加入了箭頭函數,它和普通函數最不一樣的一點就是 this 的指向,普通函數中的this,是運行時候決定的,而箭頭函數卻是定義時候就決定了。

var obj = {
  name: 'qianlong',
  showName: function () {
    console.log(this.name);
  },
  showNameLater: function () {
    setTimeout(() => {
      console.log(this.name);
    }, 1000)
  }
};

obj.showNameLater();

// qianlong</code></pre>

var obj = {
  name: 'qianlong',
  showName: () => {
    console.log(this.name);
  }
};

obj.showName(); // undefined</code></pre>

一些坑

1. setTimeout

var obj = {
  name: 'qianlong',
  showName: function () {
    console.log(this.name);
  },
  showNameLater: function () { 
    setTimeout(this.showName, 1000);
  }
};

obj.showNameLater();

// undefined</code></pre>

這里在執行setTimeout這個函數的時候傳了obj的showName函數作為第一個參數,其效果與

var showName = obj.showName

是相同的。而setTimeout內部其實也是執行了傳進去這個函數而已,即。

showName();

還記得這種調用方式和 window.showName() 是類似的效果嗎?這個時候輸入為undefined也就好理解了。

那么怎么解決這個問題呢,畢竟我們期望的效果是輸出 qianlong 。

var obj = {
  name: 'qianlong',
  showName: function () {
    console.log(this.name);
  },
  showNameLater: function () { 
    var self = this;
    setTimeout(function () {
      self.showName();
    }, 1000);
  }
};

obj.showNameLater();</code></pre>

或者

var obj = {
  name: 'qianlong',
  showName: function () {
    console.log(this.name);
  },
  showNameLater: function () { 
    setTimeout(this.showName.bind(this), 1000);
  }
};

obj.showNameLater();</code></pre>

2. setTimeout

尼瑪坑爹啊,居然還是因為你。

'use strict';

function show() { console.log(this); }

show(); // undefined

setTimeout(show, 1); // window</code></pre>

在嚴格模式下面,函數調用的時候沒有指定this的情況下,內部this的表現為 undefined ,但是setTimeout卻不同,其內部默認還是指向window。

3. 為構造函數指定this

var Person = function (name, sex) {
  this.name = name;
  this.sex = sex;
};

var p1 = new Person.call({});

// Uncaught TypeError: Person.call is not a constructor</code></pre>

這里報錯了,原因是我們去 new 了 Person.call 函數 ,這里的函數不是一個構造函數;

當然解決方式也是有的。

var Person = function (name, sex) {
  this.name = name;
  this.sex = sex;
};

var p1 = new (Person.bind({}))('qianlong', 'sex');

// Person {name: "qianlong", sex: "sex"}</code></pre>

4. 為箭頭函數指定this

var show = (str) => {
  console.log(str);
  console.log(this);
};

show('qianlong'); // qianlong // window

show.call({name: 'qianlong'}, 'qianlong'); // qianlong // window</code></pre>

可以看到使用call來手動改變箭頭函數中的this的時候,無法成功。 箭頭函數中的 this 在定義它的時候已經決定了(執行定義它的作用域中的 this),與如何調用以及在哪里調用它無關,包括 (call, apply, bind) 等操作都無法改變它的 this。

結語

文章可能有些疏漏與錯誤之處,歡迎各位指正。

 

來自:https://segmentfault.com/a/1190000007983078

 

 本文由用戶 wmwy8455 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!