最近从新温习JS,对delete操做符一直处于只知其一;不知其二的状态,偶然发现一篇文章,对此做了很是细致深刻的解释,看完有茅塞顿开的感受,不敢独享,大体翻译以下。javascript
原文地址:http://perfectionkills.com/understanding-delete/html
P.S. 做者是PrototypeJS的开发组成员之一java
========分割线========算法
在开始以前,先让咱们看一段代码浏览器
这段代码是Firebug控制台里的实际结果,初看这段代码,你以为有什么问题?但我要说的是,删除sum应该是失败的,同时typeof sum的结果不该该是undefined,由于在Javascript里以这种方式声明的变量是没法被删除的。app
那么问题出在哪里?为了回答这个问题,咱们须要理解delete操做符在各类状况下的实现细节,而后再回过头来看Firebug的这个看似“诡异”的输出。函数
P.S 没有特殊声明的状况下,下文中所提到的Javascript都指的是ECMAScript规范。性能
1. 理论this
delete操做符一般用来删除对象的属性:spa
而不是通常的变量:
或者是函数:
注意delete只有在没法删除的状况下才会返回false。
为了理解这一点,咱们必须解释一下变量初始化以及变量属性的一些基本概念--很不幸的是不多有Javascript的书能讲到这些。若是你只想知其然而不是知其因此然的话,你彻底能够跳过这一节。
代码的类型
在ECMAScript中,有三种可执行代码类型:全局代码、函数代码、eval代码。
1. 当一段代码被当作程序段运行的时候,它是在全局做用域下执行的,也就是全局代码。在浏览器环境下,一般<SCRIPT>元素就是一段全局代码。
2. 全部在function中声明的代码便是函数代码,最多见的是HTML元素的响应事件(<p onclick="...">)。
3. 传入内建的eval函数中的代码段称为eval代码,稍后咱们会看到这种类型的特别性。
执行上下文(Execution Context)
在ECMAScript代码执行的时候,老是会有一个执行的上下文。这是一个比较抽象的概念,但能够帮助咱们理解做用域以及变量初始化的相关过程。对于以上三种代码段类型,都有一个相应的执行上下文,好比函数代码有函数上下文,全局代码有全局上下文,等等。
逻辑上执行上下文相互间能够造成堆栈,在全局代码执行的最开始会有一个全局上下文,当调用一个函数的时候会进入相应函数的上下文,以后又能够再继续调用其余的函数亦或是递归调用本身,这时执行上下文的嵌套相似于函数调用栈。
Activation object / Variable object
每一个执行上下文都和一个Variable object(变量对象)相关联 ,这也是一个抽象的概念,便于咱们理解变量实例化机制:在源代码中声明的变量和方法实际上都是做为属性被加入到与当前上下文相关联的这个对象当中 。
当执行全局代码的时候,Variable object就是一个全局对象,也就是说全部全局变量和函数都是做为这个变量的属性存在。
那么对于在函数中声明的变量呢?状况是相似的,函数中声明的变量也是被当作相应上下文对象的属性,惟一的区别是在函数代码段中,这个对象被称为Activation object(活动对象)。每次进入一个函数调用都会新建一个新的活动对象。
在函数段中,并非只有显式声明的变量和函数会成为活动对象的属性,对于每一个函数中隐式存在的arguments对象(函数的参数列表)也是同样的。注意活动对象实际上是一种内部机制,程序代码是没法访问到的。
最后,在evel代码段中定义的变量都是被加入到当前执行eval的上下文环境对象中,也就是说进入eval代码时并不会新建新的变量对象,而是沿用当前的环境。
变量属性的标记
咱们已经知道声明变量时发生了什么(他们都变成了当前上下文对象的属性),接下来咱们就要看一下属性到底是怎么样一回事。每个变量属性均可以有如下任意多个属性: ReadOnly, DontEnum, DontDelete, Internal。你能够把这些当作标记,标明了变量属性能够持有的某种特性。这里咱们最感兴趣的就是DontDelete标记。
在声明变量或者函数时,他们都变成了当前上下文对象的属性--对于函数代码来讲是活动对象,对于全局代码来讲则是变量对象,而值得注意的是这些属性在建立时都带有DontDelete标记,可是显式或者隐式的赋值语句所产生的属性并不会带有这个标记!这就是为何有一些属性咱们能够删除,但另外一些却不能够:
内建对象与DontDelete
DontDelete就是一个特殊的标记,用来代表某一个属性可否被删除。须要注意的是一些内建的对象是自动持有这个标记的,从而不能被删除,好比函数内的arguments,以及函数的length属性。
函数的传入参数也是同样的:
非声明性赋值
你可能知道,非声明性的赋值语句会产生全局变量,进而变成全局变量对象的属性。因此根据上面的解释,非声明性的赋值所产生的对象是能够被删除的:
须要注意的是属性标记诸如DontDelete是在这个属性被建立的时候 产生的,以后对该属性的任何赋值都不会改变此属性的标记!
2. Firebug的困扰
如今再让咱们回到最开始的问题,为何在Firebug控制台里声明的变量能够被删除呢?这就要牵涉到eval代码段的特殊行为,也就是在eval中声明的变量建立时都不会带有DontDelete标记!
在函数内部也是同样的:
这就是致使Firebug"诡异"行为的罪魁祸首: 在Firebug控制台中的代码最终将经过eval执行,而不是做为全局代码或函数代码。显然地,这样声明出来的变量都不会带DontDelete标记,因此才能被删除!(译者:也不能太信任Firebug啊。)
3. Browsers Compliance
//译者:这一节讲了主流浏览器对一些delete的特殊状况的不一样处理,篇幅所限暂不赘述,有兴趣的能够参看原文。
4. IE bugs
是的,你没有看错,整个这一节都是在讲IE的bug!
在IE6-8中,下面的代码会抛出错误(全局代码):
看起来彷佛在IE里变量声明并不会在全局变量对象里产生相应的属性。还有更有趣的,对于显式赋值的属性老是会在删除时出错,并非真正抛出错误,而是这些属性彷佛都带有DontDelete标记,和咱们的设想相反。
但下面的代码代表,非声明性的赋值产生的属性确实是能够删除的:
不过当你试图经过全局变量对象this来访问x的时候,错误又来了:
总而言之,经过全局this变量去删除属性(delete this.x)总会出错,而直接删除该属性(delete x)时:若是x是经过全局this赋值产生会(this.x=1)致使错误;若是x经过显式声明建立(var x=1)则delete会像咱们预料的那样没法删除并返回false;若是x经过非声明式赋值建立(x=1)则delete能够正常删除。
对于以上的问题,Garrett Smith 的一个解释是"IE的全局变量对象是经过JScript实现,而通常的全局变量是由host实现的。"(ref: Eric Lippert’s blog entry )
咱们能够本身验证一下这个解释,注意this和window看上去是指向同一个对象,可是函数所返回的当前环境的变量对象却和this不一样。
5. 错误的理解
//译者: 大意为网上对Javascript一些行为有各类不一样的解释,有的甚至可能彻底矛盾,不要轻易相信别人的解释,试着本身去寻找问题的核心:)
6. delete与宿主对象(host objects)
delete的大体算法以下:
1. 若是操做对象不是一个引用,返回true
2. 若是当前上下文对象没有此名字的一个直接属性,返回true(上下文对象能够是全局对象或者函数内的活动对象)
3. 若是存在这样一个属性可是有DontDelete标记,返回false
4. 其余状况则删除该属性并返回true
然而有一个例外,即对于宿主对象而言,delete操做的结果是不可预料的。这并不奇怪,由于宿主对象根据不一样浏览器的实现容许有不一样的行为,这其中包括了delete。因此当处理宿主对象时,其结果是不可信的,好比在FF下:
总而言之,任什么时候候都不要相信宿主对象。
7. ES5 strict mode
为了能更早地发现一些应该被发现的问题,ECMAScript 5th edition 提出了strict mode的概念。下面是一个例子:
删除不存在的变量:
对未声明的变量赋值:
能够看出,strict mode采用了更主动而且描述性的方法,而不是简单的忽略无效的删除操做。
8. 总结
若是你但愿对以上这些有更进一步的了解,请参考ECMA规范:ECMA-262 3rd edition specification