this

原文出處

JavaScript深入之從ECMAScript規范解讀this
ECMA-262-3 in detail. Chapter 3. This
ECMAScript5.1中文版
ECMAScript5.1英文版

Types


首先是第 8 章 Types:

Types are further subclassified into ECMAScript language types and specification types.

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.

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.

我們簡單的翻譯一下:

ECMAScript 的類型分為語言類型和規范類型。

ECMAScript 語言類型是開發者直接使用 ECMAScript 可以操作的。其實就是我們常說的Undefined, Null, Boolean, String, Number, 和 Object。

而規范類型相當于 meta-values,是用來用算法描述 ECMAScript 語言結構和 ECMAScript 語言類型的。規范類型包括:Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record。

沒懂?沒關系,我們只要知道在 ECMAScript 規范中還有一種只存在于規范中的類型,它們的作用是用來描述語言底層行為邏輯

Reference


那什么又是 Reference ?

讓我們看 8.7 章 The Reference Specification Type

The Reference type is used to explain the behaviour of such operators as delete, typeof, and the assignment operators.

所以 Reference 類型就是用來解釋諸如 delete、typeof 以及賦值等操作行為的。

抄襲尤雨溪大大的話,就是:

這里的 Reference 是一個 Specification Type,也就是 “只存在于規范里的抽象類型”。它們是為了更好地描述語言的底層行為邏輯才存在的,但并不存在于實際的 js 代碼中。

再看接下來的這段具體介紹 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 的構成,由三個組成部分,分別是:

  1. base value: 就是屬性所在的對象或者就是 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String。

  2. referenced name: 就是屬性的名稱。

  3. strict reference: 是否處于嚴格模式。

舉個例子:

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
};

而且規范中還提供了獲取 Reference 組成部分的方法,比如 GetBase 和 IsPropertyReference。
這兩個方法很簡單,簡單看一看:

1.GetBase

GetBase(V). Returns the base value component of the reference V.

返回 reference 的 base value。

  1. IsPropertyReference

IsPropertyReference(V). Returns true if either the base value is an object or HasPrimitiveBase(V) is true; otherwise returns false.

簡單的理解:如果 base value 是一個對象,就返回true。

GetValue


除此之外,緊接著在8.7.1 章規范中就講了一個用于從 Reference 類型獲取對應值的方法: GetValue。

簡單模擬 GetValue 的使用:

var foo = 1;

var fooReference = {
    base: EnvironmentRecord,
    name: 'foo',
    strict: false
};

GetValue(fooReference) // 1;

GetValue 返回對象屬性真正的值,但是要注意:
調用 GetValue,返回的將是具體的值,而不再是一個 Reference

如何確定this的值


規范 11.2.3 Function Calls

這里講了當函數調用的時候,如何確定 this 的取值。
只看第一步、第六步、第七步:

1.Let ref be the result of evaluating MemberExpression.

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 ImplicitThisValue concrete method of GetBase(ref).

7.Else, Type(ref) is not Reference.

   a. Let thisValue be undefined.

讓我們描述一下:

  1. 計算 MemberExpression 的結果賦值給 ref

  2. 判斷 ref 是不是一個 Reference 類型

     2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值為 GetBase(ref)
     2.2 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值為 ImplicitThisValue(ref)
     2.3 如果 ref 不是 Reference,那么 this 的值為 undefined
    

具體分析


讓我們一步一步看:

  1. 計算 MemberExpression 的結果賦值給 ref

什么是 MemberExpression?看規范 11.2 Left-Hand-Side Expressions

MemberExpression :

  • PrimaryExpression // 原始表達式 可以參見《JavaScript權威指南第四章》
  • FunctionExpression // 函數定義表達式
  • MemberExpression [ Expression ] // 屬性訪問表達式
  • MemberExpression . IdentifierName // 屬性訪問表達式
  • new MemberExpression Arguments // 對象創建表達式

舉個例子:

function foo() {
    console.log(this)
}

foo(); // MemberExpression 是 foo

function foo() {
    return function() {
        console.log(this)
    }
}

foo()(); // MemberExpression 是 foo()

var foo = {
    bar: function () {
        return this;
    }
}

foo.bar(); // MemberExpression 是 foo.bar

所以簡單理解 MemberExpression 其實就是()左邊的部分。

2.判斷 ref 是不是一個 Reference 類型。

關鍵就在于看規范是如何處理各種 MemberExpression,返回的結果是不是一個Reference類型。

舉最后一個例子:

var value = 1;

var foo = {
  value: 2,
  bar: function () {
    return this.value;
  }
}

//示例1
console.log(foo.bar());
//示例2
console.log((foo.bar)());
//示例3
console.log((foo.bar = foo.bar)());
//示例4
console.log((false || foo.bar)());
//示例5
console.log((foo.bar, foo.bar)());

foo.bar()

在示例 1 中,MemberExpression 計算的結果是 foo.bar,那么 foo.bar 是不是一個 Reference 呢?

查看規范 11.2.1 Property Accessors,這里展示了一個計算的過程,什么都不管了,就看最后一步:

Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.

我們得知該表達式返回了一個 Reference 類型!
根據之前的內容,我們知道該值為:

var Reference = {
  base: foo,
  name: 'bar',
  strict: false
};

接下來按照 2.1 的判斷流程走:

2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值為 GetBase(ref)

該值是 Reference 類型,那么 IsPropertyReference(ref) 的結果是多少呢?

前面我們已經鋪墊了 IsPropertyReference 方法,如果 base value 是一個對象,結果返回 true。

base value 為 foo,是一個對象,所以 IsPropertyReference(ref) 結果為 true。

這個時候我們就可以確定 this 的值了:

this = GetBase(ref)

GetBase 也已經鋪墊了,獲得 base value 值,這個例子中就是foo,所以 this 的值就是 foo ,示例1的結果就是 2!

(foo.bar)()

看示例2:

console.log((foo.bar)());

foo.bar 被 () 包住,查看規范 11.1.6 The Grouping Operator
直接看結果部分:

Return the result of evaluating Expression. This may be of type Reference.

NOTE This algorithm does not apply GetValue to the result of evaluating Expression.

實際上 () 并沒有對 MemberExpression 進行計算,所以其實跟示例 1 的結果是一樣的。

(foo.bar = foo.bar)()

看示例3,有賦值操作符,查看規范11.13.1 Simple Assignment ( = ):

3.Let rval be GetValue(rref).

因為使用了 GetValue,所以返回的值不是 Reference 類型

按照之前講的判斷邏輯:

2.3 如果 ref 不是Reference,那么 this 的值為 undefined

this 為 undefined,非嚴格模式下,this 的值為 undefined 的時候,其值會被隱式轉換為全局對象。

(false || foo.bar)()

看示例4,邏輯與算法,查看規范 11.11 Binary Logical Operators
計算第二步:

2.Let lval be GetValue(lref).

因為使用了 GetValue,所以返回的不是 Reference 類型,this 為 undefined

(foo.bar, foo.bar)()

看示例5,逗號操作符,查看規范11.14 Comma Operator ( , )

計算第二步:

2.Call GetValue(lref).

因為使用了 GetValue,所以返回的不是 Reference 類型,this 為 undefined

揭曉結果

所以最后一個例子的結果是:

var value = 1;

var foo = {
  value: 2,
  bar: function () {
    return this.value;
  }
}

//示例1
console.log(foo.bar()); // 2
//示例2
console.log((foo.bar)()); // 2
//示例3
console.log((foo.bar = foo.bar)()); // 1
//示例4
console.log((false || foo.bar)()); // 1
//示例5
console.log((foo.bar, foo.bar)()); // 1

注意:以上是在非嚴格模式下的結果,嚴格模式下因為 this 返回 undefined,所以示例 3 會報錯。

補充

最最后,忘記了一個最最普通的情況:

function foo() {
    console.log(this)
}

foo(); 

MemberExpression 是 foo,解析標識符,查看規范 10.3.1 Identifier Resolution,會返回一個 Reference 類型的值:

var fooReference = {
    base: EnvironmentRecord,
    name: 'foo',
    strict: false
};

接下來進行判斷:

2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值為 GetBase(ref)

因為 base value 是 EnvironmentRecord,并不是一個 Object 類型,還記得前面講過的 base value 的取值可能嗎? 只可能是 undefined, an Object, a Boolean, a String, a Number, 和 an environment record 中的一種。

IsPropertyReference(ref) 的結果為 false,進入下個判斷:

2.2 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值為 ImplicitThisValue(ref)

base value 正是 Environment Record,所以會調用 ImplicitThisValue(ref)

查看規范 10.2.1.1.6,ImplicitThisValue 方法的介紹:該函數始終返回 undefined。

所以最后 this 的值就是 undefined。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容