本文和你們聊聊this
,this
在JavaScript
中可說是神同樣的存在,靈活性太強了,早期對this
作過一次梳理,但如今面對this
的使用仍是怕怕的,對this
的理解不透徹,仍是在猜結果,根據應用場景劃分梳理,更多的是在硬記,面對複雜噁心人的應用場景,特別是在面試的時候出的題,仍是不明白,對結果一頭霧水。本文,努力從底層和規範梳理this
,真正的理解this
。java
前面的文章有寫到,程序執行調用前會建立對應的執行上下文,一個執行上下文能夠理解爲一個抽象的對象,其中this就是這個對象的一個屬性:面試
executionContext: {
variable object:vars, functions, arguments
scope chain: variable object + all parents scopes
thisValue: context object
}
複製代碼
this
值在進入上下文時就已經肯定了,而且在上下文運行期間永久不變。也就是說this
是在函數調用的時候肯定的。算法
咱們看看ECMAScript
規範(5.1)中怎麼描述this
:express
The this keyword evaluates to the value of the ThisBinding of the current execution context.數組
意思就是說this
執行爲當前執行環境(執行上下文
)的ThisBinding
。ThisBinding
就是this
的值。bash
this
也是一個對象,與執行的上下文環境息息相關,也能夠把this
稱爲上下文對象,激活執行上下文的上下文。數據結構
在描述this
以前,咱們先來看幾個比較重要的概念。ide
咱們都知道在JavaScript
中對象是引用類型,函數和數組都屬於引用類型,還有基本類型:Number
、String
、Boolean
等。其實在ECMASciript5.1
規範中將類型分爲了兩種:函數
Algorithms within this specification manipulate values each of which has an associated type. The possible value types are exactly those defined in this clause. Types are further subclassified into ECMAScript language types and specification types.學習
規範的算法操做各個有類型的值,可處理的類型在算法相關敘述中定義。類型又再分爲ECMAScript
語言類型和規範類型。
An ECMAScript language type corresponds to values that are directly manipulated by an ECMAScript programmer using the ECMAScript language. The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.
ECMAScript
語言類型就是ECMAScript
開發者使用ECMAScript
語言直接操做的值對應的類型。ECMAScript
語言類型包括未定義(Undefined
)、空(Null
)、布爾值(Boolean
)、字符串(String
)、數值(Number
)、對象(Object
)。也就是咱們常說未定義(Undefined
)、空(Null
)、布爾值(Boolean
)、字符串(String
)、數值(Number
)爲基本數據類型,對象(Object
)爲引用類型。
A specification type corresponds to meta-values that are used within algorithms to describe the semantics of ECMAScript language constructs and ECMAScript language types. The specification types are Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, and Environment Record. Specification type values are specification artefacts that do not necessarily correspond to any specific entity within an ECMAScript implementation. Specification type values may be used to describe intermediate results of ECMAScript expression evaluation but such values cannot be stored as properties of objects or values of ECMAScript language variables.
規範類型是描述ECMAScript
語言構造與語言類型語意的算法所用的元值對應的類型。規範類型包括引用(Reference
)、列表(List
)、完結(Completion
)、屬性描述式(Property Descriptor
)、屬性標識(Property Identifier
)、詞法環境(Lexical Environment
)、環境記錄(Environment Record
)。
咱們須要知道規範類型的值是不必定對應ECMAScript
實現裏的任何實體的虛擬對象。規範類型能夠用來描述ECMAScript
表示運算的中途結果,但這些值不能存成對象的屬性或是ECMAScript
語言變量的值。
對於規範類型不理解的話,咱們只須要知道,規範類型用來描述表達式求值過程的中間結果,是一種內部實現,是用來描述語言底層行爲邏輯,不對開發者直接開放。
尤雨溪大大在知乎中就Reference
的提問也作來解答:
ECMASciript
規範對類型作了很明確的定義,類型分爲ECMAScript
語言類型和規範類型,其中規範類型中有講到引用(Reference
),須要注意的是這裏的引用,不一樣與咱們常說的對象(Object
)爲引用類型中的引用,這是兩個概念,對象(Object
)爲引用類型中的引用更多的是強調在咱們定義對象的時候,經過聲明變量,將值賦值給變量,變量存的是這個對象的內存地址,並非對象的值自己,這也就是引用傳遞。而規範類型中說起到的引用(Reference
)並非這個意思,規範中是怎麼定義的,咱們下面來看看,理解規範類型中說起到的引用(Reference
)對咱們理解this
有很是大的幫助,是重點:
Reference
(引用),這裏說的引用是規範類型中的引用。咱們先來看看規範中怎麼定義引用規範類型的:
The Reference type is used to explain the behaviour of such operators as delete, typeof, the assignment operators, the super keyword and other language features. For example, the left-hand operand of an assignment is expected to produce a reference.
上面是ECMAScript6
規範中的定義,比ECMASciript5.1
定義更豐富。
Reference
類型是ECMAScript
規範用來解釋delete
, typeof
、賦值運算符、super
關鍵字等語言特性的行爲。例如在賦值運算中左邊的操做數指望產生一個引用。
Reference
類型值能夠理解爲是對某個變量、數組元素或者對象屬性所在的內存地址的引用,不是對其所在內存地址所保存值的引用。在 ECMAScript
中,賦值運算符的左側是一個引用(Reference
),不是值。
咱們再來看看Reference
的具體內容:
A Reference is a resolved name binding. A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag. The base value is either undefined, an Object, a Boolean, a String, a Number, or an environment record (10.2.1). A base value of undefined indicates that the reference could not be resolved to a binding. The referenced name is a String.
一個引用(Reference
)是一個已解決的命名綁定。一個引用(Reference
)由三部分組成,基(base
)值,引用名稱(referenced name
)和布爾值、嚴格引用(strict reference
)標誌。基值是 undefined
, 一個Object
, 一個Boolean
, 一個String
, 一個Number
, 一個environment record
中的任意一個。基值是undefined
表示此引用能夠不解決一個綁定。引用名稱是一個字符串。
咱們來看一個簡單的實例,理解下引用(Reference
):
// 實例一
var a = 1;
複製代碼
上面就是一個很簡單的聲明變量,變量a
對應的Reference
是什麼呢:
// var a = 1 對應的Reference
var aReference = {
base: Environment Record,
name: 'a',
strict: false
}
複製代碼
有人會以爲根據定義好像是這麼寫的,也有人會思考爲何是這麼寫:
這是在全局環境下聲明瞭一個變量,而且作了簡單的賦值操做,咱們來看看規範中怎麼定義簡單賦值的:
11.13.1 Simple Assignment ( = )
The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:
- Let lref be the result of evaluating LeftHandSideExpression.
- Let rref be the result of evaluating AssignmentExpression. ...
- Return rval.
咱們只要看第一步就能夠了,令lref
解釋執行LeftHandSideExpression
的結果。
LeftHandSideExpression
也就是聲明的變量a
,咱們知道聲明的變量a
能夠稱爲是一個標識符(Identifier
),爲了幫助你們更好的理解,咱們來看看LeftHandSideExpression
中有沒有標識符,先看下LeftHandSideExpression
是怎麼定義的:
LeftHandSideExpression :
NewExpression
CallExpression
規範中寫明瞭LeftHandSideExpression
包含了兩種,一是new
表達式,二是函數調用表達式,並無咱們想要看到的標識符。咱們再追根下,new
表達式和函數調用表達式又包含了哪些內容:
NewExpression :
MemberExpression
new NewExpression
CallExpression :
MemberExpression Arguments
CallExpression Arguments
CallExpression [ Expression ]
CallExpression . IdentifierName
上面的new
表達式和函數調用表達式一樣也沒看到咱們想要看到的標識符,咱們就再追根下,咱們看到new表達式中能夠是成員表達式(MemberExpression
),咱們再來看當作員表達式中有沒有咱們想要的內容:
MemberExpression :
PrimaryExpression
FunctionExpression
MemberExpression [ Expression ]
MemberExpression . IdentifierName
new MemberExpression Arguments
在上面的內容中仍是沒有看到咱們想要看到的標識符,怎麼辦,只有在追下了,咱們再看看PrimaryExpression
中有什麼:
PrimaryExpression :
this
Identifier
Literal
ArrayLiteral
ObjectLiteral
( Expression )
這下在PrimaryExpression
看到了咱們想要看到的標識符(Identifier
)了,因此確保變量a
是標識符無疑了,爲了肯定它也挺不容易的。
既然咱們如今知道了變量a
是一個標識符,咱們再來看看標識符是怎麼執行的:
11.1.2 Identifier Reference
An Identifier is evaluated by performing Identifier Resolution as specified in 10.3.1. The result of evaluating an Identifier is always a value of type Reference.
Identifier
的執行遵循10.3.1
所規定的標識符查找。標識符執行的結果老是一個Reference
類型的值。也就說明了a
這個標識符是一個Reference
類型的值。咱們再來看看10.3.1
寫了什麼:
10.3.1 Identifier Resolution
Identifier resolution is the process of determining the binding of an Identifier using the LexicalEnvironment of the running execution context. During execution of ECMAScript code, the syntactic production PrimaryExpression : Identifier is evaluated using the following algorithm:
- Let env be the running execution context’s LexicalEnvironment.
- If the syntactic production that is being evaluated is contained in a strict mode code, then let strict be true, else let strict be false.
- Return the result of calling GetIdentifierReference function passing env, Identifier, and strict as arguments.
The result of evaluating an identifier is always a value of type Reference with its referenced name component equal to the Identifier String.
標識符解析是指使用正在運行的執行環境中的詞法環境,遇到一個標識符得到其對應的綁定過程。在ECMAScript
代碼執行過程當中,PrimaryExpression : Identifier
這一語法產生式將按如下算法進行解釋執行:
env
爲正在運行的執行環境的詞法環境。stric
t的值爲true
,不然令strict
的值爲false
。env
,Identifier
和strict
爲參數,調用GetIdentifierReference
函數,並返回調用的結果。 解釋執行一個標識符獲得的結果一定是引用類型的對象,且其引用名屬性的值與Identifier
字符串相等。
上面提到了GetIdentifierReference
函數,咱們再來看看這是什麼:
10.2.2.1 GetIdentifierReference (lex, name, strict)
....
Return a value of type Reference whose base value is envRec, whose referenced name is name, and whose strict mode flag is strict.
....
規範中寫到返回一個類型爲 引用(Reference
) 的對象,其基(base
)值爲envRec
,引用的名稱(name
)爲 name
,嚴格模式標識的值爲strict
就本實例來講,base
值爲envRec
,envRec
也就是10.3.
1傳入的執行環境的詞法環境。而一個詞法環境是由一個環境記錄項(Environment Record
)和可能爲空的外部詞法環境引用構成。咱們暫時不用管外部詞法環境,咱們須要知道環境記錄項這個概念。
在規範中,有兩種環境記錄項,一是聲明式環境記錄項,另外一種是對象式環境記錄項。聲明式環境記錄項定義那些將標識符與語言值直接綁定的ECMA
腳本語法元素,例如函數定義,變量定義以及Catch
語句。因此本實例的環境記錄項是聲明式環境記錄項。
說了這麼多就是爲了告訴你們本實例基(base
)值是Environment Record
,name
是foo
,因此抽象數據結構爲:
var aReference = {
base: Environment Record,
name: 'a',
strict: false
}
複製代碼
規範中也使用瞭如下抽象操做來接近引用:
GetBase(V)
。 返回引用值V
的基值組件。GetReferencedName(V)
。 返回引用值V
的引用名稱組件。IsStrictReference(V)
。 返回引用值V
的嚴格引用組件。HasPrimitiveBase(V)
。 若是基值是Boolean
,String
,Number
,那麼返回true
。IsPropertyReference(V)
。 若是基值是個對象或HasPrimitiveBase(V)
是true
,那麼返回true
;不然返回false
。IsUnresolvableReference(V)
。 若是基值是undefined
那麼返回 true
,不然返回false
。規範使用如下抽象操做來操做引用:
GetValue(v)
。返回對象屬性真正的值,是reference
傳入,會返回一個普通類型出來。好比foo
爲reference
,經過GetValue
以後就是一個普通的object
,也就是foo
對應的js
類型自己PutValue(v,w)
。 鋪墊了這麼多,如今咱們來聊聊this
了,有上面的理論基礎理解this
相對會簡單不少:
this
綁定多數是出如今函數調用的時候,不一樣的場景下,this
的指向不同,咱們先來看看函數調用在規範中怎麼定義的:
11.2.3 Function Calls
The production CallExpression : MemberExpression Arguments is evaluated as follows:
- Let ref be the result of evaluating MemberExpression.
- Let func be GetValue(ref).
- Let argList be the result of evaluating Arguments, producing an internal list of argument values (see 11.2.4).
- If Type(func) is not Object, throw a TypeError exception.
- If IsCallable(func) is false, throw a TypeError exception.
- 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 ImplicitThisValue concrete method of GetBase(ref).
- Else, Type(ref) is not Reference.
a. Let thisValue be undefined.
- 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.
函數調用具體內容以下:
ref
爲解釋執行MemberExpression
的結果func
爲GetValue(ref)
argList
爲解釋執行Arguments
的結果 , 產生參數值們的內部列表(see 11.2.4
)Type(func)
is not Object
,拋出一個TypeError
異常IsCallable(func)
is false
,拋出一個TypeError
異常Type(ref)
爲Reference
,IsPropertyReference(ref)
爲true
thisValue
爲GetBase(ref)
ref
的基值是一個環境記錄項
thisValue
爲調用GetBase(ref)
的ImplicitThisValue
具體方法的結果Type(ref)
不是Reference
thisValue
爲undefined
func
的[[Call]]
內置方法的結果 , 傳入thisValue
做爲 this
值和列表argList
做爲參數列表 規範中講到了函數調用時如何肯定this
的值,咱們下面就按照上述步驟來解析不一樣場景下this
值是什麼。
咱們先看一個最簡單的場景:
// 實例二
function foo() {
console.log(this); // window
}
foo();
複製代碼
函數foo
執行輸出想必你們都知道輸出是window
,咱們從規範中來分析下爲何this
指向的是window
:
咱們如今只須要看第一步,令ref
爲解釋執行MemberExpression
的結果。MemberExpression
是什麼呢?咱們來看看規範是怎麼定義的:
11.2 Left-Hand-Side Expressions
MemberExpression :
PrimaryExpression // 主值表達式
FunctionExpression // 函數調用表達式
MemberExpression[Expression] // 屬性訪問表達式
MemberExpression.IdentifierName // 屬性訪問表達式
new MemberExpression Arguments // 對象建立表達式
MemberExpression
能夠理解爲()左邊部分,在這裏是foo
,foo
是主值表達式(PrimaryExpression
)中的一種,咱們先看下PrimaryExpression
是怎麼定義的:
11.1 Primary Expressions
PrimaryExpression :
this
Identifier
Literal
ArrayLiteral
ObjectLiteral
( Expression )
看的出來foo
就是一個標識符(Identifier
),前面的內容也已經講過了,解釋執行一個標識符獲得的結果一定是引用類型的對象,且其引用名屬性的值與Identifier
字符串相等。
因此foo
的抽象數據結構以下:
foo_reference = {
base: Environment Record,
name: "foo",
strict: false
}
複製代碼
到這裏第一步已經完成了,執行MemberExpression
的結果就是foo_reference
,ref = foo_reference
,ref
是一個Reference
。
由於ref
是Reference
,因此來到了第六步:若是Type(ref)
是Reference
,那麼若是IsPropertyReference(ref)
爲true
,那麼令thisValue
爲GetBase(ref)
. 不然, ref
的基值是一個環境記錄項,令 thisValue
爲調用GetBase(ref)
的ImplicitThisValue
具體方法的結果。
ref
的基(base
)值是一個環境記錄項(Environment Record
),不是一個對象,因此IsPropertyReference(ref)
是false
,this
的值是調用GetBase(ref)
的ImplicitThisValue
具體方法的結果。
咱們來看看ImplicitThisValue
方法是什麼:
10.2.1.1.6 ImplicitThisValue()
Declarative Environment Records always return undefined as their ImplicitThisValue.
聲明式環境記錄項永遠將undefined
做爲其ImplicitThisValue
返回.
函數foo
執行到這裏,thisValue = undefined
,對應到JavaScript
代碼中的this
,this=undefined
,尚未結束,規範中在進入函數代碼有寫到:
10.4.3 Entering Function Code
Else if thisArg is null or undefined, set the ThisBinding to the global object.
若是thisArg
是null
或undefined
,則設this
綁定爲 全局對象 。因此函數foo
執行時,this
指向全局對象,也就是window
。
// 實例二
function foo() {
console.log(this); // window
}
foo();
複製代碼
因此這個實例控制檯輸出的就是window
。這也就是從應用場景來區分的話,就是默認綁定。
這就是this
指向解析的全過程了,從規範中解讀this
的指向,從底層瞭解this
的指向,不須要去區分默認綁定、隱性綁定、顯性綁定等這種從應用場景來記this
的指向,記不住也容易出錯,遇到複雜的場景仍是容易犯錯,咱們須要真正的從底層來理解this
,對於this
理解透徹。
有了上面的分析方式,咱們再看看幾種比較常見的this
應用場景:
// 實例三
var obj = {
foo: function() {
console.log(this); // obj
}
}
obj.foo();
複製代碼
實例三中MemberExpression
計算結果是obj.foo
,obj.foo
是個什麼,會是否是一個Reference
呢?
按照前面的分析方式,咱們知道obj.foo
是MemberExpression.IdentifierName
,是一個屬性訪問。
咱們再來看看規範中怎麼定義屬性訪問(Property Accessors
)的:
11.2.1 Property Accessors
The production MemberExpression : MemberExpression[Expression] is evaluated as follows:
- Let baseReference be the result of evaluating MemberExpression
- Let baseValue be GetValue(baseReference).
- Let propertyNameReference be the result of evaluating Expression.
- Let propertyNameValue be GetValue(propertyNameReference).
- Call CheckObjectCoercible(baseValue).
- Let propertyNameString be ToString(propertyNameValue).
- If the syntactic production that is being evaluated is contained in strict mode code, let strict be true, else let strict be false.
- Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.
上面主要講的是:
baseReference
爲解釋執行MemberExpression
的結果baseValue
爲GetValue(baseReference)
propertyNameReference
爲解釋執行Expression
的結果propertyNameValue
爲GetValue(propertyNameReference)
CheckObjectCoercible(baseValue)
propertyNameString
爲ToString(propertyNameValue)
strict
爲 true
, 不然令strict
爲false
baseValue
且其引用名爲propertyNameString
, 嚴格模式標記爲strict
咱們回到咱們的實例三,obj.foo
中的MemberExpression
是obj
,執行MemberExpression
的結果返回一個Reference
,假設是reference_obj
,baseReference
爲reference_obj
。
baseValue
就是GetValue(reference_obj)
,GetValue
方法上面也有介紹,baseValue
也就是obj
。
同理propertyNameString
就是foo
再看最後一步(第八步),因此obj.foo
最終執行是一個Reference
,且數據結構以下:
reference_obj_foo = {
base: obj,
name: "foo",
strict: false
}
複製代碼
由於obj.foo一個Reference
,ref = reference_obj_foo
,來到了函數調用的第六步:
If Type(ref) is Reference, then
a. If IsPropertyReference(ref) is true, then
i. Let thisValue be GetBase(ref).
IsPropertyReference()
方法前面也有介紹,若是基值是個對象或HasPrimitiveBase(V)
是true
,那麼返回true
。本實例base
是obj
,是一個對象,因此IsPropertyReference(ref)
返回true
,也由於返回了true
,因此thisValue = GetBase(ref)
,GetBase(V)
方法前面也有介紹,返回引用值V
的基值組件,即thisValue = GetBase(ref) = obj
。
因此obj.bar()
執行時this
指向obj
,這也就是this
的隱性綁定。
如今已經分析了this
的基本指向,默認綁定和隱性綁定的指向問題,按照應用場景來分的話,還有顯性綁定、new
綁定以及箭頭函數等,因爲內容還有不少下篇文章再詳細輸出,本文就暫時寫到這。本文也基本的講述從規範的角度this
的指向,從規範中幫助咱們更好的理解this
。
文章若有不正確的地方歡迎各位大佬指正,也但願有幸看到文章的同窗也有收穫,一塊兒成長!
-------------------------------本文首發於我的公衆號------------------------------