一:有趣的例子
例子1:看如下代码:
x = 1;
alert(x);
var y = function() {
alert(x);
var x = 2;
alert(x);
}
y();
对于我来说,第一反应它会输出:1,1,2,然而》》》》上面的代码实际输出:1,undefined,2
为什么第二个会输出undefined?在上面我明确定义了一个全局变量x,为何找不到?
那是因为:js编译器在执行这个y函数的时候,会把把它body里面的声明变量提前到最前面进行声明。比如:
var x=2;
编译器先会在body最前面进行
var x;
声明。其实上面的代码等同于下面的这段代码:
x = 1; alert(x); var y = function() { var x; alert(x);//此时x还未赋值,所以为undefined。
x = 2;
alert(x);
}
y();
所以也就不难理解x=undefined的了.但是如果把var x = 2;这段代码给删掉,在内部它没有进行var声明。它会一直沿着作用域向上找,此时的x 就为全局x.
接下来再看一个更有趣的例子。
var a = 1; function b() { a = 10; return; } b(); alert(a);
var a = 1; function b() { a = 10; return; function a() {} } b(); alert(a);
例子很简单。第一个例子为输出10,第二个会输出1。这是为什么呢?况且第二个例子我都return 了。按理都应当输出10才对呀!那时因为JS编译器在背后作怪。
两段代码差别就是第二个例子多了个
function a(){};
即便这个函数体里面什么也没有,并且也没有对它进行任何调用——其实JS编译器在背后会把function a() {}编译成
var a=function (){}
此时对于函数内部也有一个a=10; 外面的a也还是1;根据JS作用域,会先找内部的var a,如果找不到再向上一级一级找。
因为第二个例子中a = 10;不是显示声明,所以最终alert(a) 就会显示1;
二:js作用域与作用域链
1.函数作用域
我们首先要区分Javascript的函数作用域与我们熟知的C/C++等的块级作用域。
在C/C++中,花括号内中的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的。而Javascript压根没有块级作用域,而是函数作用域.
所谓函数作用域就是说:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。
为什么说Js没有块级作用域呢,有以下代码为证:
都输出是“local",如果有块级作用域,明显if语句将创建局部变量name,并不会修改全局name,可是没有这样,所以Js没有块级作用域。
现在很好理解为什么上面的例子中会得出那样的结果了。
2.变量作用域
还是首先看一段代码:
function t(flag){ if(flag){ s="ifscope"; for(var i=0;i<2;i++) ; } console.log(i); } t(true); console.log(s);
程序会报错还是输出“ifscope"呢?答案是——会输出:”ifscope"
这主要是Js中没有用var声明的变量都是全局变量,而且是顶层对象的属性。所以你用console.log(window.s)也是会输出“ifconfig"
3.作用域链
在JavaScript中,函数也是对象,实际上,JavaScript里一切都是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
来看一段代码:
name="lwy"; function t(){ var name="tlwy"; function s(){ var name="slwy"; console.log(name); } function ss(){ console.log(name); } s(); ss(); } t();
当执行s时,将创建函数s的执行环境(调用对象),并将该对象置于链表开头,然后将函数t的调用对象链接在之后,最后是全局对象,然后从链表开头寻找变量——即s()->t()->window,很明显name是"slwy"。
但执行ss()时,作用域链是: ss()->t()->window,所以name是”tlwy"
所以以上代码输出是
slwy
tlwy
四:with语句
说到作用域链,不得不说with语句。with语句主要用来临时改变作用域链,将语句中的对象添加到作用域的头部。
看下面代码
with语句将person.wife添加到当前作用域链的头部,所以输出的就是:“lwy".
with语句结束后,作用域链恢复正常。
作用域链扩展阅读 :
JavaScript 开发进阶:理解 JavaScript 作用域和作用域链