javascript 中的 delete

几周之前,我有幸拜读斯托诺夫(Stoyan Stefanov) 的 Object-Oriented Javascript 一书.(该书在亚马逊得到非常高的评分,12个五星评价.译注:亚马逊是最有节操的网上书店,评论基本上都很真实靠谱),所以我很好奇,想看看有哪些值得称赞的干货.我从 functions 一章开始读起,其行文非常流畅随意;示例非常实用,结构特别干净、爽利. 在我看来初学者也能很快上手并掌握要点.但很快我偶然就发现了一个小坑 —— 关于删除 function 的很有趣的误解.当然也还有一些其他的小错误(如函数声明和函数表达式的区别),但在本文中就展开讨论了.

书里说 ”function 被视为正常的变量 - 可以复制到另一个变量,甚至可以被删除.” 在这个解释后面,有一个例子:

>>> var sum = function(a, b) {return a + b;}  

>>> var add = sum;  

>>> delete sum  

true  

>>> typeof sum;  

"undefined"  

忽略缺少的分号,你能发现这段代码有什么问题吗?当然,问题是,删除 sum 变量不应该成功; delete 语句不应返回 true ,而且 typeof sum 也不应返回“undefined”.因为在 Javascript 中删除变量是不可能的.至少在这种声明方式下不能.

那为什么此示例会出错? 这是一个错误?玩笑?应该不是.整个代码片段实际上是 Firebug控制台 的输出, Stoyan 肯定是快速测试过的.原因是Firebug好像采用了一些不同的删除规则. 所以是 Firebug 导致 Stoyan 在这里出错!但这到底是怎么回事呢?

要回答这个问题,我们需要了解在Javascript中 delete操作符的工作机制: 什么可以被删除,什么不能被删除以及为什么.现在我将试图详细解释其原因.我们将发现 Firebug “怪异”的行为并认识到并不是所有都是怪异的,我们将深入研究当声明变量,functions,指定属性和删除它们 时在幕后究竟发生了什么; 我们将列举浏览器的承诺和一些最臭名昭著的bug;我们也会讨论第五版 ECMAScript的严格模式,以及它如何改变delete操作符的行为.

在本文中 Javascript和ECMAScript 都指的是ECMAScript(除非特别指出是Mozilla的Javascript实现).

不出所料,网络上关于 delete 的解释相当稀少. MDC的文章 可能是最全面的资源,但遗憾的是错过了一些有趣的细节; 奇怪的是,这些被遗忘的事情之一正是Firebug的复杂行为的原因.而 MSDN参考手册 几乎是无用的.

1. 基本原理

我们可以删除对象的某个属性:

var o = { x: 1 };  

delete o.x; // true  

o.x; // undefined  

但不能删除变量,比如以下面这种方式声明的:

var x = 1;  

delete x; // false  

x; // 1  

也不能删除函数,比如下面所定义的:

function x(){}  

delete x; // false  

typeof x; // "function"  

注意 如果某个属性不能被删除的话,delete操作会返回false.

要理解这一点,我们首先需要理解变量实例化和 property 属性等概念——不幸的是在Javascript的书中很少涵盖这些东西.我会在接下来的几个段落简略地介绍这些.这些概念一点都不难理解!如果你不关心为什么JavaScript工作的方式会如此,请跳过这一章.

1.1 可执行代码的分类

在 ECMAScript 中有3种类型的可执行代码: 全局代码, 函数代码, 以及 Eval 代码.

这些类型是自描述的,下面是一个简短的概述:

当一个文本 source 被当做作一段程序,它在全局范围内执行,被认为是全局代码(Global code).在浏览器环境中, SCRIPT 元素的内容通常被解析为程序,因此等价于全局代码.

直接在函数内执行的东西,很明显,被认为是一段函数代码(Function code).在浏览器中,事件属性的内容(例如 <p onclick = "…" > )通常被解析并被认为是一段函数代码.

最后,在内置 eval 函数中的文本被解析为 Eval code. 我们很快就会看到为什么这种类型是特殊的.

1.2 执行上下文

当 ECMAScript 代码执行时,它总是处于特定的执行上下文中的.执行上下文是一个抽象的存在,这有助于理解 scope 和 变量实例 是如何工作的的. 对于三种类型的可执行代码,每种都有一个执行上下文.当一个函数执行时,可以说被控制着进入 Function代码执行上下文;当全局代码执行时,进入全局代码的执行上下文 等等.

正如您所见到的,执行上下文在逻辑上形成一个堆栈.首先是全局代码及其执行上下文;而全局代码可以调用一个函数,有函数自己的执行上下文,该函数可以调用另一个函数,等等等等.即使函数递归地调用其本身,每一次调用也会进入一个新的执行上下文.

1.3 Activation对象/Variable对象

每个执行上下文都有一个被叫做 Variable object (活化对象?)的对象与其相关联.类似于执行上下文,Variable 对象也是一个抽象的存在,用来描述变量实例化的一种机制.现在,有意思的是,在一个源文本中声明的变量和函数中实际上都被添加为该 Variable object 对象的属性(properties).

当进入全局代码执行上下文,全局对象(Global object,如浏览器中的 window)被当做其 Variable object 对象.这正是为什么在全局范围内声明的变量或函数会成为全局对象的属性的原因:

/* 当在全局范围时, `this` 指向的就是 global object */  

var GLOBAL_OBJECT = this;  

var foo = 1;  

GLOBAL_OBJECT.foo; // 1  

foo === GLOBAL_OBJECT.foo; // true  

  

function bar(){}  

typeof GLOBAL_OBJECT.bar; // "function"  

GLOBAL_OBJECT.bar === bar; // true  

OK,全局变量成为了全局对象的属性,但局部变量(在函数内部声明)又是如何处理的呢?实际上是非常相似的: 他们成为 Variable 对象的属性(properties).唯一的区别是,当在函数代码中时,Variable 对象并不是全局对象,而是一个称为Activation object 的对象.每次进入函数的执行上下文时都会创建一个 Activation object 对象.

不仅函数内部声明的变量和函数会成为 Activation object 对象的属性;而且函数的每个参数(对应到相应的参数名)、以及一个特殊的 Arguments 对象(名为 arguments )也会成为 Activation object 对象的属性.注意, Activation 对象只是一种内部的机制,程序永远不可能真正地访问(引用)到这个对象.

[javascript] view plaincopy

(function(foo){  

  var bar = 2;  

  function baz(){}  

  /* 

    以抽象虚拟的说法, 

    专有的 `arguments` 对象成为对应函数 Activation 对象的一个属性(property): 

    ACTIVATION_OBJECT.arguments; // Arguments 对象 

 

 

  ...当然,参数 `foo`也是一样的道理: 

    ACTIVATION_OBJECT.foo; // 1 

 

 

  ...同时,局部变量 `bar`同样如此: 

    ACTIVATION_OBJECT.bar; // 2 

 
 

  ...定义的局部函数也是如此: 

    typeof ACTIVATION_OBJECT.baz; // "function" 

  */  

})(1);  

最后,在 Eval 代码内声明的变量被创建为调用上下文 Variable object 对象的属性.简言之,Eval代码在哪里被调用,内部的变量就相当于在哪里被声明:

[javascript] view plaincopy

var GLOBAL_OBJECT = this;  

  

/* 此处eval内的`foo` 被创建为 调用上下文 Variable 对象的属性, 

   在此处上下文对象即为全局对象 Global object */  

eval('var foo = 1;');  

GLOBAL_OBJECT.foo; // 1  

  

(function(){  

  

    /* `bar` 被创建为 调用上下文 Variable object 对象的一个属性, 

      此处 Variable object 对象是包含的function的一个 Activation 对象 */  

    eval('var bar = 1;');  

    /* 

      以一种虚拟抽象的角度看, 

      ACTIVATION_OBJECT.bar; // 1 

    */  

  

})();  

1.4 Property 属性

该说的基本上都说完了.现在对于变量是如何处理的已经很清楚(他们变成了 properties 属性),剩下唯一需要理解的概念是 property 属性.每个 property 都可以拥有下面列举的0到多个特性 ——ReadOnly,DontEnum,DontDelete以及 Internal.你可以把它们当做标志位 —— 每一个 property 中可以存在也可以不存在的 attribute .为了下面的讨论,我们只对 DontDelete 感兴趣.

当声明的变量和函数成为 Variable object 对象的properties —— 可能是 Activation object 对象(对于 Function code 来说),或全局对象(对全局代码来说),这些 properties  创建时就被赋予了 DontDelete attribute属性.然而,任何显式(或隐式)对指定 property 属性的赋值所创建的 property 属性则没有DontDelete属性.这就是为什么我们可以删除某些 properties属性,而其他的不能删除的本质原因:

[javascript] view plaincopy

var GLOBAL_OBJECT = this;  

  

/*  `foo` 是 Global object 的一个 property. 

    是通过变量声明创建的,因此拥有 DontDelete 标志. 

    这就是为什么不能被删除的原因. */  

var foo = 1;  

delete foo; // false  

typeof foo; // "number"  

  

/*  `bar` 是 Global object 的一个 property. 

    是通过 function 声明创建的,因此拥有 DontDelete 标志. 

    这也是为什么不能被删除的原因. */  

function bar(){}  

delete bar; // false  

typeof bar; // "function"  

  

/*  `baz` 同样是 Global object 的一个 property. 

    但是通过 property 赋值创建的,However,因此没有 DontDelete 标志. 

    这是为什么可以被删除的原因. */  

GLOBAL_OBJECT.baz = 'blah';  

delete GLOBAL_OBJECT.baz; // true  

typeof GLOBAL_OBJECT.baz; // "undefined"  

1.5 内置的和不可删除的

DontDelete指的是: 在 property上的一个特殊attribute,用于控制是否可以删除此property.注意,一些内置对象的属性被指定为 DontDelete,所以不能被删除.特殊的arguments变量(或者,正如我们刚刚介绍的, Activation 对象的属性 等) 拥有 DontDelete 标志.所有 function实例的length 属性(形参个数)也具有 DontDelete 标志:

[javascript] view plaincopy

(function(){  

    /* 不能删除 `arguments`,因为其拥有 DontDelete */  

    delete arguments; // false  

    typeof arguments; // "object"  

  

    /* 不能删除 function 的 `length`; it also has DontDelete */  

    function f(){}  

    delete f.length; // false  

    typeof f.length; // "number"  

  })();  

对应于 function参数的 properties 创建时也被赋予了 DontDelete 标志,所以也不能被删除:

[javascript] view plaincopy

(function(foo, bar){  

  delete foo; // false  

  foo; // 1  

  delete bar; // false  

  bar; // 'blah'  

})(1, 'blah');  

1.6 未声明赋值

您也许还记得,未声明的赋值(没有 var 定义)将在全局对象上创建一个属性.除非是在查找到全局范围对象之前就已经在作用域链上发现了一个同名的属性.现在我们知道了 property 赋值和变量声明 的区别 —— 后者被设置了 DontDelete标志,而前一个没有被设置 —— 为什么未声明的赋值 创建的property是可删除的现在就很明显了(没设置 DontDelete标志):

[javascript] view plaincopy

var GLOBAL_OBJECT = this;  

  

/* 通过变量声明 而创建的全局 property; 拥有 DontDelete 标志*/  

var foo = 1;  

  

/* 通过未声明赋值创建的 global property; 没有 DontDelete 标志*/  

bar = 2;  

  

delete foo; // false  

typeof foo; // "number"  

  

delete bar; // true  

typeof bar; // "undefined"  

注意, attributes 是在 property 创建期间决定的(即便什么也没有设置).以后的赋值不会修改已存在property的attributes.理解这个区别是很重要的.

[javascript] view plaincopy

/* `foo` is created as a property with DontDelete */  

function foo(){}  

  

/* Later assignments do not modify attributes. DontDelete is still there! */  

foo = 1;  

delete foo; // false  

typeof foo; // "number"  

  

/* But assigning to a property that doesn't exist, 

   creates that property with empty attributes (因此没有 DontDelete 标志) */  

  

this.bar = 1;  

delete bar; // true  

  

typeof bar; // "undefined"  

2. Firebug 的困惑

那 Firebug 中究竟是怎么回事?为什么在console中声明的变量可以被删除,和我们刚刚学到的相反呢?嗯,正如我之前所说,Eval代码在变成变量声明时有一个特殊的行为.在Eval代码内声明的变量实际上是没有 DontDelete 标志的:

[javascript] view plaincopy

eval('var foo = 1;');  

foo; // 1  

delete foo; // true  

typeof foo; // "undefined"  

同样,当在函数内部调用Eval代码时:

[javascript] view plaincopy

(function(){  

  eval('var foo = 1;');  

  foo; // 1  

  delete foo; // true  

  typeof foo; // "undefined"  

  

})();  

这就是Firebug的反常行为的要点.似乎控制台中所有的文本都被当做 Eval代码 解析和执行,而不是全局代码或函数代码.显然,任何声明的变量最终都没有 DontDelete 标志,因此可以很容易地被删除. 请留意在 常规的全局代码和Firebug控制台中的这些差异.

2.1 通过eval删除变量

这是eval 一个有趣的行为,加上ECMAScript的另一个方面可以技术上允许我们删除non-deletable的 properties.在相同的执行上下文中,关于函数的声明会覆盖同名的变量(原理是在context中,变量声明被提前到整个context最前面,function声明也被提前,但应该是function声明被 提到变量声明之后,所以...):

[javascript] view plaincopy

function x(){ }  

var x; // 只声明  

typeof x; // "function"  

  

function a(){ }  

var a = 6; // 声明 + 赋值  

typeof a; // "number" 

注意,函数声明优先并覆盖same-named(同名的)变量(或者,换句话说,Variable 对象的同一个property ).这是因为 function 声明在变量声明之后 实例化,并允许覆盖它们.函数声明不仅取代 property的前一个值,它也取代该 property 的特性(attributes).如果我们通过eval声明 function,该函数也应该用自己的替换该 property的attributes .由于在eval中声明的变量所创建的 properties  没有DontDelete标志,实例化这个新函数本质上应该移除property 现有的DontDelete属性,以使 移除property 可以被删除(当然,肯定也要让其指向新创建的函数).

[javascript] view plaincopy

var x = 1;  

  

/* 不能删除, `x` has DontDelete */  

  

delete x; // false  

typeof x; // "number"  

  

eval('function x(){}');  

  

/* `x` property 现在指向 function, 并且没有  DontDelete 标志*/  

  

typeof x; // "function"  

delete x; // 按理说应该是 `true`  

typeof x; // 按理说应该是 "undefined"  

不幸的是,在我测试的所有实现环境中,这种欺骗都不会生效.我可能在这里说错了什么东西,或这种行为太晦涩了,以至于实现者不去关注.

3. 浏览器兼容性

理解事物运行的原理是很有用的,但认识实际运行环境更重要.浏览器在处理 变量/属性 的 创建/删除 时遵循标准吗?在大多数情况下,是这样的.

我编写了 一个简单的测试脚本 来测试 delete 操作符对全局代码,Function 代码和Eval代码的操作 是否遵循规范.测试脚本检查- delete操作符的返回值,以及当属性应该被删除时是否被删除. delete 的返回值不如其实际结果重要.它不是很重要如果删除返回true,而不是false,但拥有DontDelete特性的properties未被删除倒是很重要的,反之亦然.

现代浏览器的兼容性通常是非常好的.除了我 前面提到过特别的 eval ,以下浏览器对测试套件完全通过:Opera 7.54 +,Firefox 1.0 +,Safari 3.1.2 +,Chrome 4 +.

Safari 2.x 和3.0.4 在删除 function arguments 时有问题; 这些properties 创建时似乎没有赋予DontDelete特性,所以可以被删除. Safari 2.x 甚至有更多的问题 —— 删除非引用(例如delete 1;)将会抛出错误; function 声明会创建 可删除的 properties (但奇怪的是,variable 声明就不能删除); 而在eval中 variable声明又变成non-deletable(但函数声明又不是这样).

类似于 Safari,Konqueror(3.5,而不是4.3)在删除非引用时(例如 delete 1;)会抛出错误,还会错误地允许删除函数 arguments.

3.1 Gecko引擎的DontDelete缺陷

Gecko 1.8.x的浏览器,Firefox 2.x,Camino 1.x,Seamonkey 1.x,等等 —— 表现出一个有趣的bug,当显式地给一个 property 赋值后,会移除其DontDelete 特性,即使该property 是通过变量或函数声明 而创建的:

[javascript] view plaincopy

function foo(){}  

 delete foo; // false (符合预期)  

 typeof foo; // "function" (符合预期)  

  

 /* 现在显示地给一个 property 赋值*/  

  

 this.foo = 1; // 错误地清除了 DontDelete 特性  

 delete foo; // true  

 typeof foo; // "undefined"  

  

 /* 注意当隐式地给 property 赋值时并不会发生 */  

  

 function bar(){}  

 bar = 1;  

 delete bar; // false  

 typeof bar; // "number" (赋值操作替换了 property)  

令人惊讶的是,Internet Explorer 5.5 - 8 完全通过了测试套件的测试,除了删除非引用(例如 detele 1;)会抛出错误以外(就像在更古老的Safari中一样).但在IE中实际上有更严重的bugs,这些bug不会立即显示出来.这些缺陷是与全局对象有关的.

4. IE的Bugs

整篇文章只有Internet Explorer中有BUG吗?多么的意想不到啊!

在IE(至少IE6 - IE8),以下表达式将会抛出错误(在Global code中执行时):

[javascript] view plaincopy

this.x = 1;  

delete x; // TypeError: Object doesn't support this action 

下面的也是一样,但是抛出了不同的异常,事情非常的有趣:

[javascript] view plaincopy

var x = 1;  

delete this.x; // TypeError: Cannot delete 'this.x'  

在IE中,在全局代码声明的变量好像不会创建为全局对象上的属性.通过赋值创建属性( this.x = 1; ),然后通过 delete x; 删除它将会抛出错误. 通过声明创建属性(var x = 1;),然后使用delete this.x; 删除它却会抛出另一个错误.

但这还不是全部.通过显式赋值创建的属性在删除时总会抛出错误.不仅此处有一个错误,而且创建的属性似乎还被设置了DontDelete标志,这当然是不应该的:

[javascript] view plaincopy

this.x = 1;  

  

delete this.x; // TypeError: Object doesn't support this action  

typeof x; // "number" (still exists, wasn't deleted as it should have been!)  

  

delete x; // TypeError: Object doesn't support this action  

typeof x; // "number" (wasn't deleted again)  

现在,与人们的预料相反,未声明的赋值(应该在全局对象上创建一个property )在IE中却创建可删除的properties:

[javascript] view plaincopy

x = 1;  

delete x; // true  

typeof x; // "undefined" 

但如果你想在全局代码中通过 this引用来删除这个property (delete this.x),那么一个你熟悉的错误就会冒出来:

[javascript] view plaincopy

x = 1;  

delete this.x; // TypeError: Cannot delete 'this.x'  

如果我们对这种行为进行推理,会发现在全局代码执行 delete this.x; 永远不会成功.当 property 是通过显式赋值创建的(this.x = 1;),delete将会抛出一个错误;当 property 是通过未声明赋值创建的(x = 1)或通过变量声明创建(var x = 1),delete将会抛出另一个错误.

换个角度来说, delete x; 只有当 property 是通过显式赋值创建时才抛出错误——this.x = 1;.如果 property 是通过变量声明创建的(var x = 1;),删除只是简单地从不执行,并且delete 正确地返回false. 如果property 是通过未声明赋值创建的(x = 1),删除按预期方式运行.

我是 在9月份反馈这个问题 的,Garrett Smith建议在IE中 “The global variable object被实现为一个 JScript 对象,并且 global object 是由 host 来实现的".Garrett 建议参考Eric Lippert的博客 .我们可以通过执行一些测试来证实了这一理论. 注意, this 和 window 指向同一个对象(我们可以采用 === 操作符来测试),但是 Variable 对象(声明函数时的那个) 与 this 引用不同.

[javascript] view plaincopy

/* in Global code */  

function getBase(){ return this; }  

  

getBase() === this.getBase(); // false  

this.getBase() === this.getBase(); // true  

window.getBase() === this.getBase(); // true  

window.getBase() === getBase(); // false  

5. 误解

对于事物以及其运行方式和原理的美丽的理解是不能被低估的.我看到网上有一些没有真正理解 delete 操作而造成的一些误解.例如, 这是在 Stackoverflow 的一个答案(高得惊人的评级),自信地解释 “当删除的目标不是一个对象的 property 时,delete 应该是无操作”.现在我们理解了 delete行为的核心,就很清楚地知道,这个答案是相当不准确的.delete不区分变量和属性(事实上,对于delete来说,这些都是引用),并且只关心DontDelete 特性(property 要存在).

看看误解彼此反弹也是有趣的,在同一 thread 中有人首先建议删除变量(当然是行不通的,除非是在 eval 中声明),然后另一个人提供了 一份错误的修正 可以如何在全局代码中删除变量而不是Function 代码中.

请小心使用Web上对 Javascript 的解释,在理想的情况下,应该去寻求理解问题的核心;)

6. 'delete' 与 host 对象(宿主对象?)

对 delete 的一个运算法则大概是这样的:

如果操作数不是一个引用,返回true

如果对象没有这个name的direct property,返回true(正如我们现在所知道的,对象可以是Activation 对象或 Global 对象)

如果 property 存在,但具有DontDelete特性,返回 false

其他情况,删除 property 并返回 true

然而, delete 操作符与 host 对象的行为可能是相当难以预测的.其实并没有哪里出错: (规范中允许) host 对象对 operations 实现任何形式的行为,如 read (内部[[Get]]方法), write (内部[[Put]]方法)或 delete (内部[[Delete ]]方法),以及其他的一些操作. 对于自定义[[Delete]]的行为正是 host 对象如此混乱的原因.

我们已经看到一些IE的古怪,删除某些特定对象(这显然是实现为 host 对象)会抛出错误. 某些版本的Firefox在尝试删除 window.location 时也会抛出.当涉及到 host 对象时,你也不能相信 delete 的返回值,看看在Firefox中会发生什么:

[javascript] view plaincopy

/* "alert" 是 `window` 的一个 direct property  

如果我们采信 `hasOwnProperty` 的话) */  

window.hasOwnProperty('alert'); // true  

  

delete window.alert; // true  

typeof window.alert; // "function"  

删除 window.alert 返回 true ,即使对该 property 没有什么理由导致这个结果. 它解析为一个引用(所以在第一部不能返回true). 这是window的一个 direct property(所以第二步不能返回true;).delete操作返回true的唯一途径 就是到达运算法则的第四步,并且真正地删除了一个属性. 是的,在这里 property 从来没有被删除.

这个故事的寓意就是绝不能信任 host对象.

作为一根bonus,下面是IE中 delete 行为的一个怪异的 case :

[javascript] view plaincopy

var element = document.createElement('div')  

delete element.onclick; // throws "Object doesn't support this action"  

  

document.body.x = 1;  

delete document.body.x; // throws "Object doesn't support this action"  

  

// in IE8  

delete XMLHttpRequest.prototype.open; // throws "Object doesn't support this action"  

7. EcmaScript5严格模式

那么 ECMAScript5 的严格模式带来了什么? 介绍了一些限制. 当使用 delete 操作符来删除 变量,函数参数或函数标识符 的直接引用时,将会抛出 SyntaxError语法错误.此外,如果 property 内部[[Configurable]]== false时,会抛出 TypeError:

[javascript] view plaincopy

(function(foo){  

  "use strict"; // 在此函数内使用 strict mode  

  

  var bar;  

  function baz(){}  

  

  delete foo; // SyntaxError (when deleting argument)  

  delete bar; // SyntaxError (when deleting variable)  

  delete baz; // SyntaxError (when deleting variable created with function declaration)  

  

  /* 函数实例的 `length`属性拥有 { [[Configurable]] : false } */  

  

  delete (function(){}).length; // TypeError  

  

})();  

此外,删除未声明的变量(或者换句话说,未解决的引用)也会抛出语法错误:

[javascript] view plaincopy

"use strict";  

delete i_dont_exist; // SyntaxError  

这有点类似于在严格模式下的未声明赋值的行为(除了抛出的是 ReferenceError 而不是SyntaxError以外):

[javascript] view plaincopy

"use strict";  

i_dont_exist = 1; // ReferenceError  

你现在明白,所有这些限制的意义,给删除变量,函数声明和参数 导致了多少混乱的原因.

在严格模式下,不会默默地忽略删除,而是需要更多 激进的和 基础的(descriptive)措施.

8. 小结

这篇文章非常的长,所以我不再去讨论诸如使用 delete 来移除数组项(array items)及其含义.你可以参考 MOZILLA对于delete的详细说明(或自己搜索与实验).

下面是关于Javascript中删除机制的一个简短摘要:

变量和函数声明都是 Activation 或 Global 对象的 properties.

Properties 拥有 attributes特性,其中一个是 DontDelete,决定 property 是否可以被删除.

在全局和Function代码中声明的变量和函数,创建的properties 总是被赋予 DontDelete 标志.

函数 arguments 也是 Activation 对象的properties ,并在创建时赋予 DontDelete 标志.

在Eval代码中声明的变量和函数在创建 properties 时没有DontDelete标志.

新指定的properties 创建时标志位是 empty 的(所以没有DontDelete标志).

Host 对象自己决定是否允许被删除.

如果你想了解更多信息,请参阅:  ECMA-262规范_en.pdf

我希望你喜欢这个概述并学到一些新东西.一如既往地欢迎任何问题,建议和修正.

原文链接: Understanding delete 原文作者: Kangax

翻译人员: 铁锚

几周之前,我有幸拜读斯托诺夫(Stoyan Stefanov) 的 Object-Oriented Javascript 一书.(该书在亚马逊得到非常高的评分,12个五星评价.译注:亚马逊是最有节操的网上书店,评论基本上都很真实靠谱),所以我很好奇,想看看有哪些值得称赞的干货.我从 functions 一章开始读起,其行文非常流畅随意;示例非常实用,结构特别干净、爽利. 在我看来初学者也能很快上手并掌握要点.但很快我偶然就发现了一个小坑 —— 关于删除 function 的很有趣的误解.当然也还有一些其他的小错误(如函数声明和函数表达式的区别),但在本文中就展开讨论了. 书里说 ”function 被视为正常的变量 - 可以复制到另一个变量,甚至可以被删除.” 在这个解释后面,有一个例子:

[javascript] view plaincopy

  1. >>> var sum = function(a, b) {return a + b;}  
  2. >>> var add = sum;  
  3. >>> delete sum  
  4. true
  5. >>> typeof sum;  
  6. "undefined"

忽略缺少的分号,你能发现这段代码有什么问题吗?当然,问题是,删除 sum 变量不应该成功; delete 语句不应返回true ,而且 typeof sum 也不应返回“undefined”.因为在 Javascript 中删除变量是不可能的.至少在这种声明方式下不能. 那为什么此示例会出错? 这是一个错误?玩笑?应该不是.整个代码片段实际上是 Firebug控制台 的输出, Stoyan 肯定是快速测试过的.原因是Firebug好像采用了一些不同的删除规则. 所以是 Firebug 导致 Stoyan 在这里出错!但这到底是怎么回事呢? 要回答这个问题,我们需要了解在Javascript中 delete操作符的工作机制: 什么可以被删除,什么不能被删除以及为什么.现在我将试图详细解释其原因.我们将发现 Firebug “怪异”的行为并认识到并不是所有都是怪异的,我们将深入研究当声明变量,functions,指定属性和删除它们 时在幕后究竟发生了什么; 我们将列举浏览器的承诺和一些最臭名昭著的bug;我们也会讨论第五版 ECMAScript的严格模式,以及它如何改变delete操作符的行为. 在本文中 Javascript和ECMAScript 都指的是ECMAScript(除非特别指出是Mozilla的Javascript实现). 不出所料,网络上关于 delete 的解释相当稀少. MDC的文章 可能是最全面的资源,但遗憾的是错过了一些有趣的细节; 奇怪的是,这些被遗忘的事情之一正是Firebug的复杂行为的原因.而 MSDN参考手册 几乎是无用的.

1. 基本原理

我们可以删除对象的某个属性:

[javascript] view plaincopy

  1. var o = { x: 1 };  
  2. delete o.x; // true
  3. o.x; // undefined

但不能删除变量,比如以下面这种方式声明的:

[javascript] view plaincopy

  1. var x = 1;  
  2. delete x; // false
  3. x; // 1

也不能删除函数,比如下面所定义的:

[javascript] view plaincopy

  1. function x(){}  
  2. delete x; // false
  3. typeof x; // "function"

注意 如果某个属性不能被删除的话,delete操作会返回false. 要理解这一点,我们首先需要理解变量实例化和 property 属性等概念——不幸的是在Javascript的书中很少涵盖这些东西.我会在接下来的几个段落简略地介绍这些.这些概念一点都不难理解!如果你不关心为什么JavaScript工作的方式会如此,请跳过这一章.

1.1 可执行代码的分类

在 ECMAScript 中有3种类型的可执行代码: 全局代码, 函数代码, 以及 Eval 代码. 这些类型是自描述的,下面是一个简短的概述:

  1. 当一个文本 source 被当做作一段程序,它在全局范围内执行,被认为是全局代码(Global code).在浏览器环境中, SCRIPT 元素的内容通常被解析为程序,因此等价于全局代码.
  2. 直接在函数内执行的东西,很明显,被认为是一段函数代码(Function code).在浏览器中,事件属性的内容(例如<p onclick = "…" > )通常被解析并被认为是一段函数代码.
  3. 最后,在内置 eval 函数中的文本被解析为 Eval code. 我们很快就会看到为什么这种类型是特殊的.

1.2 执行上下文

当 ECMAScript 代码执行时,它总是处于特定的执行上下文中的.执行上下文是一个抽象的存在,这有助于理解 scope 和 变量实例 是如何工作的的. 对于三种类型的可执行代码,每种都有一个执行上下文.当一个函数执行时,可以说被控制着进入 Function代码执行上下文;当全局代码执行时,进入全局代码的执行上下文 等等. 正如您所见到的,执行上下文在逻辑上形成一个堆栈.首先是全局代码及其执行上下文;而全局代码可以调用一个函数,有函数自己的执行上下文,该函数可以调用另一个函数,等等等等.即使函数递归地调用其本身,每一次调用也会进入一个新的执行上下文.

1.3 Activation对象/Variable对象

每个执行上下文都有一个被叫做 Variable object (活化对象?)的对象与其相关联.类似于执行上下文,Variable 对象也是一个抽象的存在,用来描述变量实例化的一种机制.现在,有意思的是,在一个源文本中声明的变量和函数中实际上都被添加为该 Variable object 对象的属性(properties). 当进入全局代码执行上下文,全局对象(Global object,如浏览器中的 window)被当做其 Variable object 对象.这正是为什么在全局范围内声明的变量或函数会成为全局对象的属性的原因:

[javascript] view plaincopy

  1. /* 当在全局范围时, `this` 指向的就是 global object */
  2. var GLOBAL_OBJECT = this;  
  3. var foo = 1;  
  4. GLOBAL_OBJECT.foo; // 1
  5. foo === GLOBAL_OBJECT.foo; // true
  6. function bar(){}  
  7. typeof GLOBAL_OBJECT.bar; // "function"
  8. GLOBAL_OBJECT.bar === bar; // true

OK,全局变量成为了全局对象的属性,但局部变量(在函数内部声明)又是如何处理的呢?实际上是非常相似的: 他们成为 Variable 对象的属性(properties).唯一的区别是,当在函数代码中时,Variable 对象并不是全局对象,而是一个称为Activation object 的对象.每次进入函数的执行上下文时都会创建一个 Activation object 对象. 不仅函数内部声明的变量和函数会成为 Activation object 对象的属性;而且函数的每个参数(对应到相应的参数名)、以及一个特殊的 Arguments 对象(名为 arguments )也会成为 Activation object 对象的属性.注意, Activation 对象只是一种内部的机制,程序永远不可能真正地访问(引用)到这个对象.

[javascript] view plaincopy

  1. (function(foo){  
  2. var bar = 2;  
  3. function baz(){}  
  4. /*
  5.     以抽象虚拟的说法,
  6.     专有的 `arguments` 对象成为对应函数 Activation 对象的一个属性(property):
  7.     ACTIVATION_OBJECT.arguments; // Arguments 对象
  8.   ...当然,参数 `foo`也是一样的道理:
  9.     ACTIVATION_OBJECT.foo; // 1
  10.   ...同时,局部变量 `bar`同样如此:
  11.     ACTIVATION_OBJECT.bar; // 2
  12.   ...定义的局部函数也是如此:
  13.     typeof ACTIVATION_OBJECT.baz; // "function"
  14.   */
  15. })(1);  

最后,在 Eval 代码内声明的变量被创建为调用上下文 Variable object 对象的属性.简言之,Eval代码在哪里被调用,内部的变量就相当于在哪里被声明:

[javascript] view plaincopy

  1. var GLOBAL_OBJECT = this;  
  2. /* 此处eval内的`foo` 被创建为 调用上下文 Variable 对象的属性,
  3.    在此处上下文对象即为全局对象 Global object */
  4. eval('var foo = 1;');  
  5. GLOBAL_OBJECT.foo; // 1
  6. (function(){  
  7. /* `bar` 被创建为 调用上下文 Variable object 对象的一个属性,
  8.       此处 Variable object 对象是包含的function的一个 Activation 对象 */
  9.     eval('var bar = 1;');  
  10. /*
  11.       以一种虚拟抽象的角度看,
  12.       ACTIVATION_OBJECT.bar; // 1
  13.     */
  14. })();  

1.4 Property 属性

该说的基本上都说完了.现在对于变量是如何处理的已经很清楚(他们变成了 properties 属性),剩下唯一需要理解的概念是 property 属性.每个 property 都可以拥有下面列举的0到多个特性 ——ReadOnly,DontEnum,DontDelete以及 Internal.你可以把它们当做标志位 —— 每一个 property 中可以存在也可以不存在的 attribute .为了下面的讨论,我们只对 DontDelete 感兴趣. 当声明的变量和函数成为 Variable object 对象的properties —— 可能是 Activation object 对象(对于 Function code 来说),或全局对象(对全局代码来说),这些 properties  创建时就被赋予了 DontDelete attribute属性.然而,任何显式(或隐式)对指定 property 属性的赋值所创建的 property 属性则没有DontDelete属性.这就是为什么我们可以删除某些 properties属性,而其他的不能删除的本质原因:

[javascript] view plaincopy

  1. var GLOBAL_OBJECT = this;  
  2. /*  `foo` 是 Global object 的一个 property.
  3.     是通过变量声明创建的,因此拥有 DontDelete 标志.
  4.     这就是为什么不能被删除的原因. */
  5. var foo = 1;  
  6. delete foo; // false
  7. typeof foo; // "number"
  8. /*  `bar` 是 Global object 的一个 property.
  9.     是通过 function 声明创建的,因此拥有 DontDelete 标志.
  10.     这也是为什么不能被删除的原因. */
  11. function bar(){}  
  12. delete bar; // false
  13. typeof bar; // "function"
  14. /*  `baz` 同样是 Global object 的一个 property.
  15.     但是通过 property 赋值创建的,However,因此没有 DontDelete 标志.
  16.     这是为什么可以被删除的原因. */
  17. GLOBAL_OBJECT.baz = 'blah';  
  18. delete GLOBAL_OBJECT.baz; // true
  19. typeof GLOBAL_OBJECT.baz; // "undefined"

1.5 内置的和不可删除的

DontDelete指的是: 在 property上的一个特殊attribute,用于控制是否可以删除此property.注意,一些内置对象的属性被指定为 DontDelete,所以不能被删除.特殊的arguments变量(或者,正如我们刚刚介绍的, Activation 对象的属性 等) 拥有 DontDelete 标志.所有 function实例的length 属性(形参个数)也具有 DontDelete 标志:

[javascript] view plaincopy

  1. (function(){  
  2. /* 不能删除 `arguments`,因为其拥有 DontDelete */
  3. delete arguments; // false
  4. typeof arguments; // "object"
  5. /* 不能删除 function 的 `length`; it also has DontDelete */
  6. function f(){}  
  7. delete f.length; // false
  8. typeof f.length; // "number"
  9.   })();  

对应于 function参数的 properties 创建时也被赋予了 DontDelete 标志,所以也不能被删除:

[javascript] view plaincopy

  1. (function(foo, bar){  
  2. delete foo; // false
  3.   foo; // 1
  4. delete bar; // false
  5.   bar; // 'blah'
  6. })(1, 'blah');  

1.6 未声明赋值

也许还记得,未声明的赋值(没有 var 定义)将在全局对象上创建一个属性.除非是在查找到全局范围对象之前就已经在作用域链上发现了一个同名的属性.现在我们知道了 property 赋值和变量声明 的区别 —— 后者被设置了 DontDelete标志,而前一个没有被设置 —— 为什么未声明的赋值 创建的property是可删除的现在就很明显了(没设置 DontDelete标志):

[javascript] view plaincopy

  1. var GLOBAL_OBJECT = this;  
  2. /* 通过变量声明 而创建的全局 property; 拥有 DontDelete 标志*/
  3. var foo = 1;  
  4. /* 通过未声明赋值创建的 global property; 没有 DontDelete 标志*/
  5. bar = 2;  
  6. delete foo; // false
  7. typeof foo; // "number"
  8. delete bar; // true
  9. typeof bar; // "undefined"

注意, attributes 是在 property 创建期间决定的(即便什么也没有设置).以后的赋值不会修改已存在property的attributes.理解这个区别是很重要的.

[javascript] view plaincopy

  1. /* `foo` is created as a property with DontDelete */
  2. function foo(){}  
  3. /* Later assignments do not modify attributes. DontDelete is still there! */
  4. foo = 1;  
  5. delete foo; // false
  6. typeof foo; // "number"
  7. /* But assigning to a property that doesn't exist,
  8.    creates that property with empty attributes (因此没有 DontDelete 标志) */
  9. this.bar = 1;  
  10. delete bar; // true
  11. typeof bar; // "undefined"

2. Firebug 的困惑

那 Firebug 中究竟是怎么回事?为什么在console中声明的变量可以被删除,和我们刚刚学到的相反呢?嗯,正如我之前所说,Eval代码在变成变量声明时有一个特殊的行为.在Eval代码内声明的变量实际上是没有 DontDelete 标志的:

[javascript] view plaincopy

  1. eval('var foo = 1;');  
  2. foo; // 1
  3. delete foo; // true
  4. typeof foo; // "undefined"

同样,当在函数内部调用Eval代码时:

[javascript] view plaincopy

  1. (function(){  
  2.   eval('var foo = 1;');  
  3.   foo; // 1
  4. delete foo; // true
  5. typeof foo; // "undefined"
  6. })();  

这就是Firebug的反常行为的要点.似乎控制台中所有的文本都被当做 Eval代码 解析和执行,而不是全局代码或函数代码.显然,任何声明的变量最终都没有 DontDelete 标志,因此可以很容易地被删除. 请留意在 常规的全局代码和Firebug控制台中的这些差异.

2.1 通过eval删除变量

这是eval 一个有趣的行为,加上ECMAScript的另一个方面可以技术上允许我们删除non-deletable的 properties.在相同的执行上下文中,关于函数的声明会覆盖同名的变量(原理是在context中,变量声明被提前到整个context最前面,function声明也被提前,但应该是function声明被 提到变量声明之后,所以...):

[javascript] view plaincopy

  1. function x(){ }  
  2. var x; // 只声明
  3. typeof x; // "function"
  4. function a(){ }  
  5. var a = 6; // 声明 + 赋值
  6. typeof a; // "number"

注意,函数声明优先并覆盖same-named(同名的)变量(或者,换句话说,Variable 对象的同一个property ).这是因为 function 声明在变量声明之后 实例化,并允许覆盖它们.函数声明不仅取代 property的前一个值,它也取代该 property 的特性(attributes).如果我们通过eval声明 function,该函数也应该用自己的替换该 property的attributes .由于在eval中声明的变量所创建的 properties  没有DontDelete标志,实例化这个新函数本质上应该移除property 现有的DontDelete属性,以使 移除property 可以被删除(当然,肯定也要让其指向新创建的函数).

[javascript] view plaincopy

  1. var x = 1;  
  2. /* 不能删除, `x` has DontDelete */
  3. delete x; // false
  4. typeof x; // "number"
  5. eval('function x(){}');  
  6. /* `x` property 现在指向 function, 并且没有  DontDelete 标志*/
  7. typeof x; // "function"
  8. delete x; // 按理说应该是 `true`
  9. typeof x; // 按理说应该是 "undefined"

不幸的是,在我测试的所有实现环境中,这种欺骗都不会生效.我可能在这里说错了什么东西,或这种行为太晦涩了,以至于实现者不去关注.

3. 浏览器兼容性

理解事物运行的原理是很有用的,但认识实际运行环境更重要.浏览器在处理 变量/属性 的 创建/删除 时遵循标准吗?在大多数情况下,是这样的. 我编写了 一个简单的测试脚本 来测试 delete 操作符对全局代码,Function 代码和Eval代码的操作 是否遵循规范.测试脚本检查- delete操作符的返回值,以及当属性应该被删除时是否被删除. delete 的返回值不如其实际结果重要.它不是很重要如果删除返回true,而不是false,但拥有DontDelete特性的properties未被删除倒是很重要的,反之亦然. 现代浏览器的兼容性通常是非常好的.除了我 前面提到过特别的 eval ,以下浏览器对测试套件完全通过:Opera 7.54 +,Firefox 1.0 +,Safari 3.1.2 +,Chrome 4 +. Safari 2.x 和3.0.4 在删除 function arguments 时有问题; 这些properties 创建时似乎没有赋予DontDelete特性,所以可以被删除. Safari 2.x 甚至有更多的问题 —— 删除非引用(例如delete 1;)将会抛出错误; function 声明会创建 可删除的 properties (但奇怪的是,variable 声明就不能删除); 而在eval中 variable声明又变成non-deletable(但函数声明又不是这样). 类似于 Safari,Konqueror(3.5,而不是4.3)在删除非引用时(例如 delete 1;)会抛出错误,还会错误地允许删除函数 arguments.

3.1 Gecko引擎的DontDelete缺陷

Gecko 1.8.x的浏览器,Firefox 2.x,Camino 1.x,Seamonkey 1.x,等等 —— 表现出一个有趣的bug,当显式地给一个 property 赋值后,会移除其DontDelete 特性,即使该property 是通过变量或函数声明 而创建的:

[javascript] view plaincopy

  1. function foo(){}  
  2. delete foo; // false (符合预期)
  3. typeof foo; // "function" (符合预期)
  4. /* 现在显示地给一个 property 赋值*/
  5. this.foo = 1; // 错误地清除了 DontDelete 特性
  6. delete foo; // true
  7. typeof foo; // "undefined"
  8. /* 注意当隐式地给 property 赋值时并不会发生 */
  9. function bar(){}  
  10.  bar = 1;  
  11. delete bar; // false
  12. typeof bar; // "number" (赋值操作替换了 property)

令人惊讶的是,Internet Explorer 5.5 - 8 完全通过了测试套件的测试,除了删除非引用(例如 detele 1;)会抛出错误以外(就像在更古老的Safari中一样).但在IE中实际上有更严重的bugs,这些bug不会立即显示出来.这些缺陷是与全局对象有关的.

4. IE的Bugs

整篇文章只有Internet Explorer中有BUG吗?多么的意想不到啊! 在IE(至少IE6 - IE8),以下表达式将会抛出错误(在Global code中执行时):

[javascript] view plaincopy

  1. this.x = 1;  
  2. delete x; // TypeError: Object doesn't support this action

下面的也是一样,但是抛出了不同的异常,事情非常的有趣:

[javascript] view plaincopy

  1. var x = 1;  
  2. delete this.x; // TypeError: Cannot delete 'this.x'

在IE中,在全局代码声明的变量好像不会创建为全局对象上的属性.通过赋值创建属性( this.x = 1; ),然后通过 delete x;删除它将会抛出错误. 通过声明创建属性(var x = 1;),然后使用delete this.x; 删除它却会抛出另一个错误. 但这还不是全部.通过显式赋值创建的属性在删除时总会抛出错误.不仅此处有一个错误,而且创建的属性似乎还被设置了DontDelete标志,这当然是不应该的:

[javascript] view plaincopy

  1. this.x = 1;  
  2. delete this.x; // TypeError: Object doesn't support this action
  3. typeof x; // "number" (still exists, wasn't deleted as it should have been!)
  4. delete x; // TypeError: Object doesn't support this action
  5. typeof x; // "number" (wasn't deleted again)

现在,与人们的预料相反,未声明的赋值(应该在全局对象上创建一个property )在IE中却创建可删除的properties:

[javascript] view plaincopy

  1. x = 1;  
  2. delete x; // true
  3. typeof x; // "undefined"

但如果你想在全局代码中通过 this引用来删除这个property (delete this.x),那么一个你熟悉的错误就会冒出来:

[javascript] view plaincopy

  1. x = 1;  
  2. delete this.x; // TypeError: Cannot delete 'this.x'

如果我们对这种行为进行推理,会发现在全局代码执行 delete this.x; 永远不会成功.当 property 是通过显式赋值创建的(this.x = 1;),delete将会抛出一个错误;当 property 是通过未声明赋值创建的(x = 1)或通过变量声明创建(var x = 1),delete将会抛出另一个错误.

换个角度来说, delete x; 只有当 property 是通过显式赋值创建时才抛出错误——this.x = 1;.如果 property 是通过变量声明创建的(var x = 1;),删除只是简单地从不执行,并且delete 正确地返回false. 如果property 是通过未声明赋值创建的(x = 1),删除按预期方式运行.

我是 在9月份反馈这个问题 的,Garrett Smith建议在IE中 “The global variable object被实现为一个 JScript 对象,并且 global object 是由 host 来实现的".Garrett 建议参考Eric Lippert的博客 .我们可以通过执行一些测试来证实了这一理论. 注意, this 和 window 指向同一个对象(我们可以采用 === 操作符来测试),但是 Variable 对象(声明函数时的那个) 与 this 引用不同.

[javascript] view plaincopy

  1. /* in Global code */
  2. function getBase(){ return this; }  
  3. getBase() === this.getBase(); // false
  4. this.getBase() === this.getBase(); // true
  5. window.getBase() === this.getBase(); // true
  6. window.getBase() === getBase(); // false

5. 误解

对于事物以及其运行方式和原理的美丽的理解是不能被低估的.我看到网上有一些没有真正理解 delete 操作而造成的一些误解.例如, 这是在 Stackoverflow 的一个答案(高得惊人的评级),自信地解释 “当删除的目标不是一个对象的 property 时,delete 应该是无操作”.现在我们理解了 delete行为的核心,就很清楚地知道,这个答案是相当不准确的.delete不区分变量和属性(事实上,对于delete来说,这些都是引用),并且只关心DontDelete 特性(property 要存在). 看看误解彼此反弹也是有趣的,在同一 thread 中有人首先建议删除变量(当然是行不通的,除非是在 eval 中声明),然后另一个人提供了 一份错误的修正 可以如何在全局代码中删除变量而不是Function 代码中. 请小心使用Web上对 Javascript 的解释,在理想的情况下,应该去寻求理解问题的核心;)

6. 'delete' 与 host 对象(宿主对象?)

对 delete 的一个运算法则大概是这样的:

  1. 如果操作数不是一个引用,返回true
  2. 如果对象没有这个name的direct property,返回true(正如我们现在所知道的,对象可以是Activation 对象或 Global 对象)
  3. 如果 property 存在,但具有DontDelete特性,返回 false
  4. 其他情况,删除 property 并返回 true

然而, delete 操作符与 host 对象的行为可能是相当难以预测的.其实并没有哪里出错: (规范中允许) host 对象对 operations 实现任何形式的行为,如 read (内部[[Get]]方法), write (内部[[Put]]方法)或 delete (内部[[Delete ]]方法),以及其他的一些操作. 对于自定义[[Delete]]的行为正是 host 对象如此混乱的原因. 我们已经看到一些IE的古怪,删除某些特定对象(这显然是实现为 host 对象)会抛出错误. 某些版本的Firefox在尝试删除 window.location 时也会抛出.当涉及到 host 对象时,你也不能相信 delete 的返回值,看看在Firefox中会发生什么:

[javascript] view plaincopy

  1. /* "alert" 是 `window` 的一个 direct property 
  2. 如果我们采信 `hasOwnProperty` 的话) */
  3. window.hasOwnProperty('alert'); // true
  4. delete window.alert; // true
  5. typeof window.alert; // "function"

删除 window.alert 返回 true ,即使对该 property 没有什么理由导致这个结果. 它解析为一个引用(所以在第一部不能返回true). 这是window的一个 direct property(所以第二步不能返回true;).delete操作返回true的唯一途径 就是到达运算法则的第四步,并且真正地删除了一个属性. 是的,在这里 property 从来没有被删除. 这个故事的寓意就是绝不能信任 host对象. 作为一根bonus,下面是IE中 delete 行为的一个怪异的 case :

[javascript] view plaincopy

  1. var element = document.createElement('div')  
  2. delete element.onclick; // throws "Object doesn't support this action"
  3. document.body.x = 1;  
  4. delete document.body.x; // throws "Object doesn't support this action"
  5. // in IE8
  6. delete XMLHttpRequest.prototype.open; // throws "Object doesn't support this action"

7. EcmaScript5严格模式

那么 ECMAScript5 的严格模式带来了什么? 介绍了一些限制. 当使用 delete 操作符来删除 变量,函数参数或函数标识符 的直接引用时,将会抛出 SyntaxError语法错误.此外,如果 property 内部[[Configurable]]== false时,会抛出 TypeError:

[javascript] view plaincopy

  1. (function(foo){  
  2. "use strict"; // 在此函数内使用 strict mode
  3. var bar;  
  4. function baz(){}  
  5. delete foo; // SyntaxError (when deleting argument)
  6. delete bar; // SyntaxError (when deleting variable)
  7. delete baz; // SyntaxError (when deleting variable created with function declaration)
  8. /* 函数实例的 `length`属性拥有 { [[Configurable]] : false } */
  9. delete (function(){}).length; // TypeError
  10. })();  

此外,删除未声明的变量(或者换句话说,未解决的引用)也会抛出语法错误:

[javascript] view plaincopy

  1. "use strict";  
  2. delete i_dont_exist; // SyntaxError

这有点类似于在严格模式下的未声明赋值的行为(除了抛出的是 ReferenceError 而不是SyntaxError以外):

[javascript] view plaincopy

  1. "use strict";  
  2. i_dont_exist = 1; // ReferenceError

你现在明白,所有这些限制的意义,给删除变量,函数声明和参数 导致了多少混乱的原因. 在严格模式下,不会默默地忽略删除,而是需要更多 激进的和 基础的(descriptive)措施.

8. 小结

这篇文章非常的长,所以我不再去讨论诸如使用 delete 来移除数组项(array items)及其含义.你可以参考 MOZILLA对于delete的详细说明(或自己搜索与实验). 下面是关于Javascript中删除机制的一个简短摘要:

  1. 变量和函数声明都是 Activation 或 Global 对象的 properties.
  2. Properties 拥有 attributes特性,其中一个是 DontDelete,决定 property 是否可以被删除.
  3. 在全局和Function代码中声明的变量和函数,创建的properties 总是被赋予 DontDelete 标志.
  4. 函数 arguments 也是 Activation 对象的properties ,并在创建时赋予 DontDelete 标志.
  5. 在Eval代码中声明的变量和函数在创建 properties 时没有DontDelete标志.
  6. 新指定的properties 创建时标志位是 empty 的(所以没有DontDelete标志).
  7. Host 对象自己决定是否允许被删除.

如果你想了解更多信息,请参阅:  ECMA-262规范_en.pdf

我希望你喜欢这个概述并学到一些新东西.一如既往地欢迎任何问题,建议和修正.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏企鹅号快讯

改善Python程序的一些建议

第一章:引论 建议1、理解Pythonic概念----详见Python中的《Python之禅》 建议2、编写Pythonic代码 (1)避免不规范代码,比如只用...

2007
来自专栏刘望舒

设计模式(九)模版方法模式

1.模版方法模式简介 模版方法模式介绍 在软件开发中,有时会遇到类似的情况,某个方法的实现需要多个步骤,其中有些步骤是固定的,而有些步骤并不固定,存在可变性。为...

2056
来自专栏信数据得永生

JavaScript 编程精解 中文第三版 二、程序结构

42315
来自专栏web前端教室

js不好学并不是因为它难,而是因为它容易混淆

这段时间我主讲的前端零基础课,让我感觉js这东西是越来越简单了。你当然可以说我这是越讲课越熟。确实是有这部分因素,但更主要的是,我发现js中虽然有许多的概念各不...

2667
来自专栏企鹅号快讯

不想再被鄙视?那就看进来!一文搞懂 Python 2 字符编码

来源:xybaby自荐投稿 www.cnblogs.com/xybaby/p/7814299.html 程序员都自视清高,觉得自己是创造者,经常鄙视不太懂技术的...

3436
来自专栏游戏杂谈

Objective-C 入门笔记

既然有头文件,所以很多人会拿它与C++进行类比,它最重要的三个特性是:Message、Category、Protocol,与C++类比,二者之间的不同:

1203
来自专栏达观数据

达观数据前端分享:理解 JavaScript 中的对象的属性

在达观数据的前端工作中,对象的属性是经常接触和使用的,正好最近重温了一下《JavaScript 高级程序设计》,把书中理解对象属性的部分整理一下与大家分享。 ...

3429
来自专栏智能算法

Python学习(一)---- Python基础必备

https://blog.csdn.net/fgf00/article/details/52061971

5334
来自专栏佳爷的后花媛

java学习要点

作为一个程序员,在找工作的过程中,都会遇到笔试,而很多笔试里面都包括java,尤其是作为一个Android开发工程师,java是必备技能之一.所以为了笔试过程中...

3455
来自专栏老码农专栏

原 荐 OSGL 工具库 - 类型转换的艺术

2013

扫码关注云+社区

领取腾讯云代金券