谈谈Javascript中的delete操作符

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

  1. Let ref be the result of evaluating UnaryExpression.
  2. If Type(ref) is not Reference, return true.
  3. If IsUnresolvableReference(ref) then,
    • If IsStrictReference(ref) is true, throw a SyntaxError exception.
    • Else, return true.
  4. 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.
  5. 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细节了,我总结了一下。

  1. 险些一切的binding都是不可删除的。函数的参数,变量声明,函数声明,catch变量,arguments均不可删除
  2. 破例是eval环境下的变量声明和函数声明是可删除的

细致的可拜见:

故有:

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均不可删除

– 完 –

    原文作者:ssnau
    原文地址: https://segmentfault.com/a/1190000000508876
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞