1.由变量开始谈
习惯性先来段代码:
var x = "globol value";
var getValue = function(){
alert(x); //弹出"undefined"
var x = "local value";
alert(x); //弹出"local value";
}
getValue();
代码很简单,首先定义了一个全局变量x并赋了初值,而后写了个getValue的方法,以后咱们用alert弹出x的值,可是结果是undefined,不是global value也不是local value,这个咱们可能会感受到奇怪。其实理解这个问题的关键就是要清楚x的做用域。
第一个var x中的x是全局变量,js解释器在执行任何代码以前会先建立一个全局对象(global object),全局变量就是至关于这个全局对象的一个属性。同理,对于getValue这个函数,就会生成一个叫作调用对象的东西,局部变量就是这个调用对象的属性,例子中第二个var x中的x就是局部变量。
2.词法做用域
词法做用域。这个是何方神圣呢?要理解的话,其实咱们能够对比传统面向对象的(如JAVA、C#)中的变量的做用域,咱们知道C#中的变量做用域是块级的,即这个变量只能活动在定义他的一个直接外界内,如if子句内,for子句内定义的变量外界是没法读取的。而js中呢,变量却不是这样的,在同一个函数内定义的变量其它的成员是能够访问的。看个例子会清楚不少:
function test(o){
var i = 0;
if(typeof o == "object"){
var j = 0;
for(var k=0; k < 10; k++){
document.write(k);
}
document.write(k); //k是能够被访问到的,即便它在for子句外
}
document.write(j); //说明j是能够被访问到的,即便它在if子句外
}
清楚了这一点后,就来理解下js中关于词法做用域的含义。
当定义了一个函数后,当前的做用域就会被保存下来,而且成为函数内部状态的一部分,这个是很重要的一个概念。
下面咱们回到开篇的那个例子,当getValue函数被定义的时候,他的做用域被保存起来,还有被加到做用域链上,他的上端就是全局执行环境。当调用getValue方法的时候,js解释器首先会把做用域设置为定义函数的时候的那个做用域(即以前保存那个),接下来,他在做用域的前加上调用对象即getValue这个函数,再在他的上端加上全局对象。以下图:闭包
这个做用域链其实和原型链有点类似,也好似在本做用域内找不到就会向上去找。比方说开篇那个例子,找x的时候(js的预约义机制,就是js解释器会先对var定义的变量进行初始化,应该说只是起了定义的做用但没赋值),会先在本做用域内找,由预约义机制知能够找到x,可是没赋值,因此是undefined值。知道了这点咱们来知道开篇那个代码实际上是等价于下面这个的:
var x = "globol value";
var getValue = function(){
var x;
alert(x); //弹出"undefined"
x = "local value";
alert(x); //弹出"local value";
}
getValue();
当调用 getValue()函数时造成的做用域链为“调用对象”->“全局对象”,执行alert(x)时,首先查找调用对象是否有x的属性,若是有则使用“调用对象”的x,若是没有,就接着查找“全局对象”是否有x的属性。
实际上js解释器作的事情应该是按以上这个例子执行的,因此从另外一个角度说,将变量的定义放在开头这个约定是有意义而且有益处的。
3.延伸
清楚了以上关于词法做用域的概念后,咱们就不难理解闭包的概念了,它只是用到了做用域链的不可向下性,即下面的做用域能够访问上面的,但上面的不能够访问下面的。固然这只是构成闭包的一个条件,闭包更重要的仍是外部函数持有内部函数的一个嵌套函数的引用,看下简单例子:
function foo(){
var age = 10;
function boo(){
age += 10;
return age;
}
return boo;
}
alert(foo()()); //20函数
//var tx = foo();
//alert(tx()); //20spa