這篇文章的產生,是基於冴羽大大的JavaScript 深刻之從 ECMAScript 規範解讀 this的思考,這是對應掘金連接,文中詳細的論述了來龍去脈,建議各位均可以去了解一下,頗有幫助,而且這篇文章在寫做時,也有冴羽大大的幫助,再次表示感謝~javascript
文中的 ES5
規範是參考 顏海鏡大大 的譯本,也在這裏表示感謝。java
那爲何還有這篇文章呢?由於不少的同窗在冴羽大大的博客下評論沒有看懂,我也是其中的一員,因而我決定要弄明白爲何,如今也把個人一些整理分享出來,但願對你們也有幫助。git
再囉嗦一句,對於知道了各類狀況下 this
如何判斷的同窗來講,這篇文章並不會告訴你如何進行 this
指向的判斷,更多的是知道爲何這樣判斷,不知足於知其然,更知其因此然。github
Reference Type
(引用類型)開始:Reference Type
:引用類型。在 ES5 文檔標準中,將Reference
描述爲 a resolved name binding
後端
顏大的 ES5 譯本 中,譯爲已解決的命名綁定。瀏覽器
resolved
翻譯爲 已完成
函數
name binding
翻譯爲 命名綁定
沒有任何問題,若是有後端語言經驗的同窗可能更好理解。post
那咱們再解釋下命名綁定:綁定是有雙方的,把 命名
,也就是 咱們取的名字
,要綁定在 某個東西
上面,換言之,就是用 名字
來描述了一個什麼 東西
。ui
舉個例子: 如今咱們有一個對象:time
,而後他有三個屬性:this
time {
second: 32,
minute: 12,
hour: 10
}
複製代碼
咱們定義完成後,它必須存在於某一個地方,才能在後面的代碼中獲取到它。
存在哪由 time
自己的特性來決定,由於它是一個對象,內部的屬性是能夠添加也能夠減小的,換言之,它的大小並不固定。因此咱們把它存在了 堆
裏面。
那若是它的大小固定呢?例如 JavaScript
中的 6 種基本類型的值 :null
,undefined
,Boolean
,Number
,String
, Symbol
,既然大小固定,咱們就能夠放在 棧
裏面。
棧
:程序運行時系統分配的一小塊內存,大小在編譯期時由編譯器參數決定。堆
:能夠理解爲當前可使用的空閒內存,其大小是須要代碼編寫的人員本身去申請和釋放。(在 JS 中,V8 下有自動垃圾回收機制不須要咱們本身操做) [這裏只作簡單解釋,有須要能夠自行 Google 更多信息]Okay。若是你已經理解了咱們的 time
是存在堆中的,那就很好理解了。如今我要用到 time
裏面 second
屬性的值, 咱們都知道用 time.second
就能夠拿到,可是爲何 time.second
或者 time[’second’]
就能夠訪問到 second
屬性的值呢?
看起來這個問題很蠢是否是,哈哈,可是仔細想一想,按理來講:這個值是在內存裏面的一小塊上面,那咱們須要找到這一塊內存,才能取到這個值啊。
如今就很好理解了。那其實 time.second
或者 time[’second’]
他們是和內存裏面的真正存放 second
的值那個內存位置 是綁定在一塊兒的。只要你用到了 time.second
或者 time[’second’]
,那編譯器就找到,哦,這就是存在xxxxx
地址裏面的值也就是 32
。
Reference Type
和 this
有什麼關係?this
在 Javascript
中一直是一個初學者難以理解的點,有一些甚至寫了 2 年的項目也沒搞明白爲何 this
有這樣那樣的不一樣。
這裏咱們先不從使用的場景上來看 this
的指向,仍是迴歸到本源。
站在編譯器的角度,是怎麼樣去理解 this
指向呢?由於this
的指向的判斷,經常發生於函數的調用中,那咱們就來看看ES5 文檔標準中的 11.2.3 Function Calls
(函數調用)。
一共分爲 8 個步驟:
1. Let ref be the result of evaluating MemberExpression.
2. Let func be GetValue(ref).
3. Let argList be the result of evaluating Arguments, producing an internal list of argument values (see 11.2.4).
4. If Type(func) is not Object, throw a TypeError exception.
5. If IsCallable(func) is false, throw a TypeError exception.
6. If Type(ref) is Reference, then
a.If IsPropertyReference(ref) is true, then
i.Let thisValue be GetBase(ref).
b. Else, the base of ref is an Environment Record
i.Let thisValue be the result of calling the Implicit This Value concrete method of GetBase(ref).
7. Else, Type(ref) is not Reference.
a.Let thisValue be undefined.
8. Return the result of calling the [[Call]] internal method on func, providing thisValue as the this value and providing the list argList as the argument values.
複製代碼
我就不翻譯了,由於就算翻譯出來你可能也讀得很累,那麼咱們用圖來看下這個流程會更加直觀。
我已經把最關鍵的幾個步驟都標紅了,若是在第三步返回的 func
沒法經過 5
的判斷的話,根本就沒有討論 this
指向的必要。
因此咱們重點看:這個裏面最關鍵的點,第 2
, 6
, 7
步驟:
第 2
步:計算 MemberExpression
的值而且賦值給 Ref
: 也就是計算 ()
左邊的內容的結果,並賦值給 Ref
。換句話說: Ref
就是對於 ()
左邊的內容進行計算以後的引用。
第 6
步:判斷 ref 是否爲 Reference 類型: 這個沒什麼好說的。
第 7
步:判斷 ref 是不是屬性引用類型: 官方解釋: 經過執行IsPropertyReference(V)
來判斷的,若是基值是個對象或 HasPrimitiveBase(V)
是 true,那麼返回 true;不然返回 false。 HasPrimitiveBase(V)
:若是基值是 Boolean, String, Number,那麼返回 true。 換成大白話,取決於Ref
這個引用是基於誰的? 若是它基於一個對象
或者 Boolean
, String
, Number
那就返回 true
不然返回 false
。
OK 看到這裏,估計你也有些累,可是最關鍵的部分在下面。
this
的 N 種狀況let a = 'm';
function test() {
console.log(this.a);
}
test(); // m
複製代碼
咱們用剛剛所看到的 3
個步驟來判斷下 this
:
test()
的 Ref
就是 test
引用,它關聯到在內存中存儲了test()
的某一片斷。test()
是否爲引用類型 => true
Ref
是不是屬性引用類型 => false
,它並無定義在某個引用類型的內部。this = ImplicitThisValue(Ref)
,在 Environment Records
下返回 undefined
,而在非嚴格模式下,瀏覽器會把 this
指向 window
提及來很麻煩,其實理解起來很簡單。
function test() {
console.log(this.a);
}
let parent = {
a: 's',
test: test
};
parent.test(); // s
複製代碼
parent.test()
的 Ref
就是 parent.test
引用,它關聯到在內存中存儲了test()
的某一片斷。parent.test()
是否爲引用類型 => true
Ref
是不是屬性引用類型 => true
this = GetBase(Ref)
那這個test()
方法是基於誰呢?很明顯就是 parent
,因此 this
指向 parent
let a = 'k';
function Foo() {
console.log(this);
}
let c = new Foo();
c.a = 's'
複製代碼
new
關鍵字調用,區別於通常的函數調用,你們能夠看下MDN 上的解釋,明確的指出了
若是你仍舊想從規範的角度來解釋,建議你讀一下ES5 規範:11.2.2 The new Operator 以及關聯的 ES5 規範:8.7.1 GetValue (V) 我反覆了讀了不少遍,可是沒有發現如何從規範的角度去解釋 this
的指向問題,最後也是請教了冴羽大大才知道 new
可能在底層有明確指定 this
的過程,不適合用這樣的方式解讀,可是,若是你有了更好的答案,很歡迎一塊兒討論~
既然明白了this
指向的是 c
那麼輸出的是 {a : 's'}
function Foo() {
return () => {
return () => {
console.log(this);
};
};
}
console.log(Foo()()());
複製代碼
和 new
同樣箭頭函數也是一個特例,可是箭頭函數一樣能夠從對應的規範中找到 this
的答案:
建議參考ES6 規範-箭頭函數-evaluation裏面的一段話:
「An ArrowFunction does not define local bindings for arguments, super, this, or new.target. Any reference to arguments, super, this, or new.target within an ArrowFunction must resolve to a binding in a lexically enclosing environment. 」
直譯爲:"ArrowFunction
不爲 arguments
, super
, this
或 new.target
定義本地綁定。 對 ArrowFunction
中的 arguments
, super
, this
或 new.target
的任何引用都必須解析爲詞法做用域中的綁定。"
也就是說,箭頭函數內部不會定義 this
,都是由它外部的詞法做用域來決定的,也就是說,箭頭函數的外部的 this
指向的是誰,那箭頭函數內部的 this
指向的也是誰。
回到這個例子,咱們知道至始至終,不管你套多少層箭頭函數,this
都是指向 Foo
裏面的 this
,那Foo
裏面的 this
根據咱們以前的例子能夠知道,就是指向了 window
。
歡迎你們關注個人掘金專欄,後期也會更新更多優質的內容~ 有任何問題,歡迎理性和友好的討論~
題圖來自 unsplash