聲明:本人看了冴羽一篇文章後,感受那邊文章寫的很好,可是不夠詳細,因此在這裏豐富了一下,而後也但願分享給更多的人,一塊兒學習javascript
javascript 的this這個關鍵字我相信有不少人會像我同樣對他既陌生有熟悉。相信每個學習JavaScript的前端工程師都作過這麼一個事那就是到某個搜索引擎 key in ( javscript this),而後看過幾個博客後感受本身懂了this!html
來來讓咱們看看大部分博客會怎麼寫:前端
反正基本上都第一步,先告訴你一個事實『this 是在函數被調用時發生的綁定,指向上下文對象,即被調用函數所處的環境,也就是說,this 在函數內部指向了調用函數的對象。』;第二步,而後要不經過綁定方式的角度給你從new 綁定,顯示綁定等多個角度告訴你this是誰,或者從函數調用狀況,構造函數狀況,bind調用等多個狀況給你分析一波。而後不知道你會不會想我同樣深深的記住調用對象是誰,this就是誰,而後把不太明白的特殊狀況緊緊的記下,作個筆記什麼的。而後以爲我終於弄明白this了,而後在真正使用的時候偶爾還會發生,怎麼又忘記這種狀況了!!!納尼?what?臉好疼有沒有?
而後在去看網上的博客,看看有沒有這種狀況的解釋,看了冴羽大大的這篇《JavaScript深刻之從ECMAScript規範解讀this》文章後我以爲我能夠從一個新的角度再去學習一下,讓咱們能夠更深一步的去理解this,甚至找出一種方法,找this的時候經過「公式」去找到this的指代。java
PS.在正式開始以前,本人先聲明如下的文章會囉嗦一點,若是你還不是很懂this,但願你有時間去耐心的讀下去,但願也能夠給你一種新的認識。git
在深刻了解 JavaScript 中的 this 關鍵字以前,有必要先退一步,看一下爲何 this 關鍵字很重要。this 容許複用函數時使用不一樣的上下文。換句話說,「this」 關鍵字容許在調用函數或方法時決定哪一個對象應該是焦點。github
【ECMAScript規範 10.4.3節 進入函數代碼】是這麼解釋的: 當控制流根據一個函數對象 F、調用者提供的 thisArg 以及調用者提供的 argumentList,進入 函數代碼 的執行環境時,執行如下步驟:面試
由於咱們是研究this,因此咱們着重關注前4條就能夠了,簡單總結一下就是:當調用函數的時候會建立函數執行上下文,在建立的過程當中有一步就是建立this ,並根據是否在嚴格模式下,調用者是否是NUll或者undefined,調用者是否是對象決定this的指向。bash
那咱們來看看【ECMAScript規範 11.2.3節 函數調用】:前端工程師
這裏的thisValue的值其實就是咱們要找的thisArg了!!app
那咱們看一看MemberExpression的語法:
原來MemberExpression 的結果就是:執行函數名部分表達式的結果(簡單理解 MemberExpression 其實就是()左邊的部分表達式的結果)。
Reference type:按字面翻譯就是引用類型,可是它並非咱們常說的JavaScript中的引用類型,它是一個規範類型(實際並不存在),也就是說是爲了解釋規範某些行爲而存在的,好比delete、typeof、賦值語句等。規範類型設計用於解析命名綁定的(A Reference is a resolved name binding.),它由三部分組成:
基值就是屬性所在的對象或者就是 EnvironmentRecord,基值是 undefined, 一個 Object, 一個 Boolean, 一個 String, 一個 Number, 一個 environment record 中的任意一個。基值是 undefined 表示此引用能夠不解決一個名字的綁定(A base value of undefined indicates that the reference could not be resolved to a binding.)。
PS.最後一句話個人理解是這個引用不須要一個名字和他關聯起來,好比咱們建立的匿名函數,他的base應該就是undefined。
舉個栗子🌰:
var foo = 1;
// 對應的Reference是:
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
var foo = {
bar: function () {
return this;
}
};
foo.bar(); // foo
// bar對應的Reference是:
var BarReference = {
base: foo,
propertyName: 'bar',
strict: false
};
複製代碼
把2.1 和2.2的知識進行總結能夠寫出以下僞代碼:
FUNCTION.this = {
ref = MemberExpression 的結果; //'()'左邊的表達式的結果
IF Type(ref) == Reference THEN //若是左邊的表達式的結果是引用
IF IsPropertyReference(ref) THEN //斷定引用的基值是否是一個對象
thisArg = ref.base //若是是,thisArg是這個引用的基值
ELSE
thisArg = ref.base.ImplicitThisValue() //若是引用的基值不是對象,說明是個環境記錄,thisArg是
//它的ImplicitThisValue,絕大部分狀況下是Undefined
ELSE
thisArg = undefined; //若是左邊的表達式的結果不是引用,thisArg是undefined
IF thisArg == undefined
IF 'using strict' THEN //thisArg若是是undefined
return undefined //在嚴格模式下,函數的this就是undefined
ELSE
return 全局對象 //在嚴格模式下,函數的this就是undefined
ELSE
return thisArg //thisArg不是undefined,函數的this就是這個基值對象
}
複製代碼
舉幾個栗子🌰:
var value = 1;
function foo() {
console.log(this.value)
}
var fooObj = {
value: 2,
bar: function () {
return this.value;
},
fooIn:{
value:3,
foo:foo
}
}
//示例1
console.log(foo());
//示例2
console.log(fooObj.bar());
//示例3
console.log((fooObj.bar)());
//示例4
console.log((fooObj.bar = fooObj.bar)());
//示例5
console.log((false || fooObj.bar)());
//示例6
console.log((fooObj.bar, fooObj.bar)());
//示例7
console.log(fooObj.fooIn.foo());
//輸出結果是:1 2 2 1 1 1 3
複製代碼
foo() 的MemberExpression 是 foo,他的base是聲明式環境記錄,聲明式環境記錄的隱性的this值(ImplicitThisValue)是undefined ,在非嚴格模式下因此this指向window。
fooObj.bar()的MemberExpression 是fooObj.bar是個引用,他的base 值是 fooObj,因此this指向 fooObj。
(fooObj.bar)()的MemberExpression 是(fooObj.bar) 一個分組表達式,查看11.1.6分組表達式的規範:
The production PrimaryExpression : ( Expression ) is evaluated as follows: Return the result of evaluating Expression. This may be of type Reference
咱們要這個表達式的結果,結果是:fooObj.bar,因此和2是同樣的。
(fooObj.bar = fooObj.bar)()的MemberExpression 是(fooObj.bar = fooObj.bar) 咱們分組表達式的返回值是 fooObj.bar = fooObj.bar,咱們就要計算這個表達式的結果,查看11.13.1簡單賦值的規範:
令 lref 爲解釋執行 LeftH 和 SideExpression 的結果 .
令 rref 爲解釋執行 AssignmentExpression 的結果 .
令 rval 爲 GetValue(rref).
拋出一個 SyntaxError 異常,當如下條件都成立 : Type(lref) 爲 Reference IsStrictReference(lref) 爲 true Type(GetBase(lref)) 爲環境記錄項 GetReferencedName(lref) 爲 "eval" 或 "arguments"
調用 PutValue(lref, rval).
返回 rval.
由藍色部分能夠知道(fooObj.bar = fooObj.bar) 不是一個Reference,因此thisArg是一個undefined,非嚴格模式是window.
(false || fooObj.bar)()的MemberExpression 是(false || fooObj.bar) ,類比4查看 11.11 二元邏輯運算符 ,咱們知道(false || fooObj.bar) 是個值不是Reference因此this也是指向window
(fooObj.bar, fooObj.bar)()的MemberExpression 是(false || fooObj.bar) ,類比4查看11.14 逗號運算符,,咱們知道(false || fooObj.bar) 是個值不是Reference因此this也是指向window。
fooObj.fooIn.foo()的MemberExpression 是fooObj.fooIn.foo,他是個Reference,基值是fooObj.fooIn,因此this指向fooObj.fooIn
看了本篇文章後應該能夠解決絕大多數的this的指向問題,還有傳說中的 構造函數調用模式 , 箭頭函數調用模式 , call、apply、bind 調用模式,你們應該也是均可以經過規範解釋的清楚,在這裏再也不一一列舉。
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());//The Window
複製代碼
這個我沒有在規範裏面找到,因此只能當個問題給你們寫出來,若是寫的不對,還但願有大神在評論中指出來。
object.getNameFunc()()的MemberExpression 是object.getNameFunc(),這個函數的運行結果是return的匿名函數的指針,是個Reference可是它的base不是對象,因此是一個環境記錄項,沒有被bind,this,call改變因此ImplicitThisValue()爲undefined 因此非嚴格模式下就是window