LeoYuan / leoyuan.github.io

This is my blog repo.
http://leoyuan.github.io
17 stars 2 forks source link

JavaScript中后边的代码被先执行? #1

Open LeoYuan opened 11 years ago

LeoYuan commented 11 years ago

事件背景

在某个页面中引入的first.js文件中有如下一段代码(已做简化处理)。

window.globalObj = {
    firstAttr: 1
}

注:globalObj是系统中被认可的少数几个全局变量之一。 在同一个页面中位置靠后引入的另一个js文件second.js中有如下代码

globalObj = globalObj || {};
globalObj.secondAttr = 2;

console.log(globalObj)

此时一切如预期运行,globalObj中被注入了新的属性secondAttr。 可随着需求的变更,需要给globalObj中新增加一个属性thirdAttr,于是我写了如下一句代码

var globalObj = globalObj || {};
globalObj.thirdAttr = 3;

结果在 IE8 下,神奇的事情出现了,globalObj对象中的firstAttr消失了,去掉后来增加的代码后,又出现了,冥思苦想都想不明白,在运行var globalObj = globalObj || {};的时候,globalObj已经存在,显然不会去覆盖,怎么回事? 各种console都不得要领,最后被同事一句外行话惊醒,

是不是js在编译期先执行了后边的语句?

注:事实上js是解释运行的,没有编译阶段。

这才想起,js中确实有hoisting一说,即函数定义和变量初始化会被提升(hoisted),谜团遂解。

柳暗花明

正是因为这句var globalObj = globalObj || {};中的var globalObj = undefined被提升到了作用域的顶端,此例即被提升到了文件最开始处,于是反而影响了文件头部那句globalObj = globalObj || {};的执行,globalObj被重置了!

拓展阅读

JavaScript-Scoping-and-Hoisting written by ben cherry Javascript function scoping and hoisting asked on stackoverflow

疑问

不过还有一点还是不太明白,为什么IE9+, Chrome, FF都没事呢?难道对于这种情况下的hoisting做了特殊处理?

LeoYuan commented 11 years ago

@varfunction 唐洋,有啥想法不?

varfunction commented 11 years ago

谈一下我对var命令及作用域的理解吧~ 在function运行时,js引擎会先构建function的执行环境,其中重要一步就是把这个function里,通过var命令声明的变量放到这个函数的作用域头部。 需要注意的是,放到作用域头部的标识符,并不指向任何内存区域,比如看下面例子:

    var a = 1; 
    function b() { 
       debugger;//第一行
       a = 10; //第二行
       function a() {} //第三行
    } 
    b(); 
    console.log(a);

b函数执行前,由于第三行的作用, 相当于 var a = function(){}; 所以相当于在b的作用域顶端放入了一个 执行环境.a,这个a并不影响全局变量,只是b函数的局部变量。 (这也是IE8和其他浏览器的差异所在, IE8下,相当于执行执行环境.a = undefined;, 而其他浏览器下,相当于执行执行环境.a = 执行环境.a || undefined;

上述代码,执行到第一行时,a = undefined; 执行完第二行,a = 10; 执行完第三行,a = function();

不知道这是不是可以解释你的问题?

LeoYuan commented 11 years ago

洋仔,发现你上边分析的小案例有误。。。 varfunction都是直接提升的,function不会转换成var a = function() {} 所以,function a() {}应该提升到a = 10上边,因此,代码等价于

var a = 1; 
function b() { 
  function a() {};
  a = 10;
} 
b(); 
console.log(a);
varfunction commented 11 years ago

没问题的,效果都一样。 你说的“直接提升”,和我说的 “相当于 var a = function(){}” 本质都是在作用域链的最前面定义了 a 这个东西,只是在不同浏览器中a 的初始化方式不同,所以导致你遇到的问题。

LeoYuan commented 11 years ago

呵呵,撇开我最原始的问题不谈。。。 在上面的小demo中,function a() {}确实被提升到了a=10之前,虽然不影响此贴的最终结果,但是怕你对 变量提升 有点小误解,特意提醒下。