堂主 - WEB前端开发

专注于互联网 | WEB前端开发 | 以用户为中心的体验 | 家是圆心 | Code is art !

关于FireFox中with语句在解析时的一个BUG

Tags : JS   Bug     5 comments

首先感谢寒飞紫,是他对我在翻译的《Pro JavaScript with MooTools》第二章(非出版目的,纯属个人爱好)的demo中发现了这个问题。

下面我们来看一段简单的代码:

var demo = 123;

function demo() {}

alert(typeof demo);

我的问题是,alert出的会是function还是number?

OK,如果看官对JavaScript函数的内部工作机制和JavaScript解析器的解析机制不那么清楚的话,回答这个问题最简答的方法就是copy出这段代码,放到FireBug的控制台中运行一下。但是,FireBug控制台中运行的结果就一定准确么?

在FireBug控制台中alert出的结果是function。但如果我们把这段代码放到一个网页body的script中,再访问这个网页,那么alert出的会是什么呢?

我们直接用浏览器访问这个页面(演示一),结果会很诧异,输出的是number!那么究竟哪一个输出是正确的呢?什么地方出了问题么?

我先告诉你答案,number是正确的结果。

一个JavaScript解析器在解析上面那段代码时,忽略其中我们这里不需要去关注的一些细节,关键的过程是:

  1. 创建一个执行上下文。
  2. 在这个上下文中进行变量声明,首先是查找由var关键字声明的变量。
  3. 遇到第一行的变量demo声明,此时变量未初始化完成(因为还未执行赋值操作),其值为undefined。
  4. 遇到第二行的函数声明,这里需要注意的是采用函数声明方式创建的函数,其引用变量的声明和初始化过程是同时进行的。于是此时变量demo值指向了创建的匿名函数,类型为function。
  5. 解析器在代码中再找不到变量声明,于是开始从头处理可执行代码。
  6. 第一行有一个赋值操作,于是demo的值被更改为数字123,此时其类型为number。
  7. 执行alert语句,弹出了number的提示。

为了证明采用函数声明方式创建的函数,其引用变量的声明和初始化过程是同步的。我们把上面的代码稍微修改一下,如下:

alert(typeof demo);

var demo = 123;

function demo() {}

alert(typeof demo);

上面代码的运行情况可以点击这个页面(演示二)来查看。我们会看到在给变量demo赋值为123的动作未执行之前,先执行的alert操作提示的类型是function,而之后的提示是number。这证明了上面的说法。

那为什么我们最开始的那端代码在FireBug控制台运行时给出的提示会是function而不是正确的number呢?

为解答这一点我们需要了解FireBug控制台的运行机制——它是使用with语句来执行在控制台中输入的代码的。

上面的代码实际上在FireBug中是被用如下方式执行的,演示可点击这里(演示三)查看:

var _FirebugCommandLine = {};

with(_FirebugCommandLine){
    var demo = 123;

    function demo() {}

    alert(typeof demo);
};

FireBug控制台首先创建一个_FirebugCommandLine对象,之后在这个对象的作用域中执行函数体中的代码。除了FireFox浏览器,在其他浏览器中演示三alert的都是number类型,而只有FireFox给出的是function类型。

这就说明了一个问题:在FireFox中,with语句中采用函数声明方式创建的函数,其声明和初始化的过程是分开的,这其实就等于是一个采用函数表达式方式创建的函数。此时我们的代码其实被变成了这样:

var demo = 123;

var demo = function() {}

alert(typeof demo);

这应该是一个BUG,因为这个解析流程是不符合规范的。清楚了这一点,我们就能明白在FireBug控制台中,最开始的那段代码是被按照如下的方式解析的:

  1. 创建一个执行上下文。
  2. 在这个上下文中进行变量声明,首先是查找由var关键字声明的变量。
  3. 遇到第一行的变量demo声明,此时变量未初始化完成(因为还未执行赋值操作),其值为undefined,写入作用域链的variable对象中。
  4. 遇到第二行的demo函数声明,在作用域链的variable对象中发现同名标识符,覆盖之,值仍为undefined。
  5. 解析器在代码中再找不到变量声明,于是开始从头处理可执行代码。
  6. 第一行有一个赋值操作,于是demo的值被更改为数字123,此时其类型为number。
  7. 第二行再次对demo进行赋值,此时demo的值是对一个匿名函数的引用。
  8. 执行alert语句,弹出了function的提示。

这个BUG很可能会导致采用FireBug作为调试工具时,在对对象的类型进行检测时产生错误的结果,这是需要我们在实践中去重点留意的。所以更好的习惯是使用Chrome浏览器,它的调试工具靠谱得多。

最后,对于with语句,建议在实际项目中尽可能的不要使用,原因有三:

  1. 不利于代码的可读性。
  2. 性能低下。
  3. 严格模式下已经禁用该语句,继续使用会引起报错。

延伸阅读:FireFox官方关于Functions的文档

参与讨论

*

*