在这篇文章中作者从《JavaScript面向对象编程指南》一书中关于 delete 的错误讲起,详细讲述了关于 delete 操作的实现, 局限以及在不同浏览器和插件(这里指 firebug)中的表现。
下面翻译其中的主要部分。
...书中声称
“函数就像一个普通的变量那样——可以拷贝到不同变量,甚至被删除”
并附上了下面的代码片段作为说明:
复制代码 代码如下:
>>> var sum = function(a, b) {return a+b;};
>>> var add = sum;
>>> delete sum;
true
>>> typeof sum;
"undefined"
你能发现片段中的问题吗? 这个问题就是——删除 sum 变量的操作不应该成功; delete 的声明不应该返回 true 而 typeof sum 也不应该返回为 undefined。 因为,javascript 中不能够删除变量,至少不能以这个方式声明删除。
那么这个例子发生了什么? 是打印错误或者玩笑? 应该不是。 这个片段是 firebug 控制台中的一个实际输出,而 Stoyan(上面所说书的作者)应该正是用它做的快速测试。 这仿佛说明了 firebug 有一些不同的删除规则。 正是 firebug 误导了 Stoyan! 那么这里面究竟是怎么回事呢?
为了回答这个问题,我们需要了解 delete 运算符在 Javascript 中是如何工作的: 哪些可以被删除,哪些不能删除以及为什么。 下面我试着解释一下这方面的细节。 我们将通过观察 firebug 的“奇怪”的表现而认识到它实际上完全不“奇怪”; 我们将深入了解那些,当我们声明变量、函数,赋值属性和删除它们时的,隐藏在背后的细节; 我们将看一下浏览器对此的实现和一些有名的 bug; 我们还会讨论到 ECMAScript 版本 5 中的严格模式(strict mode)以及它如何改变 delete 运算符的行为。
我在下面交替使用的 Javascript 和 ECMPScript 一般都指 ECMAScript(除非当明确谈到 Mozilla 的 JavaScript™ 实现时)。
意料之中的,网络上目前对于 delete 的解释非常少(笔者按:这篇文章写于 2010 年 1 月)。 MDC(]) 的资源大概是这其中最详细的了,但不幸的是它遗漏了一些有趣的细节,这些细节中就包括了上述 firebug 的奇怪表现。MSDN 文档几乎没什么用处。
一、理论 | Theory
那么,为什么我们能删除一个对象的属性:
复制代码 代码如下:
var x = { a: 1 };
delete x.a; // true
x.a; // undefined
但却不能删除一个变量:
复制代码 代码如下:
var x = 1;
delete x; // false;
x; // 1
也不能删除一个函数:
复制代码 代码如下:
function x() {};
delete x; // false;
typeof x; // "function"
注意:delete 只有当一个属性无法被删除时才返回 false。
为了理解这一点,我们需要首先把握一些概念: 变量实例化(variable instantiation)和属性的内部属性(property attributes) (译者按:关于 property 和 attributes 的区别见参考文章,根据下面涉及到的内容,拟译成内部属性) ——这些很少在 javascript 书中被提到。 在下面几段中我将试着简短地回顾这些内容,要理解它们并不难。 如果你并不关注它们表现背后的原因,可以跳过这一章。
1.1、代码的类型 | Type of code
ECMAScript 中有三类可执行代码:
1.全局代码 Global code
2.函数代码 Function code
3.Eval code
这几类的含义大致就像它们命名的那样,但还是快速地回顾一下:
1.当一个源文件被看做是一个程序,它在全局作用域(scope)内执行,而这就被认为是一段全局代码 Global code。 在浏览器环境下,SCRIPT 元素的内容通常都被解析为一个程序,因而作为全局代码来执行。
2.当然,任何在一段函数中直接执行的代码就被认为是一段函数代码 Function code, 在浏览器环境下,事件属性的内容(e.g. <a)通常都作为函数代码来解析和执行。
3.最后,放入内建函数 eval 中的代码就作为 Eval code 来解析。 我们将很快看到为什么这一类型是特殊的。
1.2、代码执行的上下文 | Execution Context
当 ECMAScript 代码执行时,它总是发生在一个确定的执行上下文(context)中。 执行作用域是一个抽象实体,它有助于理解作用域和变量实例化的工作原理。 上面三类可执行代码都有各自的执行上下文。 当函数代码执行时,我们说控制端进入了函数代码的执行上下文; 当全局代码执行时,我们说控制端进入了全局代码的执行上下文,以此类推。