几道常见的面试题,知识点涉及块级作用域、闭包等。
块级作用域
由花括号{}包裹的部分是块级作用域,如 if(){}、for(){}等
for(let i = 0; i < 3; i++){
console.log(i);
} // 0,1,2
console.log(i) // error
这里let提升到块级作用域里的顶部
for(var i = 0; i < 3; i++){
console.log(i)
} // 0,1,2
console.log(i) //3
在ES6之前没有块级作用域,for循环属于全局作用域,都可以被访问。var提升到全局作用域的顶部,即for循环的前面
题目1:
for(var i = 0; i < 3; i++){
setTimeout(function(){
console.log(i);
},200);
} // 3,3,3
看到var可知这里不是块级作用域,而是全局作用域,var i 变量提升,这里的i是全局变量的 i,即3次循环更改的都是同一个全局变量 i 的值。
setTimeout是异步操作,for循环是同步操作,需要等同步操作结束后执行异步操作。当200毫秒后执行function时,查找 i 的值,发现 i 是全局变量,此时值为3
for(let i = 0; i < 3; i++){
setTimeout(function(){
console.log(i);
},200);
} // 0,1,2
看到let可知这是块级作用域,每次循环产生一个作用域,共有3个 i ,每个 i 属于每次循环产生的作用域中。
整个执行过程如下:
{
let i=0;
setTimeout(function(){
console.log(i);
},200);
} //0
{
let i=1;
setTimeout(function(){
console.log(i);
},200);
} //1
{
let i=2;
setTimeout(function(){
console.log(i);
},200);
} //2
当200毫秒后执行每个function时,查找 i 的值,i 是每个块级作用域里的“全局变量”
其他办法实现块级作用域效果:
for(var i = 0; i < 3; i++){
f(i);
}
function f(n){
setTimeout(function(){
console.log(n);
},200);
}
调用函数,用函数作用域代替块级作用域,每次循环把 i 作为实参传给函数 f,执行过程如下:
// 函数的形参也是局部变量,首先声明
function f(n){
var n=n;
setTimeout(function(){
console.log(n);
},200)
}
//第一次循环时,执行f(0),即:
{var i =0;
setTimeout(function(){
console.log(i);
},200)
}
for(var i = 0; i < 3; i++){
(function(j){
setTimeout(function(){
console.log(j);
},200);
})(i)
}
使用立即执行函数,本质和上一种方法一样。把每次for循环的 i 记录下来,作为实参赋值给 j
闭包
function createFunctions(){
var result = new Array();
for(var i = 0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
console.log(createFunctions()[0]()); //10
执行createFunctions(),做for循环,数组result每个元素赋值为一个匿名函数。for循环结束后,i=10,调用匿名函数 function() { return i; },在函数内查找 i,没找到,因为闭包的存在,可以访问createFunctions()作用域,查找到 i 为10
解决:
1.变成块级作用域
function createFunctions(){
var result = new Array();
for(let i = 0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
console.log(createFunctions()[0]()); //0
看到let表明for循环变成了块级作用域,此时 i 不再是函数createFunctions内的“全局变量”。每次循环都会产生一个作用域,每个 i 值保存在每个作用域里。当调用匿名函数return i 时,在对应的作用域里查找,即:
//每次循环都会产生如下一个作用域,比如第一次循环时:
{
let i=0;
result[0]= function(){
return i;
}
}
2.使用立即执行函数
function createFunctions(){
var result = new Array();
for(let i = 0; i < 10; i++){
result[i] = (function(num){
return function(){
return num;
};
} )(i)
}
return result;
}
console.log(createFunctions()[0]()); //0
通过立即执行函数,把每一轮的 i 值以实参的形式传递给了num保存下来。当调用 function() { return i; }时,查找 i 值为num对应的值。