(function(){
a = 5;
alert(window.a);
var a = 10;
alert(a);
})();
这段代码中的alert,为什么第一个是返回的undefined.按说,不要VAR的a变量,不就是全局变量吗?
(function(){
a = 5;
alert(window.a);
var a = 10;
alert(a);
})();
这段代码中的alert,为什么第一个是返回的undefined.按说,不要VAR的a变量,不就是全局变量吗?
程序运行分为两个阶段,第一是编译阶段(在当前作用域注册变量),第二是运行阶段。
程序首先编译这段代码,其他都不管,只关注变量和函数的声明。首先看到var a
,就在你的当前作用域IIFE函数中注册a这个变量。
然后进入运行阶段。遇到a = 5
时,首先是寻找a这个变量是否已经在当前作用域注册了?如果已注册,就使用当前作用域的a,并给他赋值5.如果在当前作用域没找到已注册的变量a,这时就会向外一层作用域寻找,也就是全局作用域寻找a。
随后执行alert(window.a)
,这时是寻找window
对象的a
属性,此时window
对象并没有a
属性,所以此时结果是undefined
。如果你在IIFE函数外赋值a = 20
,这时结果就是20了。
随后a = 10
寻找变量a
并赋值10(运行过程同第三段)。
最后alert(a)
,查找当前作用域是否有变量a,如果有,则返回该变量的值。否则向外一层寻找,如果在全局作用域都没有找到a,这时程序就会报ReferenceError: a is not defined
的错误。
关于作用域,推荐You Don't Know JS
之前的回答感觉有不正确的地方,自己下去又研究了下,`
(function(){
a = 5;
alert(window.a);
var a = 10;
alert(a);
})();`
这个代码中a=5在程序执行时这个a是局部变量a,而不是全局变量a,因为下面的var a=10;导致变量声明提升到程序最顶部,也就是a=5之前了,所以执行a=5时其实是对局部变量的赋值。window.a中的a是window对象的一个属性,因为从未赋值,所以一直是未定义,这句话放在这个匿名函数内的任何位置输出都是未定义。
8 回答4.6k 阅读✓ 已解决
6 回答3.3k 阅读✓ 已解决
5 回答2.8k 阅读✓ 已解决
5 回答6.3k 阅读✓ 已解决
4 回答2.2k 阅读✓ 已解决
4 回答2.7k 阅读✓ 已解决
3 回答2.4k 阅读✓ 已解决
这个问题很有趣,但也很难真的完整的回答,至少我会认为它在不同的浏览器品牌中或版本(尤其IE)中,行为有有极大差异。以下只是我的的测试结果与个人理解,如果你在不同的浏览器上测试,或许有不同的答案。为了简化起见,太细节的资料就略过
首先要理解的是,在浏览器全域中有个
window
物件(在Node.js是global
物件),在全域中共有四种可能的宣告变量的方式:再加上ES6(ES2015)的
let
与const
,又会多了两种。这两种先不在这里讨论,我只看这个问题中的var
方式。那么它们会有什么差异?而且有哪些是相等的?用一个简单的测试程式码来看:
你会得到类似的答案,这三个都是5。在严格模式(strict code/mode)情况下,只有
a=5
这个测试直接报错无法执行。第二个测试是加上IIFE(立即执行函式定义),来看这几种情况在函式里的是否能存取:
在这组测试中,明显的在严格模式中,产生很多的错误,尤其是
a = 5
是全部报错,其它的则是在函式中的this.a
会报错。最后一个测试是这个程式码,它只是用来对照这个问题中的在IIFE函式中如果有使用
a = 5
,a变量会转为全域的情况,当然你也可以代换用其他的window.a
试试:根据测试结果与相关文件来看,
window.a
与this.a
是一致的,因为在全域中的this
相当于window
物件。当然因为this
到了函式中是另一回事,一般不会这样直接使用,不过这里的IIFE的呼叫者是全域物件,this
也相当于window
物件,刚好是个对应到的情况。不过因为严格模式对于函式有非常严密的规定,在函式中的this
无法再对应到window
物件,这一点是要注意的。var a = 5;
这是一个正常情况下的变量宣告,也是一般情况下应该这样作的方式,而且它需要定义在所有执行或函式呼叫的最上面。它与window.a
的定义不同的地方在于,JavaScript会把它视为原始资料类型的定义,而非window物件
中的一个属性值,虽然用起来都一样,但内部的实作是有差异的。要怎么知道差异在哪?最明显的例子是用
delete
运算符,它是用来删除物件中的属性用的。你可以把上面四种宣告出来的变数用下面的这语句来删除看看,因为delete
会回传删除成功与否的布林,所以你可以看到是不是能被删除。不过在严格模式中,delete
是无法使用的。根据测试后,只有
var a = 5;
这种宣告方式,是无法被删除,它并不真正算是window物件的属性而已。另外藉由对window
物件进行(for...in),也可以得到一样的结果。那么
a = 5
这种宣告方式又是什么?它其实是隐喻式的window.a
宣告方式,也就是说如果window.a = 5
是明确地来指定window物件的属性a为5,a = 5
就是暗地里把window物件的属性指定为5。不过在IE8之前中的测试资料显示,
a = 5
与var a = 5
的意义相等,这又是浏览器品牌与版本实作的可怕差异之处。所以这个宣告方式,是一种极为不建议的陷阱语法。回到题目本身的程式码来看,另外有一个变数特性重点,称之为提升(Hoisting),提升的层次是局限在函式中的,大致的情况会是,所有的以
var
(let与const相同行为)宣告的变量,在执行前会先提升宣告部份到函式区块中的最前面,但要特别注意的是它的赋值(指定值)部份是不会被一并提升的,所以会变为先被赋值为undefined
。所以原本的程式码在执行前的变量被提升如下:
所以到执行到
a = 5
这行时,a
是一个只在函式区块中的区域变量,而非像上面所言的成为全域window
物件中的属性,也因此在window.a
要取值时,必定是取到undefined
。你可以对比上面的问题,另外宣告一个b=100
,看看会不会取得到window.b
范例如下:这个问题是个典型而常见的陷阱题目,在面试时也常被拿出来考试。要很清楚地回答它需要对JavaScript语言中的内部设计有一定理解。当然,你也发觉有很多地方在最新的浏览器中或严格模式中被修改或拿掉,代表这些都是很容易造成错误的设计。
其他资讯可以参考: http://stackoverflow.com/ques...