你以为下列代码中,哪些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- 完 -