摘要:我會解釋里面神秘的引用,一旦你理解了引用,你就會明白通過引用來了解的綁定是多么輕松,你也會發現讀的規范容易得多了。二理論把引用定義成。看看運算符的說法這也就是為什么我們對一個無法解析的引用使用操作符的時候并不會報錯。
Know thy reference
(原文:know thy reference - kangax)
一、前言翻譯好不是件容易的事兒,我盡量講得通順,一些術語會保留原詞,翻出一篇拗口的文章實在是得不償失。
二、用大量的抽象來解釋"this"之前的一個星期天的早上,我躺床上看HackerNews,有一篇叫「This in JavaScript」的文章,我稍微掃了兩眼。不出意外,就是函數調用、方法調用、顯式綁定、構造函數實例化這檔子事。這篇文章特別長,我越看就越覺得,這一大堆的解釋和例子會給一個不了解this機制的人帶來多大的心理陰影啊。
我想起來幾年前我第一次看DC的「JavaScript The Good Parts」,當時看完書里的相關總結之后覺得無比清晰,書里簡要地列出了這幾條:
The this parameter is very important in object oriented programming, and its value is determined by the invocation pattern.There are four patterns of invocation in JavaScript:the method invocation pattern,the function invocation pattern,the constructor invocation pattern and the apply invocation pattern.The patterns differ in how the bonus parameter this is initialized.
只由調用方式決定,而且只有四種情況。看看,這說得多簡單。
于是我去評論里看有沒有人說 HackerNews 的這篇文章講得太復雜了。果然,很多人都搬出了「JavaScript The Good Parts」里的總結,其中一個人提煉了一下:
The keyword this refers to whatever is left of the dot at call-time.
If there"s nothing to the left of the dot,then this refers to the root scope(e.g. Window)
A few functions change the behavior of this - bind,call and apply
The keyword new binds this to the object just created
簡直精辟。但是我注意到里面的一句話-"whatever is left of the dot at call-time"。乍一看很有道理嘛,比方說foo.bar(),this指向foo;又比方說foo.bar.baz(),this指向foo.bar。但是(f = foo.bar)()呢?在這里所謂的「Whatever is left of the dot at call-time」就是foo,那this就指向foo咯?
為了拯救前端程序員于水火之中,我留言說,所謂的「句號左邊的東西」可能沒這么簡單,要真的理解this,你可能需要理解引用和它的base values。
也是這一次經歷我才真的意識到引用的概念其實很少被提到。我去搜了一下"JavaScript reference",結果出來一些關于"pass-by-reference vs. pass-by-value"的討論。不行,我得出來救場了。
這就是為什么我要來寫這篇博客。
我會解釋ECMAScript里面神秘的引用,一旦你理解了引用,你就會明白通過引用來了解this的綁定是多么輕松,你也會發現讀ECMAScript的規范容易得多了。
一、關于引用老實說,看到關于引用的討論那么少我也多多少少可以理解,畢竟這也并不是語言本身的一部分。引用只是一種機制,用來描述ECMAScript里的特定行為。它對于解釋引擎的實現至關重要,但是它們在代碼里是看不見摸不著的。
當然,理解它對于寫代碼完完全全是必要的。
回到我們之前的問題:
foo.bar() (f = foo.bar)()
到底為什么第一個的this指向foo,而第二個指向全局對象呢?
你可能會說,“括號左邊的表達式里面完成了一次對 f 的賦值,賦值完了之后就相當于調用 f() ,這樣的話就是一次函數調用,而不是方法調用了。”
好的,那這個呢:
(1, foo.bar)()
“噢,這是個圓括號運算符嘛!它完成從左邊到右邊的求值,所以它肯定和 foo.bar() 是一樣的,所以它的this指向foo。”
var foo = { bar: function() { "use strict" return this } } (1, foo.bar)() //undefined
“呃......真是奇怪啊”
那這個呢:
(foo.bar)()
“呃,考慮到上一個例子,肯定也是undefined吧,應該是圓括號搞了什么鬼。”
(foo.bar)() //{bar: function(){ ... }}
“好吧......我服了。”
二、理論ECMAScript把引用定義成「resolved name binding」。這是由三個部分組成的抽象實體 - base, name和strict flag,第三個好懂,現在咱們聊前兩個就夠了。
創建引用有兩種情況:
Identifier resolution
property access
比方說吧,foo創建了一個引用,foo.bar也創建了一個引用。而像1, "foo", /x/, { }, [ 1,2,3 ]這些字面量值和函數表達式(function(){})就沒有。
Example | Reference? | Notes |
---|---|---|
"foo" | NO | |
123 | NO | |
/x/ | NO | |
({}) | NO | |
(function(){}) | NO | |
foo | YES | Could be unresolved reference if foo is not defined |
foo.bar | YES | Property reference |
(123).toString | YES | Property reference |
(function(){}).toString | YES | Property reference |
(1, foo.bar)() | NO | Already evaluated, BUT see grouping operator exception |
(f = foo.bar)() | NO | Already evaluated, BUT see grouping operator exception |
(foo) | YES | Grouping operator does not evaluate reference |
(foo.bar) | YES | Grouping operator does not evaluate reference |
先別管后面四個,我們待會再看。
每次一個引用創建的時候,它的組成部分base,name,strict都被賦上值。name就是解析的標識符或者屬性名,base就是屬性對象或者環境對象。
可能把引用理解成一個沒有原型的JavaScript對象會比較好,它就只有base, name和strict三個屬性。下面舉兩個例子:
//when foo is defined earlier foo var Reference = { base: Environment, name: "foo", strict: false } ---------------- foo.bar //這就是所謂的「Property Reference」 var Reference = { base: foo, name: "bar", strict: false }
還有第三種情況,即不可解析引用。如果在作用域里找不到標識符,引用的base就會設為undefined:
//when foo is not defined foo var Reference = { base: undefined, name: "foo", strict: false }
你肯定見過,解析不了的引用可能會導致引用錯誤-("foo is not defined").
本質上來說,引用就是一種代表名稱綁定的簡單機制,它把對象的屬性解析和變量解析抽象出一個類似對象的數據結構:
var reference = { base: Object or Environment, name: name }
現在我們知道ECMAScript底層做了什么了,但是這對解釋this的指向有什么用呢?
三、函數調用看看函數調用的時候發生了什么:
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
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).
Else, Type(ref) is not Reference.
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.
加粗的第六步基本上就解釋了DC四條里面的1、2兩條:
//foo.bar() - `foo.bar`是個屬性引用嗎? - 是的 - 那取它的base,也就是`foo`作為`this`吧 //foo() - `foo`是個屬性引用嗎? - 不是 - 那你的base就是undefined //(function(){})() - 什么?你連引用都不是啊,那不用看了,undefined四、賦值,逗號,圓括號運算符
有了前面的了解,我們看看能不能解釋得了下面這幾個函數調用的this指向。
(f = foo.bar)()
(1, foo.bar)()
(foo.bar)()
從第一個賦值運算說起,括號里是一個簡單賦值操作,如果我們看看簡單賦值做了些什么的話,我們可能可以看出點端倪:
Let lref be the result of evaluating LeftHandSideExpression.
Let rref be the result of evaluating AssignmentExpression.
Let rval be GetValue(rref).
Throw a SyntaxError exception if the following conditions are all true:
Type(lref) is Reference is true
IsStrictReference(lref) is true
Type(GetBase(lref)) is Environment Record
GetReferencedName(lref) is either "eval" or "arguments"
Call PutValue(lref, rval).
Return rval.
注意到右邊的表達式在賦值之前通過內部的GetValue()求值。在我們的例子里面,foo.bar引用被轉換成了一個函數對象,那么以非引用方式調用函數的話,this就指向了undefined。所以深入剖析的話,比起來foo.bar(),(f = foo.bar)()其實更像是(function(){})()。就是說,它是個求過值的表達式,而不是一個擁有base的引用。
第二個逗號運算就類似了:
Let lref be the result of evaluating Expression.
Call GetValue(lref).
Let rref be the result of evaluating AssignmentExpression.
Return GetValue(rref).
通過了GetValue,引用轉換成了函數對象,this指向了undefined.
最后是圓括號運算符:
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. The principal motivation for this is so that operators such as delete and typeof may be applied to parenthesised expressions.
那很明白了,圓括號運算符沒有對引用進行轉換,這也就是為什么它的this指向了foo.
五、typeof運算符既然都聊到這兒了,干脆聊一聊別的。看看typeof運算符的說法:
Let val be the result of evaluating UnaryExpression.
If Type(val) is Reference, then
If IsUnresolvableReference(val) is true, return "undefined".
Let val be GetValue(val).
Return a String determined by Type(val) according to Table 20.
這也就是為什么我們對一個無法解析的引用使用typeof操作符的時候并不會報錯。但是如果不用typeof運算符,直接做一個聲明呢:
Expression Statement:
Let exprRef be the result of evaluating Expression.
Return (normal, GetValue(exprRef), empty).
GetValue():
If Type(V) is not Reference, return V.
Let base be the result of calling GetBase(V).
If IsUnresolvableReference(V), throw a ReferenceError exception.
看到了吧,過不了GetValue這一關,所以說出現了沒法解析的聲明直接就報錯了。
六、delete運算符長話短說:
如果不是個引用,返回true(delete 1,delete /x/)
如果是沒法解析的引用(delete iDontExist)
嚴格模式,報錯
否則返回true
如果確實是個屬性引用,那就刪了它,返回true
如果是全局對象作為base的屬性
嚴格模式,報錯
否則,刪除,返回true
三、后記這篇文章都是基于ES5的,ES2015可能會有些變化。
另外,結果我還是翻出來一篇拗口的文章,Oops!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/79037.html
摘要:二邏輯判斷中的引用通常我們會把理解為值相同,把理解為值相同且類型相同。但是這種理解不是完全準確的。只能確保定義的變量的引用地址不會被改變。 Author: bugall Wechat: bugallF Email: 769088641@qq.com Github: https://github.com/bugall 一: 函數中的引用傳遞 我們看下下面的代碼的...
摘要:一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。為一個對象設置虛引用關聯的唯一目的就是能在這個對象被垃圾收集器回收時收到一個系統通知。在之后提供了類來實現虛引用參考深入理解虛擬機 GC $TODO$ 一個對象的生命周期 一個對象的生命周期從它被創建開始,此時虛擬機會給它置一個內部標識finalizable,當 GC 到達某一個安全點并檢驗該...
摘要:即產生了相當于這樣的效果,所以改變的值也同時改變了的值。不要用返回引用來增加性能,引擎足夠聰明來自己進行優化。只能從函數返回引用變量沒別的方法。 關鍵是對global的誤解,之前以為在函數中global變量,就是把函數外部的變量拿進函數內部使用,但似乎我錯了引用傳遞+unset+global理解 php的引用(就是在變量、函數、對象等前面加上&符號)在PHP中引用的意思是:不同的名字訪...
摘要:一棧數據結構與不同,中并沒有嚴格意義上區分棧內存與堆內存。引用數據類型的值是保存在堆內存中的對象。不允許直接訪問堆內存中的位置,因此我們不能直接操作對象的堆內存空間。為了更好的搞懂變量對象與堆內存,我們可以結合以下例子與圖解進行理解。 showImg(https://segmentfault.com/img/remote/1460000009784102?w=1240&h=683); ...
JavaScript 有七種內置類型,其中: 基本類型 ? 空值(null) ? 未定義(undefined) ? 布爾值( boolean) ? 數字(number) ? 字符串(string) ? 符號(symbol,ES6 中新增) 引用類型 ? 對象(object) 對于基本類型,賦值(=)是值的拷貝,比較(===)的是實際的值,而對于引用類型(Array也是一種Object),賦值(=)...
閱讀 718·2021-10-14 09:42
閱讀 1972·2021-09-22 15:04
閱讀 1574·2019-08-30 12:44
閱讀 2140·2019-08-29 13:29
閱讀 2734·2019-08-29 12:51
閱讀 548·2019-08-26 18:18
閱讀 702·2019-08-26 13:43
閱讀 2808·2019-08-26 13:38