关于Javascript中的delete运算符

众所周知delete是删除对象中的属性,但如果不深入了解delete的真正使用在项目中会出现非常严重的问题!下面是翻译kangax 的一篇文章 "Understanding Delete";

 

目录:

1.原理

2.Type of Code (代码级别)

3.Execution context(执行上下文)

4.Activation object / Variable object

5.Property attributes(属性特性)

6.Build-ins and DontDelete(嵌入式/不可删除)

7.Undeclared assignments (未声明任务)

8.FireBug confusion (奇异的FireBug)

9.Deleting variables via eval(通过eval删除变量)

10.Browsers compliance(浏览器兼容性)

11.'delete' and host objects

12.ES5 strict mode

13.Summary

 

================Enein翻译===================

先上例子:

var sum = function(a, b) {return a + b;};
var add = sum;
delete sum;  //true
typeof sum; //"undefined"

这段代码你能看出什么问题?

当然,这个问题很明显:"delete sum" 是不会成功的。delete 返回的值不应该是 "true" , "typeof sum" 返回的结果也不是"undefined",造成问题的原因是在JavaScript中"delete 是不可以删除变量的"。

但是,上面的代码在FireBug Console中执行的结果却显示sum可以被删除。到底是怎么回事? 我们来讨论一下。

要想知道答案,我们首先要知道 "delete" 操作符在JavaScript中的实际是怎样工作的?本文主要从3个方向:(1)什么情况能正确删除?(2)什么时候不能删除?(3)为什么?

我们来看一个FireBug的古怪行为。并了解其实这是正常的。

我们将深入了解下 "声明变量","函数","加入属性"是怎么工作的并在适当的时候删除它们。我们还会看一下浏览器的兼容性以及其一些常见的Bugs, ECMAScript 5 strict mode 和如何改变delete 操作符的行为

注释: 在这里我将使用JavaScript和ECMAScript(这是真正意义上的ECMAScript除非有明确声明为Mozilla's ECMAScript扩展)

PS:这一段是作者对Mozilla MSNMSDN 上的两篇文章发表的个人看法(他会认为practically useless)这里不做翻译有兴趣的同学可以点其链接自行查看分析。

 

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"

注意:当属性不能被删除的时候将返回false

要明白理解这些, 要需要进一步理解变量实例概念、属性的特性。

 

2.代码类型

在ECMAScript中有3种作用域: Global code(全局作用域), Function code(函数作用域), Eval code(Eval作用域) 。

Global code : 当一段文本作为在全局作用域下执行的程序。 在浏览器环境中,Script标签下的内容通常会被看做一个程序, 因此具有全局作用域。

Function code : 在Function执行的代码很明显属于函数作用域, 在浏览器中事件属性通过也会被当作函数作用域。(e.g <p onclick=""/>)

Eval code : 最后,在eval函数体里的代码就是 Eval作用域。很快我们就会看到为什么这个类型是特殊的。

 

3.Execution context(执行上下文)

当ECMAScript的代码执行的时候, 它就一直在某一个执行上下文中, “Execution context 是一个抽象的实体” 它会使我们明白作用域和变量实例化的过程。 对以上3种类型的范围, 它们就是一个执行上下文。

当function被执行的时候, 这个实体的上下文就是"Function code";当代码是在Global code下被执行的时候, 那么它就是 "Global code" 等等。

就像我们看到的那样,逻辑上执行上下文创建一个 stack (栈)。 首先,它可能是拥有自己执行上下文的Global Code 或 Function Code,在这个function里有可能还会调用另一个function, function还可以递归调用, 以此类推。

 

4.Activation object / Variable object

每个执行上下文都会和一个变量对象("Variable Object")相关联, 和执行上下文类型类似, 变量对象也是一个抽象的实体。 通过一种机制来描述变量初始化过程, 现在, 我们感兴趣的是变量和函数声明的时候实际上是作为变量对象的属性被加入的。

当这个实体的执行上下文为全局作用域的时候, 那么这个全局的对象会当做一个变量对象。这也就说明了:为什么在全局对象中声明的变量和函数时候会变成全局对象的属性了。

/* 注意在全局对象中this指代Global Object,在浏览器中即为window */

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, 所以全局变量会变成全局对象的属性,但对于局部变量它发生了什么, 在函数体内他们是怎么声明的, 很简单、它们变成了变量对象的属性。 只是在作为Function code的时候有所不同, 一个变量对象不是一个全局对象,它会调用一个"激活对象"(Activation object), 每一次给函数分配上下文的时候激活对象将会被创建。

不仅是变量和函数声明会成为激活对象的属性, 函数的形参和特殊的Arguments object也是如此。注意:Activation object是一种内部机制, 是永远不可能访问的程序代码。

(function(foo){
var bar = 2;
function baz(){}
/* 在抽象概念中,特殊的`arguments`对象变成了包含函数的激活对象的一个属性 ACTIVATION_OBJECT.arguments; // Arguments object
参数foo也一样 ACTIVATION_OBJECT.foo; // 1
变量bar也一样 ACTIVATION_OBJECT.bar; // 2
逻辑上声明的函数也是一样 typeof ACTIVATION_OBJECT.baz; // "function" */
})(1);

最后, 在Eval code中声明的变量被创建为上下文中变量对象的属性,Eval code一般使用如下代码中执行上下文的变量对象。

var GLOBAL_OBJECT =this;

//'foo' 被创建为一个变量对象调用上下文的属性, 在这个案例中它是全局对象
eval('var foo = 1;'); GLOBAL_OBJECT.foo; // 1
(function(){ //'bar' 被创建作为变量对象调用时上下文中的属性, 在案例中是function链中的激活对象 eval('var bar = 1;');
/* 在抽象概念中 ACTIVATION_OBJECT.bar; // 1 */
})();

 

5.属性特性

我们就快要明白了, 现在我们清楚的明白变量到底发生了什么(它们变成属性), 剩下需要理解的概念就是属性特性(Property attributes)了。每个属性(property)都会存在0个或多个下面的特性(attributes):ReadOnly(只读)、DontEnum(不可枚举)、DontDelete(不可删除)。这里,我们只讨论DontDelete。

当声明变量和函数变成变量对象、或者激活对象(Function code)、或者全局对象(Global code)的属性时,这些属性被创建并含有DontDelete特性。不管怎样, 一些显式或隐式的属性分配创建不含Dontdelete特性的属性,这也是有些属性可以删除有些不行的根本原因。 

var GLOBAL_OBJECT =this;

//'foo' 是全局对象属性,它通过变量声明创建,所以它存在DontDelete特性,这就是它为什么不会被删除 var foo = 1;
delete foo; // false
typeof foo; // "number"
//'bar' 是一个全局对象的属性,它被创建通过函数声明,所以它存在DontDelete属性,这也就是它为什么也删除不了 function bar(){}
delete bar; // false
typeof bar; // "function"
//‘baz’ 也是全局对象的属性,它的创建是通过分配属性,所以它没有DontDelete特性,是可以删除的 GLOBAL_OBJECT.baz = 'blah';
delete GLOBAL_OBJECT.baz; // true
typeof GLOBAL_OBJECT.baz; // "undefined"

 

6.嵌入式和不可删除

这节我们说的是, 属性的一些特殊特性来控制这些属性可否被删除。注意: 一些内置的属性会被默认指定成DontDelete, 故不能被删除。特殊arguments变量(现在我们知道它是激活对象的属性)有DontDelete。 同样的一些function实例的length属性也存在DontDelete:

(function(){
    //不能删除 'arguments',因为它有DontDelete特性
delete arguments; // false
typeof arguments; // "object"
//不能删除function的lenth属性, 它也是DontDelete特性
    function f(){}
delete f.length; // false
typeof f.length; // "number"
})();

同样, 函数的形参也是有DontDelete,也是不可删除的。

(function(foo, bar){
delete foo; // false foo; // 1

delete bar; // false bar; // 'blah' })(1, 'blah');

 

7.未声明的任务

你可能记得,未声明的任务创建一个全局对象的属性。 除非在全局对象之前你已经在作用域链中找到这个属性。 现在我们清楚,属性分配和变量声明之间的不同, 后者有DontDelete属性, 前者则没有(应该清楚为什么未被声明的会创建不含有DontDelete的属性)。

 var GLOBAL_OBJECT =this;

//创建全局属性通过变量声明; 属性不可删除的
var foo = 1;
//创建全局属性通过未声明的任务,其属性是可删除的 bar = 2;

delete foo; // false
typeof foo; // "number"

delete bar; // true
typeof bar; // "undefined"

注意在属性创建期间, 其属性是被确定的。 后面的任务是不可改变已存在的属性的, 明白这点是很重要的。

//'foo' 作为含有DontDelete的属性被创建
function foo(){}
//之后的任务不能修改其属性, DontDelete还在 foo = 1;
delete foo; // false
typeof foo; // "number"
//但加入属性是新的, 就不含有DontDelete
this.bar = 1;
delete bar; // true
typeof bar; // "undefined"

 

8.奇怪的FireBug

在FireBug中发生了什么? 之前说过在FireBug console中变量的声明是可以删除的。 这违背了我们之前说的所有? 好吧,之前我说过, Eval code的变量声明时有着特殊的行为, 变量声明在Eval code里实际上是创建了没有DontDelete的属性:

 eval('var foo = 1;');
 foo; // 1
delete foo; // true
typeof foo; // "undefined"

同样对于在Function code里调用:

(function(){
    eval('var foo = 1;');
    foo; // 1
delete foo; // true
typeof foo; // "undefined" })();

这就是重点, 所有在Firebug console中执行的代码会被当成是 Eval code来进行解析,所以和console的不同。

 

9.通过eval删除变量

最有意思的是eval的特性, 另一方面ECMAScript能从技术上允许我们去删除不可删除的属性。在同一个上下文中function的声明是可以被同名变量重写的。

function x(){ }
var x;
typeof x; // "function"

注意 function声明优先,重写同名变量(或者, 换句话说, 在变量对象中存在了相同属性)。 这是因为函数声明被实例是在变量声明之后,而且,函数声明允许被覆盖。不仅函数声明替换一个属性之前的值, 它也能替换它的属性特性。

如果我们通过eval来声明函数,那么函数可以替换相应的属性特性, 因为在eval里创建的变量声明没有DontDelete, 以下示例会从本质上删除存在的DontDelete特性。

var x = 1;

//x有DoneDelete特性,不可删除
delete x; // false
typeof x; // "number"
eval('function x(){}'); //`x` 属性现在引用函数,所以应该没有DontDelete特性

typeof x; // "function"
delete x; // 应该是`true`
typeof x; // 应该是"undefined"

不幸的是, 我尝试各种不能环境都没有正常运行,有可能我会有所疏漏。

 

10.浏览器兼容性

学习这些东西的工作原理是很实用的, 实践至上。 在浏览器兼容上会存在多在的差异。作者做了很多的浏览器测试, 最主要的是属性中含有DontDelete是不可删除的,相反则然。

当今浏览器的脾气都是很友好的。 我测试的Opera 7.54+, Firefox 1.0+, Safari 3.1.2+, Chrome 4+浏览器都是可行的。

Safari 2.x and 3.0.4是有问题的在处理function的参数问题上;这些参数被看做没有DontDelete特性, 问题主要是我们可以detele它们。 实际上Safari 2.x存在更多的问题(删除没有引用的变量e.g delete 1)会抛异常, function的声明会创建可删除属性(不包括变量声名), 变量声明在eval中变为不可删除(除了function声明)。

Konqueror (3.5)也同样(删除function参数会报错)

Gecko DontDelete Bug

Gecko 1.8.x browsers — Firefox 2.x, Camino 1.x, Seamonkey 1.x, etc. 显示出一个很有意思的bug “显式地设定一个属性是可以移除DontDelete特性, 即使这个属性通过变量或函数声明被创建”: 

function foo(){}
delete foo; // false (和预测的一样)
typeof foo; // "function" (和预测的一样)
//现在显式的创建一个属性
this.foo = 1; // 错误的清除DontDelete特性
delete foo; // true
typeof foo; // "undefined"
//注意:当隐式的创建属性就不是这样了
function bar(){} bar = 1; delete bar; // false
typeof bar; // "number" (虽然新建的替换了属性)

比较出乎意外的是,IE 5.5 - 8 基本测试都通过了。 只是删除没有引用的会报错(e.g delete 1) , 但其实实际上IE中有更严重的BUG是关于全局对象的。

IE Bugs

这一章主要是说一下Internat Explorer下的BUGS:

在IE5.5-8, 下面的代码会报错(在全局域中执行)

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

这个也一样, 不同的异常, 看起来异常有趣。

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

这好像是在IE中 “在全局域中声明变量是不属于全局对象的属性的” 通过 this.x = 1 声明属性, 使用 delete x 会报错通过变量声明 var x = 1; 通过delete this.x删除也会报错。

但说的也不全部, 显示的创建一个属性在删除的时候是一直会报错的 

this.x = 1;
deletethis.x; // TypeError: Object doesn't support this action
typeof x; // "number" (依然存在,没有如预料中的被删除)

delete x; // TypeError: Object doesn't support this action
typeof x; // "number" (同样未删除)

现在, 相反,在ie中未声明的任务(将会在全局对象上)会创建可删除属性:

x = 1;
delete x; // true
typeof x; // "undefined"

但你要尝试使用全局对象的属性的方式来删除, 则会报错:

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

小结: 只要是 delete this.x 都不会成功。

 

11.'delete' and host objects

简单的推算一下delete如下:

  • 如果这个运算对象没有引用, 返回 true。
  • 如果不是Object的内部属性, 返回 true。
  • 如果Object有属性但有DontDelete特性, 返回 false。
  • 其它移除属性返回 true。

无论如华为, delete操作符在宿主对象上的行为也是不可预知的。这其实是没有问题的,宿主对象是允许(通过规范)去实现各种操作行为,比如 read(内部实现[[Get]]方法), write(内部实现[[Put]]方法), delete(内部实现[[Delete]]方法)。

之前我们已经讨论了IE的差异, delete 某一对象会抛异常, 在一些火狐版本中删除window.location 会抛出的异常, 你不能相信delete 宿主对象的属性的返回值的。 看下面代码在FireFox:

// "alert"是window的直接属性,(假如我们不使用`hasOwnProperty`)
window.hasOwnProperty('alert'); // true
delete window.alert; // true
typeof window.alert; // "function"

删除window.alert返回的是true , 它的解析过程 :

One step : 被解析成一个引用(不会返回true);

two step : 是window的内部属性(不会返回true);

只有在真正 "delete window.alert" 的时候才真正删除了嘛? no 它还是没有被删除。

小结: 从来不要相信宿主对象

 

12.ES5 strict mode

ECMAScript 规范 严格格式下会有很多限制, 在这几种情况下会报语法错误: delete 直接去删除 变量, 函数的参数, 函数定义, 另外, 当属性有内部属性[[Configurable]]== false 是会报 类型错误:

(function(foo){
"use strict"; // enable strict mode within this functionvar 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` of function instances has { [[Configurable]] : false } */delete (function(){}).length; // TypeError
})();

另外, 在删除未声明变量(或未指明的引用)抛出词法错误:

"use strict";
delete i_dont_exist; // SyntaxError

同样的在未指定确定类型的变量在严格模式(strict mode)下也是会报词法错误的:

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

现在我明白了, 在严格模式下的这些限制都是很有用的, ECMAScript strict mode 解决了很多问题, 而不是忽视它们, 从中我们也可以通过ECMAScript这些限制, 反向理解来学习更深入的知识。

 

13.Summary

这篇文章说的太长了, 如果你能静下心来好好看完, 那你将会明白很多, 这里我只是说了一部分。关于Array的delete我这里就不说了, 但希望有兴趣的同学可以自己去尝试(你可以参考MDC for that particular explanation文章)。

这是里简单的做一下在JavaScript中delete的操作:

  • 变量和函数声明属性要么是激活对象, 要么是全局对象

  • 属性里有DontDelete特性的表示不可删除属性。
  • 变量和函数声明只要是在"全局代码块"/"函数级代码块"中都会有 —— DontDelete。

  • Functions的参数也是属于激活对象的属性, 所以也有 —— DontDelete。
  • 变量和函数声明在Eval代码块中的, 都不会创建 —— DontDelete。

  • 为对象加入新的属性(没有任何特性), 也是不会创建 —— DontDelete。
  • 不管他们想怎样, 宿主对象对删除是会返回状态的。

================Enein翻译===================

 

原文地址:http://www.cnblogs.com/enein/archive/2012/08/23/2651312.html

posted @ 2014-11-22 15:27  South Wind  Views(242)  Comments(0)    收藏  举报