你以爲下列代碼中,哪些delete
操做能成功?人肉判斷一下,不要放進瀏覽器裏執行。瀏覽器
// #1 a = "hello world"; delete a; // #2 var b = "hello world"; delete b; // #3 x = {}; Object.defineProperties(x, { "p1": { value: 'hello', configurable: true }, "p2": { value: "world", configurable: false } }); console.log(delete x.p1); console.log(delete x.p2); // #4 function f() { console.log("hello f"); } delete f; // #5 with({t:'try'}) { console.log(t); delete t; console.log(t); // print what? } // #6 try { throw "hello"; } catch (e) { console.log(e); delete e; console.log(e);// print what? } // #7 function test(a, b, c) { delete a; console.log(a); delete arguments; console.log(arguments); } test(1,2,3); // #8 eval('var v = "ttt"'); delete v; console.log(v); // #9 y = {a:'bye'}; function f() { return y.a; } delete f();
若是上述代碼都在strict
模式下執行呢,又有哪些操做是成功的呢?若是不清楚的話,往下看。函數
PS:本文所表述的內容均由規範演譯而來,而非經過實驗進行推理,代碼示例僅用來證實文中闡述內容的正確性,若有疑惑歡迎留言討論。this
參見EMCA 262-5 第11.4.1小節:lua
The production UnaryExpression : delete UnaryExpression is evaluated as follows:code
- Let ref be the result of evaluating UnaryExpression.
- If Type(ref) is not Reference, return true.
- If IsUnresolvableReference(ref) then,
- If IsStrictReference(ref) is true, throw a SyntaxError exception.
- Else, return true.
- If IsPropertyReference(ref) is true, then
- Return the result of calling the [[Delete]] internal method on
ToObject(GetBase(ref)) providing GetReferencedName(ref) and
IsStrictReference(ref) as the arguments.- Else, ref is a Reference to an Environment Record binding, so
- If IsStrictReference(ref) is true, throw a SyntaxError exception.
- Let bindings be GetBase(ref).
- Return the result of calling the DeleteBinding concrete method of bindings, providing GetReferencedName(ref) as the argument.
要讀懂上面這一堆術語諸如Type(ref), Reference, GetBase(ref),DeleteBinding彷佛仍是須要花費點力氣的,不要緊,慢慢來。對象
在ECMA規範中,Reference是一個抽象的概念,由三個部分組成,能夠理解爲:接口
{ base: undefined | Object | Boolean | String | Number | environment record, //這個表示Reference的基 refName: string, //在ECMA中,常以Reference Name表示 isStrict: boolean //是不是一個strict的reference,若是在strict mode下執行的話,對全部的Reference這個值都是true。而在普通mode下,則須要分狀況討論了 }
何時會建立一個Reference呢?有兩種狀況:
- 解析變量(GetIdentifierReference )
- 訪問對象屬性(Property Accessors)ip
對於以下代碼(在全局做用域下):作用域
var jake= 'string'; delete jake;
在delete表達式中,對jake
變量的解析即可獲得這樣的一個Reference
:字符串
{ base: GLOBAL, //base是全局對象,在瀏覽器環境下就是window對象 refName: 'jake', //Reference Name就是字符串jake isStrict: false }
而對於以下代碼:
var man = { name: 'delta', age: 24 }; console.log(man.age);
在console.log(man.age)
語句中,對man.age
的解析即可獲得以下的Reference
。
{ base: man, refName: 'age', isStrict: false }
So Easy,那什麼狀況下會有IsUnresolvableReference(ref)
爲true
的狀況呢?當且僅當一個Reference的Base值爲undefined
時,纔會有IsUnresolvableReference(ref)
爲true。
delete abcd;
在解析abcd
變量時,會查找當前環境記錄(Environment Record)是否有一個叫abcd
這樣的綁定(Binding),若是有,則當前環境記錄則爲Base值,不然再從當前詞法環境(Lexical Environment)的父環境(parent Lexical Environment)的環境記錄中查找,直到undefined
。故對於解析abcd
而言,獲得的*Reference`爲:
{ base: undefined, refName: 'abcd', isStrict: false }
上述全部Reference的isStrict
屬性在strict mode
下均爲true
。
回到delete的定義,能夠看到:
If Type(ref) is not Reference, return true.
If IsUnresolvableReference(ref) then,
- If IsStrictReference(ref) is true, throw a SyntaxError exception.
- Else, return true.
這就很好理解了,能夠得出以下結論(在普通mode下):
delete abcdefg; //不會報錯,並且還返回true delete "abcde"; //"abcde"是一個值,不是Reference,返回true
何時會有IsPropertyReference(ref)
爲true呢?這很好理解,僅當一個Reference
的Base值爲一個Object或一個JS原生類型如string, boolean, number時,它纔會爲true
.
回到delete的定義:
If IsPropertyReference(ref) is true, then
+ Return the result of calling the [[Delete]] internal method on ToObject(GetBase(ref)) providing GetReferencedName(ref) and IsStrictReference(ref) as the arguments.
所以有:
a = {}; delete a.p; //結果是true delete "hello".p //結果也是true y = {a:'bye'}; function f() { return y.a; } delete f(); //結果是true,由於f()的結果是一個值,不是Reference
重點在於[[Delete]]這個內部方法,若是一個屬性的Configurable
爲false,那麼:
若是一個屬性的Configurable
爲true的話,那麼delete操做就能成功去除相應的屬性。
回到delete的定義,最後一段:
Else, ref is a Reference to an Environment Record binding, so
- If IsStrictReference(ref) is true, throw a SyntaxError exception.
- Let bindings be GetBase(ref).
- Return the result of calling the DeleteBinding concrete method of
bindings, providing GetReferencedName(ref) as the argument.
若是一個reference是一個Environment Record binding的話,但Environment Record是什麼?而Environment Record binding又是什麼?
這要從執行上下文(Execution Context)提及。
對於一個特定的執行上下文,它有以下構成:
{ LexicalEnvironment: {}, VariableEnvironment: {}, ThisBinding: {} }
ThisBinding很好理解,就是一個特定執行上下文的this
值。而LexicalEnvironment和VariableEnvironment又是什麼?這兩個都是Lexical Environment,(摔,術語愈來愈多了)。
一個Lexical Environment由兩部分組成:
{ EnvironmentRecord: {}, //一個Environment Record OuterLexicalEnvironment: outer //指向它外層的詞法環境 }
那環境記錄(Environment Record)是什麼呢?
Environment Record
分爲兩種,一種是Object Environment Record
,另外一種是Declarative Environment Record
。 從概念上來說,這二者區別不大,它們都實現了相同的接口。惟一區別就是Object Environment Record
是一個用戶可訪問到的Javascript Object。而Declarative Environment Record
沒法在JS代碼中訪問到。一個Environment Record
上會有一系列的綁定(binding),若是把Environment Record
當作一個對象的話,那麼它上面的綁定(binding)就能夠認爲是它的屬性了。
//對於一個函數 function hello(b, c) { var a = 10; } hello();//執行它會進入一個新的Execution Context //它有一個Environment Record er = { a: undefined, b: undefined, c: undefined, arguments: `List of args` } //它有一個Lexical Environment le = { EnvironmentRecord: er, OuterLexicalEnvironment: GLOBAL } //而它的Execution Context爲: EC = { LexicalEnvironment: le, VariableEnvironment: le, //VariableEnvironment和LexicalEnvironment指向同一個Lexical Environment ThisBinding: GLOBAL }
其實對於任意Execution Context(簡稱EC),99%的狀況你均可以認爲它的LexicalEnvironment和VariableEnvironment都指向同一個Lexical Environment。但爲何還區分出這兩個呢?
with
代碼塊裏或是catch
代碼塊裏,進入with
和catch
後,會建立新的LexicalEnvironment(簡稱LE),而後將當前的LE當作新的LE的parent,最後將EC.LexicalEnvironment指向新的LE一旦瞭解了Execution Context, Lexical Environment, Environment Record這些概念,回到delete定義:
- Let bindings be GetBase(ref).
- Return the result of calling the DeleteBinding concrete method of
bindings, providing GetReferencedName(ref) as the argument.
經過GetBase(ref)取得它的Environment Record,而後調用相應的DeleteBinding的內部方法來刪除binding。那麼DeleteBinding又有什麼玄機呢?
DeleteBinding的操做可理解爲:
首先看簡單的Object Environment Record狀況:
a = "ttt"; delete a; console.log(a); //報錯,由於GLOBAL是一個Object Environment Record(簡稱OER),而a屬性是可刪除的 var t = {a:'ccc'} with(t) { delete a; console.log(a); //報錯,由於當前的Environment Record是一個指向t的OER,而其a屬性是可刪除的 }
對於其它狀況,咱們就須要充分理解Create Binding細節了,我總結了一下。
詳細的可參見:
故有:
var a = "cccc"; delete a; //沒用的 function s(){ } delete s; //沒用 function f(a,b){ //均沒用 delete a; delete b; delete f; delete arguments; } try { throw "hello"; } catch(e) { delete e; //沒用 } eval("var a = 'ccc'; delete a; console.log(a)");//能刪掉,最後的console.log會報錯
delete a.b
,取決於configurable
屬性。a = 123
,另外一種是引擎採用CreateBinding來建立,如在全局做用域下的var x = 123
,就會在GLOBAL對象上建立一個configurable爲false的binding- 完 -