由两段代码浅谈javascript作用域

废话少说,先上第一段代码:

1
2
3
4
5
6
7
var tt = 'aa';
function test(){
alert(tt);
var tt = 'dd';
alert(tt);
}
test();

大家可以说下第一段代码的答案!反正我看完题目的的第一感觉是“太简单了!”,于是乎我答错了。
正确的答案应该是:undefined和dd*

为什么呢?

为什么第一次alert的结果是undefined呢?要解释得清楚明白需要用到Javascript的作用域和作用域链。
Javascript中的函数“在定义它们的作用域里运行,而不是在执行它们的作用域里运行”,这是权威指南里抽象而精辟的总结。

Javascript的逻辑默认在一个全局作用域中执行,上面代码中的“var tt=’aa’;”就是定义一个全局作用域的全局变量(如果以上代码段不是在某个函数内部的话)。而test()函数内部的逻辑必须在原有的作用域(全局作用域)链再添加test函数本身的作用域(局部性),然而Javascript作用域链的特别之处在于函数内部能够嵌套函数的定义。注:在js中函数是唯一形式的代码作用域。

嵌套的内部函数可以调用其父函数的变量和其他兄弟函数(函数也可以理解成一种数据)。如果父函数被调用,当父函数执行完毕后所有数据(包括父函数和其嵌套的内部函数)都将被垃圾回收机制收集——这可以理解是js的垃圾回收机制。

有一种情况,经常用来举例做闭包讲解的代码,就是Javascript允许外部调用父函数嵌套的内部函数,即使被嵌套函数已经被‘垃圾收集’—-
最常见的就是在‘某个函数’中用其嵌套的内部函数定义某些元素的响应事件,页面载入的时候被嵌套函数(‘某个函数’)已经执行完毕(被垃圾回收),但当事件触发的时候仍然会有响应的动作,而且响应函数中还可能调用到在被嵌套函数(‘某个函数’)中定义的变量最终值(不是被垃圾回收了吗?)。ok,关于闭包的打住,我们这里的重点是:
调用对象位于作用域链的前端,局部变量、函数参数及Arguments对象都在函数内的作用域中——这意味着它们隐藏了作用域链更上层的任何同名的属性。

关于第一段代码其实也可以这样写:

1
2
3
4
5
6
7
8
var tt = 'aa';
function test(){
var tt;
alert(tt);
tt = 'dd';
alert(tt);
}
test();

也许这样写就比较容易得到正确的答案了。因为javascript可以先使用后定义,即不用在调用之前作任何声明,所以才有了第一段代码的意想不到。

然后大家继续看一段代码:

1
2
3
4
5
6
7
8
function fn(a) {
console.log(a);
var a = 2;
function a() {}
console.log(a);
}

fn(1);

输出:function a(){} 2
这又是为什么呢?我们通过第一段代码已经知道:javascript可以先使用后定义这个特性,通过这段代码我们可以发现:javascript是会先把var和function提前声明的,而且function是优先于var声明的(如果同时存在的话),所以提前声明后输出的a是个function,然后代码往下执行a进行重新赋值了,故第二次输出是2。

重申一下本文的重点:

1、函数在定义它们的作用域里运行,而不是在执行它们的作用域里运行。
2、调用对象位于作用域链的前端,局部变量、函数的参数及Arguments对象都在函数内的作用域中——这意味着它们隐藏了作用域链更上层的任何同名的对象。
3、function是优先于var声明的。

感谢您的阅读,有不足之处请在评论为我指出。

参考资料

[1]:这10道javascript笔试题你都会么

版权声明:本文为博主原创文章,未经博主允许不得转载。本文地址 http://yangyuji.github.io/2015/06/02/javascript-scope/