本文共 2025 字,看完只需 8 分鐘javascript
前面的文章講解了 JavaScript 中的執行上下文,做用域,變量對象,this 的相關原理,可是我後來在網上看到一些例題的時候,依然沒能全作對,說明本身有些細節還沒能掌握,本文就結合例題進行深刻實踐,討論函數在不一樣的調用方式 this 的指向問題。java
老規矩,先給結論 1 和 結論2:git
this 始終指向最後調用它的對象es6
「箭頭函數」的this,老是指向定義時所在的對象,而不是運行時所在的對象。github
特別提示:
本文的例子,最好本身在瀏覽器控制檯中去試一遍,看完過兩天就會忘的,必定要實踐。瀏覽器
// 例 1
var name = "window";
function foo() {
var name = "inner";
console.log(this.name);
}
foo(); // ?
複製代碼
輸出:閉包
windowapp
例 1 中,非嚴格模式,因爲 foo 函數是在全局環境中被調用,this 會被默認指向全局對象 window;函數
因此符合了咱們的結論一:post
this 始終指向最後調用它的對象
// 例 2
var name = "window";
var person = {
name: "inner",
show1: function () {
console.log(this.name);
},
show2: () => {
console.log(this.name);
}
}
person.show1(); // ?
person.show2(); // ?
複製代碼
輸出:
inner
window
person.show1() 輸出 inner 沒毛病,person.show2() 箭頭函數爲何會輸出 window 呢。MDN 中對 this 的定義是:
箭頭函數不綁定 this, 箭頭函數不會建立本身的this,它只會從本身的做用域鏈的上一層繼承this。
再看本文前面給的結論:
「箭頭函數」的this,老是指向定義時所在的對象,而不是運行時所在的對象。
因爲 JS 中只有全局做用域和函數做用域,箭頭函數在定義時的上一層做用域是全局環境,全局環境中的 this 指向全局對象自己,即 window。
// 例 3
var name = 'window'
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }
person1.show1() // ?
person1.show1.call(person2) // ?
person1.show2() // ?
person1.show2.call(person2) // ?
person1.show3()() // ?
person1.show3().call(person2) // ?
person1.show3.call(person2)() // ?
person1.show4()() // ?
person1.show4().call(person2) // ?
person1.show4.call(person2)() // ?
複製代碼
輸出:
person1
person2window
windowwindow
person2
windowperson1
person1
person2
上面 10 行打印,你對了幾個呢?
首先:
person1.show1()
和 person1.show1.call(person2)
輸出結果應該沒問題,call
的做用就是改變了調用的對象 爲 person2
。
其次:
person1.show2()
,person1.show2.call(person2)
,因爲調用的是箭頭函數,和本文例 2 中是同樣的,箭頭函數定義時 this 指向的是上一層,也就是全局對象, 而且 箭頭函數不綁定本身的 this, 因此經過 call()
或 apply()
方法調用箭頭函數時,只能傳遞參數,不能傳遞新的對象進行綁定。故打印的值都是 window。
進而:
function foo () {
return function () {
console.log(this.name)
}
}
foo()();
複製代碼
博客前面的文章有講過閉包,上面這段代碼也是典型的閉包運用,能夠看做:
function foo () {
return function () {
console.log(this.name)
}
}
var bar = foo();
bar();
複製代碼
因此,很明顯,被返回的內部函數實際上是在全局環境下被調用的。回到前面看咱們的結論 1,this 始終指向最後調用函數的對象
,這句話的關鍵詞應該是什麼?我以爲應該是 調用
,何時調用,誰調用。
再回過頭來看:
person1.show3()()
輸出 window,由於內部函數在全局環境中被調用。
person1.show3().call(person2)
輸出 person2, 由於內部函數被 person2 對象調用了。
person1.show3.call(person2)()
輸出 window,也是由於內部函數在全局環境中被調用。
最後:
重點理解結論 2:
「箭頭函數」的this,老是指向定義時所在的對象,而不是運行時所在的對象。
show4: function () {
return () => console.log(this.name)
}
複製代碼
這段代碼中,箭頭函數是在 外層函數 show4 執行後才被定義的。爲何?能夠翻看我前面關於做用域鏈,執行上下文,變量對象的文章,函數在進入執行階段時,會先查找內部的變量和函數聲明,將他們做爲變量對象的屬性,關聯做用域鏈,並綁定 this 指向。
因此:
person1.show4()()
輸出 person1,由於外部函數在執行時的 this 爲 person1, 此時定義了內部函數,而內部函數爲外部函數的 this。
person1.show4().call(person2)
輸出 person1,箭頭函數不會綁定 this, 因此 call 傳入 this 指向無效。
person1.show4.call(person2)()
輸出 person2,由於外部函數在執行時的 this 爲 person2,此時定義了內部函數,而內部函數爲外部函數的 this。
// 例 4
var name = 'window'
function Person (name) {
this.name = name;
this.show1 = function () {
console.log(this.name)
}
this.show2 = () => console.log(this.name)
this.show3 = function () {
return function () {
console.log(this.name)
}
}
this.show4 = function () {
return () => console.log(this.name)
}
}
var personA = new Person('personA')
var personB = new Person('personB')
personA.show1() //
personA.show1.call(personB) //
personA.show2() //
personA.show2.call(personB) //
personA.show3()() //
personA.show3().call(personB) //
personA.show3.call(personB)() //
personA.show4()() //
personA.show4().call(personB) //
personA.show4.call(personB)() //
複製代碼
輸出:
personA
personBpersonA
personAwindow
personB
windowpersonA
personA
personB
例 4 和 例 3 大體同樣,惟一的區別在於兩點:
// 例 5
function foo(){
setTimeout(() =>{
console.log("id:", this.id)
setTimeout(() =>{
console.log("id:", this.id)
}, 100);
}, 100);
}
foo.call({id: 111}); //
複製代碼
輸出:
111
111
注意一點:
setTimeout
函數是在全局環境被 window 對象執行的,可是 foo 函數在執行時,setTimtout
委託的匿名箭頭函數被定義,箭頭函數的 this 來自於上層函數 foo 的調用對象, 因此打印結果才爲 111;
// 例 6
function foo1(){
setTimeout(() =>{
console.log("id:", this.id)
setTimeout(function (){
console.log("id:", this.id)
}, 100);
}, 100);
}
function foo2(){
setTimeout(function() {
console.log("id:", this.id)
setTimeout(() => {
console.log("id:", this.id)
}, 100);
}, 100);
}
foo1.call({ id: 111 }); // ?
foo2.call({ id: 222 }); // ?
複製代碼
輸出:
111
undefinedundefined
undefined
例 5 中已經提到,setTimeout
函數被 window 對象調用,若是 是普通函數,內部的 this 天然指向了全局對象下的 id, 因此爲 undefined
,若是是箭頭函數,this 指向的就是外部函數的 this。
// 例 7
function foo() {
return () => {
return () => {
return () => {
console.log("id:", this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); //
var t2 = f().call({id: 3})(); //
var t3 = f()().call({id: 4}); //
複製代碼
輸出:
1
1
1
這段代碼是爲了鞏固咱們的結論2:
「箭頭函數」的this,老是指向定義時所在的對象,而不是運行時所在的對象。
有本書中有提到,當理解 JavaScript 中的 this 以後,JavaScript 纔算入門,我深覺得然。
緣由是,要完全理解 this, 應該是創建在已經大體理解了 JS 中的執行上下文,做用域、做用域鏈,閉包,變量對象,函數執行過程的基礎上。
有興趣深刻了解上下文,做用域,閉包相關內容的同窗能夠翻看我以前的文章。
1:this、apply、call、bind
2: 從這兩套題,從新認識JS的this、做用域、閉包、對象
3: 關於箭頭函數this的理解幾乎徹底是錯誤的
4: 深刻JS系列
歡迎關注個人我的公衆號「謝南波」,專一分享原創文章。