国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

理解引用

curlyCheng / 1704人閱讀

摘要:我會解釋里面神秘的引用,一旦你理解了引用,你就會明白通過引用來了解的綁定是多么輕松,你也會發現讀的規范容易得多了。二理論把引用定義成。看看運算符的說法這也就是為什么我們對一個無法解析的引用使用操作符的時候并不會報錯。

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, namestrict 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, namestrict三個屬性。下面舉兩個例子:

//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

相關文章

  • 理解Javascritp中的"引用"

    摘要:二邏輯判斷中的引用通常我們會把理解為值相同,把理解為值相同且類型相同。但是這種理解不是完全準確的。只能確保定義的變量的引用地址不會被改變。 Author: bugall Wechat: bugallF Email: 769088641@qq.com Github: https://github.com/bugall 一: 函數中的引用傳遞 我們看下下面的代碼的...

    用戶83 評論0 收藏0
  • 幾分鐘理解 Jdk - Reference

    摘要:一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。為一個對象設置虛引用關聯的唯一目的就是能在這個對象被垃圾收集器回收時收到一個系統通知。在之后提供了類來實現虛引用參考深入理解虛擬機 GC $TODO$ 一個對象的生命周期 一個對象的生命周期從它被創建開始,此時虛擬機會給它置一個內部標識finalizable,當 GC 到達某一個安全點并檢驗該...

    JohnLui 評論0 收藏0
  • PHP中引用傳遞+unset+global理解,希望大神指正

    摘要:即產生了相當于這樣的效果,所以改變的值也同時改變了的值。不要用返回引用來增加性能,引擎足夠聰明來自己進行優化。只能從函數返回引用變量沒別的方法。 關鍵是對global的誤解,之前以為在函數中global變量,就是把函數外部的變量拿進函數內部使用,但似乎我錯了引用傳遞+unset+global理解 php的引用(就是在變量、函數、對象等前面加上&符號)在PHP中引用的意思是:不同的名字訪...

    ConardLi 評論0 收藏0
  • 理解-PHP引用

    摘要:引用本身概念好理解性能也很好但是用好它還是存在著一定的門檻不太好寫。寫本文的起因是這幾天碰到非常好的一個解決方案,讓我重新理解了引用。如果下面的代碼,你看完就能理解了,說明你引用真是學到家了你也可以直接跳過本文哈。 起因: 日常開發中,我們會碰到構造樹的需求,通過id,pid的關系去構建一個樹結構,然后對樹進行遍歷等操作。其實現方式分為兩種: 1. 遞歸, 2. 引用而這兩個方法的優缺...

    ermaoL 評論0 收藏0
  • 前端基礎進階(一):內存空間詳細圖解

    摘要:一棧數據結構與不同,中并沒有嚴格意義上區分棧內存與堆內存。引用數據類型的值是保存在堆內存中的對象。不允許直接訪問堆內存中的位置,因此我們不能直接操作對象的堆內存空間。為了更好的搞懂變量對象與堆內存,我們可以結合以下例子與圖解進行理解。 showImg(https://segmentfault.com/img/remote/1460000009784102?w=1240&h=683); ...

    _Suqin 評論0 收藏0
  • 深入理解js對象的引用

    JavaScript 有七種內置類型,其中: 基本類型 ? 空值(null) ? 未定義(undefined) ? 布爾值( boolean) ? 數字(number) ? 字符串(string) ? 符號(symbol,ES6 中新增) 引用類型 ? 對象(object) 對于基本類型,賦值(=)是值的拷貝,比較(===)的是實際的值,而對于引用類型(Array也是一種Object),賦值(=)...

    hedge_hog 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<