闭包
闭包是指有权访问另一个函数作用域中的变量的==函数==。
function fn() {
var max = 10;
return function bar(x) {
if ( x > max) {
console.log(x);
}
};
}
var f1 = fn();
f1(15);
在上述代码中,bar函数就是一个闭包,解释闭包前先解释几个前置概念,有哪里不懂得可以自行了解。
作用域分为全局作用域和函数作用域(ES6新增块级作用域),作用域在代码执行之前就会创建,函数作用域在函数定义时创建。作用域规定了变量的可访问范围也就是使用变量时可以在哪个范围里查找该变量。作用域的作用是隔离变量。不同作用域中的同名变量不会发生冲突。
第一个方框内:全局作用域,第二个方框内:fn作用域,第三个方框:bar作用域。可以看出,作用域可以嵌套,在作用域中查找某个变量时若此作用域中没有定义这个变量,则向外一层的作用域中查找,这样==由内向外==查找直到全局作用域。这种作用域由内向外一级一级的嵌套关系叫做作用域链。
执行上下文环境分为全局执行上下文环境和函数执行上下文环境。首先进入全局上下文,在函数调用时创建此函数的函数执行上下文(压栈:压入执行上下文栈),函数调用结束就销毁此函数的函数执行上下文(出栈:压出执行上下文栈),再回到全局上下文环境中。同一时刻只能有一个执行上下文处于活动状态。
自由变量在当前作用域中存在但未定义的变量。自由变量的取值要到==创建==(注意是创建而不是调用,也有父级作用域的说法)这个函数的那个作用域中取值。
function fn() {
var max = 10;
return function bar(x) {
if ( x > max) {
console.log(x);
}
};
}
var f1 = fn();
f1(15);
我们知道变量只能由内而外访问,在这段代码当中,fn()的返回值是bar,所以第9行代码相当于把bar()赋给f1(),bar()中含有自由变量x,而x在fn()函数当中,所以此时fn()函数虽然执行完了,但是仍然不能销毁fn()的执行上下文(因为里面存放着x,bar()函数还要使用),要等bar()函数执行完后fn()的执行环境才可以销毁。这种情况下bar()函数就叫闭包。
for循环面试题分析
答案为D,在这道题中的关键在于,for循环不是函数,所以 i 是全局作用域中的变量。首先依次执行代码:
elements = [li,li,li,li]
length = 4
elements[0].onclick = function() { alert(i); } //注意:此时函数只是定义,并未执行。
elements[1].onclick = function() { alert(i); }
elements[2].onclick = function() { alert(i); }
elements[3].onclick = function() { alert(i); }
接着执行i++,此时,全局作用域中变量 i = 4 。
接着,点击第一个 li 标签,触发elements[0].onclick执行function() { alert(i); },function() 函数中并没有定义变量 i ,故去上一级也就是全局作用域中查找变量 i 的取值,此时 i = 4,故提示 4。
同样的,点击第二个 li 标签,触发elements[1].onclick执行function() { alert(i); },function() 函数中并没有定义变量 i ,故去上一级也就是全局作用域中查找变量 i 的取值,此时 i = 4,故提示 4。
..........
所以依次点击提示结果为4,4,4,4。
var element = document.getElementsByTagName('li');
var length = element.length;
for(let i =0; i < length; i++) {
element[i].onclick = function () {
alert(i);
}
}
依次点击提示结果为0,1,2,3。
使用let关键字时会产生块级作用域,for每次循环的大括号都是一个独立的块级作用域,由于后面还要用到 i ,所以这几个块级作用域都不会销毁。
elements = [li,li,li,li]
length = 4
elements[0].onclick = function() { alert(i); } // 在此块级作用域中,i = 0
elements[1].onclick = function() { alert(i); } // 在此块级作用域中,i = 1
elements[2].onclick = function() { alert(i); } // 在此块级作用域中,i = 2
elements[3].onclick = function() { alert(i); } // 在此块级作用域中,i = 3
接着,点击第一个 li 标签,触发elements[0].onclick执行function() { alert(i); },function() 函数中并没有定义变量 i ,故去上一级也就是第一个块级作用域中查找变量 i 的取值,在此作用域中 i = 0,故提示 0。
点击第一个 li 标签,触发elements[1].onclick执行function() { alert(i); },function() 函数中并没有定义变量 i ,故去上一级也就是第二个块级作用域中查找变量 i 的取值,在此作用域中 i = 1,故提示 1。
..........
所以依次点击提示结果为0,1,2,3。 其实就是在内部形成了一个小闭包,也可以由立即执行函数等实现这种效果。
一般如何产生闭包
- 返回函数
- 函数当做参数传递
参考文章: