【JavaScript】從 this 指向到 reference 類型


判斷“this 指向誰”是個老大難的問題。

網絡上有許多文章教我們如何判別,但大多艱澀復雜,難以理解。

那么這里介紹一個非常簡單實用的判別規則:

1)在函數【調用】時,“this”總是指向小數點左側的那個對象

2)如果沒有小數點,那么“this”指向全局作用域(比如 Window,嚴格模式為 undefined)

3)有幾個可以改變“this”指向的函數——bind,call 和 apply

4)關鍵字 “new” 將 “this” 綁定到那個新創建的對象上

好了,我們已經學會了基本的理論知識,是時候運用一波了。請判斷下面的 this 指向:

var foo = {bar: function () {return this}}
var b = foo.bar
  
foo.bar();
b();
var c = new someFunction(); 

相信你已經完全掌握了,下面我們再進行一點鞏固練習:

(f = foo.bar)();
(1, foo.bar)();
(foo.bar)();

並不是上面的判別規則除了差錯,而是新增的操作符/運算符增加了代碼復雜度。為了解釋上述代碼的行為,我們需要理解 Reference 和 函數調用。

Reference Specification Type

在 ES5 中,除了基本的 6 種類型(string,number,boolearn,null,undefined,object),還有 reference 類型,不過它對使用者屏蔽,作為開發者,我們也不用關心它。

但是,了解它能夠提升我們對 ECMAScript 的認識。

Reference 是什么

ECMAScript 將 Reference 定義為“被解析的命名綁定(resolved name binding)”,它由三部分組成——base,name, and strict flag。

有兩種創建 Reference 的途徑:

  • 標識符解析
  • 屬性訪問

比如,foo 和 foo.bar 創建了一個 Reference ,而字面量(1,“foo”,[1,3]等)或函數表達式——(function(){})卻不會。

參考下圖:

每創建一個 Reference 都會為其對應的 base,name,strict 設置相應的值。"strict "對應代碼是否開啟了嚴格模式;"name"設置為標識符或屬性名;"base"設置為 property 對象或環境記錄(environment record)。

可以認為 Reference 是一個不帶原型、有且只有 3 個屬性的對象。譬如說:

'use strict';
var foo;

// 標識符解析會產生 Reference
var Reference = Object.create(null);
Reference.base = EnvironmentRecord;
Reference.name = 'foo';
Reference.strict = true;

// or
foo.bar;

// 屬性訪問會產生 Reference
var Reference = Object.create(null);
Reference.base = foo;
Reference.name = 'bar';
Reference.strict = true;

// or 使用未聲明的變量
a;
var Reference = Object.create(null);
Reference.base = undefined;
Reference.name = 'a';
Reference.strict = true;

函數調用

當函數調用的時候,會發生什么?Function Calls

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](https://es5.github.io/#x11.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
    * If IsPropertyReference(ref) is true, then Let thisValue be GetBase(ref).
    * Else, the base of ref is an Environment Record, Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref). 
7. Else, Type(ref) is not Reference.
    * 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.

ES5 標准告訴我們一個事實——只有在函數真正調用的時候,才能判斷 this 的值。

賦值,逗號和分組操作符

有了以上的准備,我們可以解答(f = foo.bar)()、(1, foo.bar)()和(foo.bar)()的 this 指向問題了。

簡單賦值(=)操作

諸如 a = 1, g = function(){} 等都屬於Simple Assignment,和函數調用一樣,在賦值發生之前,JS 也會做一些准備工作:

  1. Let lref be the result of evaluating LeftHandSideExpression.
  2. Let rref be the result of evaluating AssignmentExpression.
  3. Let rval be GetValue(rref).
  4. Throw a SyntaxError exception if the following conditions are all true:
    a. Type(lref) is Reference is true
    b. IsStrictReference(lref) is true
    c. Type(GetBase(lref)) is Environment Record
    d. GetReferencedName(lref) is either "eval" or "arguments"
  5. Call PutValue(lref, rval).
  6. Return rval.

注意,在賦值前,等號右側的表達式的值會經過內部函數 GetValue 進行轉化。

在我們的例子中,GetValue 將 foo.bar 的引用轉化成那個實際的函數。賦值完成后,和調用(function(){})()沒有什么分別。現在我們可以使用前面定義的規則來判別 this 指向了,顯然這符合第二條規則—— this 指向全局。

逗號操作符

上面的過程也適用於逗號操作符 Comma Operator ( , )

  1. Let lref be the result of evaluating Expression.
  2. Call GetValue(lref).
  3. Let rref be the result of evaluating AssignmentExpression.
  4. Return GetValue(rref).

GetValue 將 foo.bar 的引用轉化成那個實際的函數。逗號操作符計算完成后,和調用(function(){})()沒有什么分別。

分組操作符

Grouping Operator會使用 GetValue 計算表達式嗎?

Return the result of evaluating Expression. This may be of type Reference.
This algorithm does not apply GetValue to the result of evaluating Expression.

由於分組操作符不會對表達式做額外的操作,所以(foo.bar)() 和 foo.bar()沒有差別,this 指向 foo。

(完)

如果你想知道更多細節,不妨點擊Annotated ECMAScript 5.1

雖然不是標准文檔,但更容易閱讀。

參考

know-thy-reference/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM