深入學習js之——this#6

MagicalLouis發表於2019-03-31

深入學習js系列是自己階段性成長的見證,希望通過文章的形式更加嚴謹、客觀地梳理js的相關知識,也希望能夠幫助更多的前端開發的朋友解決問題,期待我們的共同進步。

如果覺得本系列不錯,歡迎點贊、評論、轉發,您的支援就是我堅持的最大動力。


開篇

this關鍵字是JavaScript中最複雜的機制之一,它是一個很特別的關鍵字,被自動定義在所有函式的作用域 中。

跟別的語言大相徑庭的是:js的this總是指向一個物件,而具體指向哪個物件是在執行時基於函式的執行環境動態繫結的,而非函式被宣告時候的環境。

為什麼要使用this

如果學習this的代價很大,但是對於我們平時工作並不大,我們幹嘛要付出這麼大的代價學習呢?的確,在介紹怎麼做之前我們需要先明白為什麼。

除去不常用的with和eval的情況,具體到實際應用中,this的指向大致可以分為以下四種:

  • 1.作為物件的方法呼叫。
  • 2.作為普通函式呼叫。
  • 3.構造器呼叫。
  • 4.Function.prototype.call 或者 Function.prototype.apply 下面分為這四種情況分別進行呼叫:

1.作為物件的方法呼叫:

當函式作為物件的方法被呼叫的時候,this指向該物件:

var obj = {
  a: 1,
  getA: function () {
    console.log(this === obj); // true 
    console.log(this.a); // 1;
  }
}
obj.getA();
複製程式碼

2.作為普通函式被呼叫:

當函式不作為物件的屬性被呼叫時候,也就是我們所說的普通函式方式,此時的this總是指向全域性的物件。在瀏覽器的js裡面,這個全域性物件是window物件。

// 建立全域性的name物件 掛載在window上面
window.name = "globalName"; 
var getName = function () {
  return this.name;
}
console.log(getName()); // 輸出的是 globalName

複製程式碼

或者


window.name = "globalName";
var myObject = {
  name: "louis",
  getName: function () {
    return this.name;
  }
}
var getName = myObject.getName;
console.log(getName()); // "globalName"

複製程式碼

有時候我們會遇到一些困擾,比如在事件節點的div函式內部,有一個區域性的callback方法,callback被作為普通的函式被呼叫時,callback內部的this指向了window,但我們往往想讓它的指向div節點.

<div id = "div1">我是一個div</div>
複製程式碼
window.id = "window";
document.getElementById('div1').onclick = function () {
  alert(this.id); // 輸出:'div1'
  var callback = function () {
    alert(this.id); // 輸出:'window'
  }
  callback();
};
複製程式碼

此時有一種簡單的解決方案,可以用一個變數儲存div節點的引用:


document.getElementById('div1').onclick = function () {
  var that = this;    // 儲存div的引用
  var callback = function () {
    alert(that.id);    // 輸出:'div1'
  }
  callback();
}

複製程式碼

在ECMAScript 2015 中的嚴格模式下,這種情況下的this指向已經被規定為不會指向全域性物件,而是undefined:


function func() {
  "use strict"
  alert(this); // undefined
}
func();

複製程式碼

3.構造器的呼叫:

js中沒有類,但是可以從構造器中建立物件,同時也提供了new運算子,使得構造器看起來像是一個類,

除了宿主提供的一些內建函式,大部分js函式都可以當成構造器使用,構造器的外表看起來和普通的函式沒有什麼區別,他們的區別在於呼叫方式,當使用new運算子呼叫函式的時候,該函式總是返回一個物件,通常情況下,構造器裡面的this就是指向返回的這個物件。

var MyClass = function () {
  this.name = "louis";
}

var obj = new MyClass();
console.log(obj.name);
複製程式碼

但是new呼叫構造器時候,還要注意一個問題,如果構造器顯式的返回了一個object物件那麼此次運算結果最終會返回這個物件,而不是我們之前期待的this:

var MyClass = function () {
  this.name = 'sven';
  return {    // 顯式地返回一個物件
    name: 'anne'
  }
};

var obj = new MyClass();
alert(obj.name);     // 輸出:anne”
複製程式碼

如果構造器不顯式的返回任何資料,或是返回一個非物件型別的資料,就不會造成上述問題。


var MyClass = function () {
  this.name = 'sven'
  return 'anne';    // 返回string型別
};

var obj = new MyClass();
alert(obj.name);     // 輸出:sven”

複製程式碼

4.Function.prototype.call 或者 Function.prototype.apply呼叫

跟普通函式呼叫相比,用 Function.prototype.call 或者 Function.prototype.apply可以動態的改變傳入函式的this:

var obj1 = {
  name: "louis",
  getName: function () {
    return this.name;
  }
}

var obj2 = {
  name: "kerry";
}

console.log(obj1.getName());     // 輸出: louis
console.log(obj1.getName.call(obj2));    // 輸出: kerry

複製程式碼

call 和 apply 方法能夠很好的體現 js的函式式語言特性 在js中幾乎每一次編寫函式式語言風格的程式碼都離不開call和apply

5.丟失的this

下面看一個經常遇到的問題:

var obj = {
  myName: "louis",
  getName: function () {
    return this.name;
  }
}

console.log(obj.getName()); // louis;
var getName2 = obj.getName;
console.log(getName2()) // undefined
複製程式碼

當呼叫obj.getName時,getName方法是作為obj物件的屬性被呼叫的,根據上文提到的規律,此時的this指向obj物件,所以obj.getName()輸出'louis'。

當用另外一個變數getName2來引用obj.getName,並且呼叫getName2時, 此時是普通函式呼叫方式,this是指向全域性window的,window上面並沒有掛載任何屬性所以程式的執行結果是undefined。

再看另一個例子,document.getElementById這個方法名實在有點過長,我們大概嘗試過用一個短的函式來代替它,如同prototype.js等一些框架所做過的事情:

var getId = function (id) {
  return document.getElementById(id);
};

getId('div1');

複製程式碼

我們也許思考過為什麼不能用下面這種更簡單的方式

var getId = document.getElementById;
getId( 'div1' );
複製程式碼

現在不妨花1分鐘時間,讓這段程式碼在瀏覽器中執行一次


<div id="div1">我是一個div</div>

複製程式碼

var getId = document.getElementById;
getId( 'div1' );

複製程式碼

在chrome friefox IE10 中執行過後就會發現,這段程式碼丟擲一個異常,這是因為很多引擎的document.getElementById 方法的內部實現中需要用到this,這個this本來被期望指向document,當getElementById方法作為document物件的屬性被呼叫時,方法內部的this確實是指向document的。

但是當getId來引用document,getElementById之後,再呼叫getId,此時就成了普通的函式呼叫了,函式內部的this指向了window,而不是原來的document。

我們可以嘗試利用apply把document當做this傳遞給getId函式,修正 this指向問題。


document.getElementById = (function(func){
  return function(){
    return func.apply(document,arguments);
  }
})(document.getElementById);

var getId = document.getElementById;
var div = getId('div1');

alert(div.id);

複製程式碼

深入學習JavaScript系列目錄

歡迎新增我的個人微信討論技術和個體成長。

深入學習js之——this#6
歡迎關注我的個人微信公眾號——指尖的宇宙,更多優質思考乾貨

深入學習js之——this#6

相關文章