深入理解Delete(JavaScript)

delete  众所周知是删除对象中的属性. 但如果不深入了解delete的真正使用在项目中会出现非常严重的问题 (:

Following 是翻译  kangax 的一篇文章 “Understanding Delete“;


PS:文章主要分为8部分, 有时间弄点瓜子儿, 整壶茶了解一下. (小编建议直接看原文地址, 以下翻译仅供自己学习使用);

相信大家如果有时间看完会有收获…也希望有大牛能指出其中翻译的不足…

 

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

先上例子:

 

>>> var sum = function(a, b) {return a + b;}>>> var add = sum;>>> delete sumtrue>>> 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 MSNMSDN 上的两篇文章发表的个人看法(他会认为practically useless)这里不做翻译有兴趣的同学可以点其链接自行查看分析.

 

§ 原理

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

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

  变量却不能, like this:

var x = 1;delete x; // falsex; // 1

  函数也不允许, like this:

function x(){}delete x; // falsetypeof 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(执行上下文) 

/* remember that `this` refers to global object when in global scope */var GLOBAL_OBJECT = this;var foo = 1;GLOBAL_OBJECT.foo; // 1foo === GLOBAL_OBJECT.foo; // truefunction bar(){}typeof GLOBAL_OBJECT.bar; // "function"GLOBAL_OBJECT.bar === bar; // true

 

OK, 所以全局变量变成全局对象的属性, 但对于本地变量它发生了什么, 在函数级代码中他们的声明是怎样的, 其实他们与其相类似.

  它们变成了变量对象的属性. 只是在作为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'); // truedelete window.alert; // truetypeof 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翻译===================

本文链接



You must enable javascript to see captcha here!

Copyright © All Rights Reserved · Green Hope Theme by Sivan & schiy · Proudly powered by WordPress

无觅相关文章插件,快速提升流量