你以为以下代码中,哪些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:本文所表述的内容均由范例演译而来,而非经由过程试验举行推理,代码示例仅用来证实文中论述内容的正确性,若有迷惑迎接留言议论。
delete终究在做啥?
拜见EMCA 262-5 第11.4.1小节:
The production UnaryExpression : delete UnaryExpression is evaluated as follows:
- 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好像照样须要花费点气力的,没紧要,慢慢来。
什么是Reference?
在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)
关于以下代码(在全局作用域下):
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
Property Reference
什么时候会有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,那末:
- 在一般mode下,属性不会被删除,返回true
- 在strict mode下,抛出Type Error非常
假如一个属性的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)提及。
Execution Context
关于一个特定的实行上下文,它有以下组成:
{
LexicalEnvironment: {},
VariableEnvironment: {},
ThisBinding: {}
}
ThisBinding很好明白,就是一个特定实行上下文的this
值。而LexicalEnvironment和VariableEnvironment又是什么?这两个都是Lexical Environment,(摔,术语愈来愈多了)。
一个Lexical Environment由两部份组成:
{
EnvironmentRecord: {}, //一个Environment Record
OuterLexicalEnvironment: outer //指向它外层的词法环境
}
那环境纪录(Environment Record)是什么呢?
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。但为何还区分出这两个呢?
- 关于一个EC的VariableEnvironment,一量建立它的指向不会转变,永远是指向同一个Lexical Environment
- 关于一个LexicalEnvironment,可能会依据代码的掌握流转变,如进入了
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
DeleteBinding的操纵可明白为:
- 关于Object Environment Record,挪用其内部的[[Delete]]要领。
- 关于Declarative Environment Record,当且仅且在建立这个Binding时指定了它是可删除的,才能够从当前Record中删掉这个binding
首先看简朴的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细节了,我总结了一下。
- 险些一切的binding都是不可删除的。函数的参数,变量声明,函数声明,catch变量,arguments均不可删除
- 破例是eval环境下的变量声明和函数声明是可删除的
细致的可拜见:
- 10.5 Declaration Binding Instantiation 变量声明绑定
- 12.14 The try Statement try..catch时的变量绑定
故有:
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
属性。- Object Environment Record 上的binding也取决于其configurable属性,但是一个OER的binding的建立体式格局有两种,一种是用户代码本身赋上去,如
a = 123
,另一种是引擎采纳CreateBinding来建立,如在全局作用域下的var x = 123
,就会在GLOBAL对象上建立一个configurable为false的binding
- Object Environment Record 上的binding也取决于其configurable属性,但是一个OER的binding的建立体式格局有两种,一种是用户代码本身赋上去,如
- 关于Environment Record而言,取决于CreateBinding时是不是指定了这个Binding是一个可删除了,除了eval中的变量声明和函数声明是可删除的外,别的一切binding均不可删除
– 完 –