本文共 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系列
歡迎關注個人我的公衆號「謝南波」,專一分享原創文章。