1.难度
★★★★
function func1() {
var n = 0;
add = function () {
n++;
}
function func2() {
console.log(n);
}
return func2;
}
var result1 = func1();
var result2 = func1();
result1(); // 0
result2(); // 0
add();
result1(); // 0
result2(); // 1
解:
这段程序的考点有三个:一是变量作用域;二是函数作用域链的创建;三是闭包。
(1)首先func1中的add没有var,相当于在全局声明了一个add变量,指向一个动态创建的匿名函数
(2)result1初始化的时候引擎做了什么?首先在全局添加变量add,然后制作一个匿名函数挂到add上,这个匿名函数在创建时,产生了一个作用域链,其中包含n的值0;
(3)result2初始化的时候引擎做了什么?此时全局已经有了add变量,func1再次创建一个匿名函数,并将函数挂在add上,这样add就被覆盖了一次,(2)中产生的匿名函数将不能被调用,因为指向它的add已经指向了新创建的匿名函数。新的匿名函数有自己独立的作用域链,开始时作用域中的n也是0,但这个n跟(2)中的n已经不是同一个了,因为两个匿名函数的作用域链是不同的,因为n前面有个var
(4)result1和result2都是闭包,他们第一次执行的时候,输出各自的n的初始值0。
(5)add运行的时候,实际是执行了与result2有关的另一个闭包,而与result1有关的闭包中的n并没有被修改。
(6)result1和result2再次执行的时候,上述分析得到验证。
(7)引申以下,如果去掉n前面的var呢?答,输出则是0,0,1,1。因为这时n也被提升了,result1和result2的作用域联中对n的引用是同一个,所以执行add后都输出1。
(8)如果重写以上代码,让它已大家熟悉的形式展现,则相当于:
var add;
function func1(n) {
add = function () {
n++;
}
return function () {
console.log(n);
};
}
var result1 = func1(0);
var result2 = func1(0);
result1(); // 0
result2(); // 0
add();
result1(); // 0
result2(); // 1
2.难度
★★
function main() {
var n = 0;
var func = function () {
n = 1;
}
func();
console.log(n); // 1
function func() {
n = 2;
}
func();
console.log(n); // 1
}
main();
解:
这段程序考点比较少,只有函数提升和表达式函数。
(1)在一个作用域块中,任何地方定义的函数(非表达式方式定义),都相当于在该作用域块头部定义,在作用域任何位置都能调用,这就是”函数提升“。也就是说解释器在创建main是,就将func创建好了,但此时n还没有创建,因为main还没有执行。
(2)在等号后面创建的函数是表达式函数,表达式函数与声明函数的不同在于只有代码执行到这一样时,这个函数才被创建,在创建之前是不能调用的。在一个作用域块中,表达式函数可以覆盖声明函数,但声明函数不能覆盖表达式函数。
(3)接下来看main执行的时候发生了什么。
首先创建一个变量n。
然后创建一个匿名函数,并将匿名函数挂到func上。注意,func前面的var其实是没有意义的,因为func已经存在了,是包含“n = 2;”的那个函数。如果在main的第一行添加“console.log(func)”,控制台会输出函数体,而不是undefined。但执行到这里的时候,原来的函数被匿名函数覆盖掉了。
执行func时,执行的是新创建的匿名函数,输出1。
第一个控制台输出后面的代码是没有意义的,因为这部分代码已经被提升了,完全当它不存在就可以。
接下来的执行和输出道理就一样了。
(4)总之记住一点:作用域中的函数在作用域任何地方都可以用;但var出来的东西只有在var后面才能用;作用域中的变量,在var之前用永远是undefined。