您的位置:首页 > Web前端 > JavaScript

js的delete运算符深入解析

2016-07-28 18:25 447 查看
摘自:kangax 的一篇文章 "Understanding Delete(http://perfectionkills.com/understanding-delete/#comment-166660)";

delete就是删除某些东西,更具体的说,它会删除对象的属性。

1、delete运算符一般不会删除普通变量

var benjamin = "http://www.zuojj.com";

delete benjamin;

console.log(benjamin); //Outputs: "http://www.zuojj.com"

但是,它可以删除“全局变量”,因为它们事实上是全局对象(浏览器中是window)对象的属性。

// Because var isn't used, this is a property of window

benjamin = "zuojj";

delete window.benjamin;

// ReferenceError: benjamin is not defined

console.log(benjamin);

delete运算符也有一个返回值,如果删除一个属性成功了,返回true,如果不能删除属性,因为该属性是不可写,将返回false,或者如果在严格模式下会抛出一个错误。

需要记住的是,删除并没有破坏属性的值,仅仅属性本身,看下面的例子:

var name = "zuojj",

benjamin = {};

benjamin.name = name;

delete benjamin.name;

console.log(name); //Outputs: "zuojj"

console.log(benjamin.name); //Outputs: "undefined",删除benjamin.name并不影响name

================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下正确输出.(你可以快速测试一下) 仿佛在FireBug下有它自己的删除规则. 这...给我干蒙了. 到底是怎么回事? 我们来讨论一下.

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

让我们带着疑问继续往下看:(I’ll try to explain this in details)

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

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

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


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

§ 原理

  为什么它能删除对象的属性:

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


  变量却不能, like this:

var x = 1;
delete x; // false
x; // 1


  函数也不允许, like this:

function x(){}
delete x; // false
typeof x; // "function"


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

要明白理解这些, 要需要进一步理解变量实例概念、属性的特性。(有限的是在JavaScript相关书籍中涉及的知识还是比较少的)以下就要详细的介绍.

(如果你不关心这些东西为什么工作方式是这样的,那就skip this chapter)

§ Type of Code (代码级别) [ps:代码级别是出于自己的理解]

  在ECMAScript中有3种作用域: Global code(全局作用域), Function code(函数作用域), Eval code(Eval作用域) 以下对三种级别的描述.

  Global code : 当一段文本做为一个程序的时候, 它是在全局作用域下执行的. 在浏览器环境中通常写在SCRIPT标签下的内容会被解析, 因为也算是全局作用域

  Function code : 任何东西在function里是会随着function执行并执行.很明显这是属于函数作用域, 在浏览器中事件属性通过也会被当作函数作用域.(e.g <p onclick=""/>)

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

§ Execution context(执行上下文)

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

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

  就你像我们看到的那样, 执行上下文逻辑上属于一个 stack (栈) 首先它可能是执行在全局作用域下的, 它拥有自己的上下文, 在这段代码里, 可能还会调用一个function, 这个function也会有自己的上下文, 在这个function里有可能还会调用一个function, function还可以递归调用, 以此类推.


§ Activation object / Variable object

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

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

/* remember that `this` refers to global object when in global scope */
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 Object)变量对象的属性. 只是在作为Function code的时候有所不同, 一个变量对象不是一个全局对象.
但它会调用一个"Activation object"(激活对象), 每一次给函数分配上下文的时候Activation object将会被创建.

  不仅仅是变量和函数的声明会成为Activation object的属性, 函数的形参(形式参数:对应实际参数)和特殊对象Arguments object 注意 Activation object是一种内部机制, 是永远不可能访问的程序代码.

(function(foo){

var bar = 2;
function baz(){}

/*
In abstract terms,

Special `arguments` object becomes a property of containing function's Activation object:
ACTIVATION_OBJECT.arguments; // Arguments object

...as well as argument `foo`:
ACTIVATION_OBJECT.foo; // 1

...as well as variable `bar`:
ACTIVATION_OBJECT.bar; // 2

...as well as function declared locally:
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;');

/*
In abstract terms,
ACTIVATION_OBJECT.bar; // 1
*/

})();


§ Property
attributes(属性特性)

  我们就快要明白了, 现在我们清楚的明白变量到底发生了什么(它们属性之间的变换), 剩下 Property attributes了.每个属性都会存在0个或多个属性包括(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"


§ Build-ins
and DontDelete(嵌入式和不可删除)

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

(function(){

/* 不能删除 'arguments', 它是不可删除的*/
delete arguments; // false
typeof arguments; // "object"

/* 不能删除function的lenth属性, 它也是不可删除的 */
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');


§ Undeclared
assignments (未声明的任务)

  未声明的任务创建一个全局对象的属性. 除非在全局对象之前你能找到这个属性是属性哪个作用域链的. 现在我们清楚,属性任务和变量声明之间的不同, 后者是是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"


§ FireBug
confusion (奇异的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的不同.

  

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

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

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


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

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

var x = 1;

/* Can't delete, `x` has DontDelete */

delete x; // false
typeof x; // "number"

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

/* `x` property now references function, and should have no DontDelete */

typeof x; // "function"
delete x; // should be `true`
typeof x; // should be "undefined"


不幸的事, 我尝试各种不能工作的场景, 有可能我会有所疏漏.

§ Browsers
compliance (浏览器兼容性)

   学习这些东西的工作原理是很实用的, 实践至上. 在浏览器兼容上会存在多在的差异.作者做了很多的浏览器测试, 最主要的是属性中含有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 (as expected)
typeof foo; // "function" (as expected)

/* now assign to a property explicitly */

this.foo = 1; // erroneously clears DontDelete attribute
delete foo; // true
typeof foo; // "undefined"

/* note that this doesn't happen when assigning property implicitly */

function bar(){}
bar = 1;
delete bar; // false
typeof bar; // "number" (although assignment replaced property)


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

IE BUGS(IE bug)

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

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

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


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

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


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

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

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)


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

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


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

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


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

Misconceptions (歧义) 

/*...*/



§ '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" is a direct property of `window` (if we were to believe `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 它还是没有被删除.

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

§ ES5
strict mode

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

(function(foo){

"use strict"; // enable strict mode within this function

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` 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这些限制, 反向理解来学习更深入的知识.

§ Summary

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

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

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

属性里有DontDelete特性的表示不可删除属性.

变量和函数声明只要是在"全局代码块"/"函数级代码块"中都会有 —— DontDelete.

Functions的参数也是属于激活对象的属性, 所以也有 —— DontDelete.

变量和函数声明在Eval代码块中的, 都不会创建 —— DontDelete.

为对象加入新的属性(没有任何特性), 也是不会创建 —— DontDelete.

不管他们想怎样, 宿主对象对删除是会返回状态的.

================Enein翻译===================
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jsdelete