资讯专栏INFORMATION COLUMN

谈谈Javascript中的delete操作符

antz / 1906人阅读

摘要:你觉得下列代码中,哪些操作能成功人肉判断一下,不要放进浏览器里执行。故对于解析而言,得到的为上述所有的属性在下均为。那么又有什么玄机呢的操作可理解为对于,调用其内部的方法。几乎所有的都是不可删除的。

你觉得下列代码中,哪些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代码块里,进入withcatch后,会创建新的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

对于Environment Record而言,取决于CreateBinding时是否指定了这个Binding是一个可删除了,除了eval中的变量声明和函数声明是可删除的外,其它所有binding均不可删除

- 完 -

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78099.html

相关文章

  • 谈谈Javascript中的void作符

    摘要:由于表达式偏啰嗦,于是最近便开始采用来减轻负担。规范是这么说的在规范,有如下描述搬译一下操作符产生式按如下流程解释令为解释执行的结果。而如果把换成操作符写成,她的就不会减了,因为操作符不会对求值。 由于JS表达式偏啰嗦,于是最近便开始采用Coffeescript来减轻负担。举个栗子,当我想取屋子里的第一条dog时,首先要判断house对象是否存在,然后再判断house.dogs是否存在...

    Barrior 评论0 收藏0
  • 谈谈JavaScript中严格模式你应该遵守的那些事

    严格模式 首先来了解一下严格模式是什么?严格模式是JavaScript中的一种限制性更强的变种方式,不是一个子集:它在语义上与正常代码有明显的差异,不支持严格模式的浏览器与支持严格模式的浏览器行为上也不一样,所以不要在未经严格模式特性测试情况下使用严格模式,严格模式可以与非严格模式共存,所以脚本可以逐渐的选择性加入严格模式 严格模式的目的 首先,严格模式会将JavaScript陷阱直接变成明显的错...

    MingjunYang 评论0 收藏0
  • 谈谈浏览器里的JavaScript

    摘要:而与最大的区别在于与浏览器沟通的窗口,不涉及网页内容。完全依赖于浏览器厂商实作本身无标准规范,而有着所制定的标准来规范。而透过选取出来的节点,我们可以通过操作属性来变更它的文字。在许多的网页前端教学或是文章书籍当中,你可能常常听到这样的说法:「HTML、CSS 与JavaScript 是网页前端三大要素」,其中: HTML 负责资料与结构 CSS 负责样式与呈现 JavaScript 负责...

    CastlePeaK 评论0 收藏0
  • 关于JavaScript对象,你所不知道的事(一)- 先谈对象

    摘要:对象与属性让我们保持耐心,再梳理一下对象与属性的关系对象是属性的集合,当对象的属性是函数时,我们将其称之为方法。 这篇博文的主要目的是为了填坑,很久之前我发表了一篇名为关于JavaScript对象中的一切(一) — 对象属性的文章,想要谈一谈JavaScript对象,可那时只是贴了一张关于这个主题的思维导图,今天我会针对这一主题进行展开,将JavaScript对象一些平常不太常用的知识...

    mykurisu 评论0 收藏0
  • 前端面试:谈谈 JS 垃圾回收机制

    摘要:例如本地函数的局部变量和参数当前嵌套调用链上的其他函数的变量和参数全局变量还有一些其他的,内部的这些值称为根。例如,如果局部变量中有对象,并且该对象具有引用另一个对象的属性,则该对象被视为可达性,它引用的那些也是可以访问的,详细的例子如下。 最近看到一些面试的回顾,不少有被面试官问到谈谈JS 垃圾回收机制,说实话,面试官会问这个问题,说明他最近看到一些关于 JS 垃圾回收机制的相关的文...

    孙淑建 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<