但这还不是全部. 通过显式赋值来创建属性事实上总会引起在删除时的抛出异常. 这里不止有错误, 而且所创建的属性似乎会拥有DontDelete特性, 而这当然是不应该具有的.
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"
但是, 如果你是同通过全局代码中的this引用来删除这个属性的话(delete this.x), 就会弹出一个类似的错误.
x = 1;
delete this.x; // TypeError: Cannot delete 'this.x'
如果我们想要归纳一下这种行为的话, 看起来是从全局代码中使用delete this.x来删除变量从来不可能成功. 当问题中的属性通过显式的赋值(this.x = 1)来创建时,delete抛出了一个错误; 当属性是通过未声明的赋值(x = 1)或通过声明(var x = 1)来创建时, delete抛出另外一个错误.
delete x, 另一方面来说, 应当只在属性是通过显式赋值来创建时抛出错误 ——this.x = 1.如果一个属性是通过声明来创建的(var x = 1), 删除操作从来不会发生, 并且删除操作会正确地返回false. 如果一个属性是通过未声明的赋值来创建的(x = 1), 删除操作会像期望地一样工作.
我这9月份又思考了一下这个问题, Garrett Smith建议说在IE下,
"全局变量对象(The global variable object)是实现为一个JScript对象的, 并且全局对象是由host来实现的".
Garrett使用了Eric Lippert's blog entry作为参考.
我们多多少少可以通过实施一些测试来确认这个理论. 注意到this和window看起来是应当指向同一个对象的(如果我们能够信任===操作符的话), 但是变量对象(函数声明所在的那个对象)却与this所指向的不同.
复制代码 代码如下:
/* 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
误解:
理解事物为何以那种方式工作的美是不可低估的. 我在网络上见过一些有关delete操作符的误解. 例如, 这个Stackoverflow上的答案(拥有令人吃惊的高rating), 自信地解释道
"当目标操作数不是一个对象属性时, delete应当是无操作的".
现在既然我们已经理解了delete操作行为的核心, 这个答案的错误也就变得显而易见了. delete并不会去区分因为变量和属性(事实上, 对于delete来说, 它们都是引用类型)并且事实上只关心DontDelete特性(和属性本身是否存在).
看到各种误解互相反驳也是非常有趣的, 在一个相同的话题中一个人首先建议只delete变量(这将不会有效果, 除非它是在eval中声明的), 而另一个人提供了一个错误的纠正说明delete是如何在全局代码中用于删除变量, 而在函数代码中却不行.
对于网络上JavaScript的解释要格外小心, 理想的方法是总去理解问题的本质. ;)
delete和宿主对象(Host Object):
delete的算法大概是这样的:
如果操作数不是引用类型, 则返回true
如果对象没有这个名字的直接属性, 返回true(正如我们所知, 对象可以是活动对象或者全局对象)
如果属性存在但是有DontDelete特性, 返回false
其它情况, 删除属性并且返回true
然而, delete操作符在宿主对象上的行为是难以预测的. 并且这种行为实际上并没有错: (根据标准), 宿主对象是被允许对于像read(内部[[Get]]方法), write(内部[[Put]]方法)和delete(内部[[Delete]]方法)其中几个操作符实现任何行为的. 这种对自定义[[Delete]]行为的宽限就是将宿主对象变得如此混乱的原因.
我们已经见过了一些IE的怪癖, 删除特定的对象(显然是指被实现为宿主对象的)会抛出错误. Firefox的一些版本在删除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"