深入學習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系列目錄
- #1 【深入學習js之——原型和原型鏈】
- #2 【深入學習js之——詞法作用域和動態作用域】
- #3 【深入學習js之——執行山下文棧】
- #4 【深入學習js之——變數物件】
- #5 【深入學習js之——作用域鏈】
- #6 【深入學習js之——實際開發場景中的this指向】
- #7 【深入學習js之——執行上下文】
- #8 【深入學習js之——閉包】
- #9 【深入學習js之——引數按值傳遞】
- #10 【深入學習js之——call和apply】
歡迎新增我的個人微信討論技術和個體成長。
歡迎關注我的個人微信公眾號——指尖的宇宙,更多優質思考乾貨