闭包+面试真题
对闭包的理解
闭包是内层函数使用外层变量
(子级可以访问父级的变量,但是父级不可以访问子级的)
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
闭包有两个常用的用途;
- 闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
- 闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
这是因为 JavaScript 的垃圾回收机制会检查对象是否还被引用,如果没有引用,则认为该对象是垃圾,可以被回收。而闭包的内部函数可能会继续访问外部函数的变量。只有当闭包函数本身也被销毁时,其引用的外部作用域才会被释放。
比如,函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。
function A() {
let a = 1;
window.B = function () {
console.log(a);
};
}
A();
B(); // 1
面试真题
1. 基础题
在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量。经典面试题:循环中使用闭包解决 var 定义函数的问题
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
首先因为 setTimeout
是个异步函数,所以会先把循环全部执行完毕,这时候 i
就是 6 了,所以会输出一堆 6。解决办法有三种:
- 第一种是使用闭包的方式
for (var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i); // 把i作为参数传递给前面括号里的j
}
使用立即执行函数包裹 setTimeout 的回调函数,能够创建一个闭包
在上述代码中,首先使用了立即执行函数
将 i
传入函数内部,这个时候值就被固定在了参数 j
上面不会改变,当下次执行 timer
这个闭包的时候,就可以使用外部函数的变量 j
,从而达到目的。
- 第二种就是使用
setTimeout
的第三个参数,这个参数会被当成timer
回调函数的参数传入。
for (var i = 1; i <= 5; i++) {
setTimeout(
function timer(j) {
console.log(j);
},
i * 1000,
i
);
}
- 第三种就是使用
let
定义i
了来解决问题了,这个也是最为推荐的方式
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
2. 变形题
/* TASK A1
In the following code, what will be printed to console if a user clicks the first and fourth button? Why?
*/
var nodes = document.getElementsByTagName("button");
for (var i = 0; i < nodes.length; i++) {
nodes[i].addEventListener("click", function () {
console.log("You clicked element #" + i);
});
}
输出的是 nodes.length的值;
因为var生命的是函数作用域
解决:
方法1:let
方法2:闭包
var nodes = document.getElementsByTagName("button");
for (var i = 0; i < nodes.length; i++) {
(function (i) {
nodes[i].addEventListener("click", function () {
console.log("You clicked element #" + i);
});
})(i);
}