闭包
《JavaScript高级程序设计》:
闭包是指有权访问另一个函数作用域中的变量的函数
《JavaScript权威指南》:
从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。
《你不知道的JavaScript》
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
闭包就是那些引用了外部作用域中变量的函数。
外部作用域执行完毕后
当外部作用域执行完毕后,内部函数还存活(仍在其他地方被引用)时,闭包才真正发挥其作用。譬如以下几种情况:
- 在异步任务例如
timer定时器,事件处理,Ajax请求中被作为回调 - 被外部函数作为返回结果返回,或者返回结果对象中引用该内部函数
考虑如下的几个示例:
第一种异步任务情况
Timer
(function autorun(){
let x = 1;
setTimeout(function log(){
console.log(x);
}, 10000);
})();
变量x将一直存活着直到定时器的回调执行或者clearTimeout()被调用。 如果这里使用的是setInterval(),那么变量x将一直存活到clearInterval()被调用。
Event
(function autorun(){
let x = 1;
$("#btn").on("click", function log(){
console.log(x);
});
})();
当变量x在事件处理函数中被使用时,它将一直存活直到该事件处理函数被移除。
Ajax
(function autorun(){
let x = 1;
fetch("http://").then(function log(){
console.log(x);
});
})();
变量x将一直存活到接收到后端返回结果,回调函数被执行。
在已上几个示例中,我们可以看到,log()函数在父函数执行完毕后还一直存活着,log()函数就是一个闭包。
第二种函数返回情况
函数作为返回值
function fn() {
var max = 10;
return function bar(x) {
if (x > max) {
console.log(x)
}
}
}
var f1 = fn();
f1(15);
函数作为参数被传递
var max = 10;
var fn = function (x) {
if (x > max) {
console.log(x);
}
};
(function (f) {
var max = 100;
f(15);
})(fn)
闭包的作用
1、能够访问函数定义时所在的词法作用域 ( 阻止其被回收 ) 。
2、私有化变量
function base() {
let x = 10; // 私有变量
return {
getX: function () {
return x;
}
}
}
let obj = base();
console.log(obj.getX());
3、模拟块级作用域
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = (function (j) {
return function () {
console.log(j)
}
})(i)
}
a[6]();
4、创建模块
模块模式具有两个必备的条件 ( 来自《你不知道的JavaScript》)
- 必须有外部的封闭函数,该函数必须至少被调用一次 ( 每次调用都会创建一个新的模块实例 )
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
function coolModule() {
let name = 'XiaoYang';
let age = 20;
function sayName() {
console.log(name);
}
function sayAge() {
console.log(age);
}
return {
sayName,
sayAge
}
}
let info = coolModule();
info.sayName();
测试
function createCounter() {
let counter = 0
const myFunction = function() {
counter = counter + 1
return counter
}
return myFunction
}
const increment = createCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log('example increment', c1, c2, c3)
闭包与循环
经典面试题,循环中使用闭包解决 var 定义函数的问题
闭包只存储外部变量的引用,而不会拷贝这些外部变量的值。
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i);
不会是期望的 5-> 0,1,2,3,4,而是 5->5,5,5,5,5 即第 1 个 5 直接输出,1 秒之后,输出 5 个 5
1、使用闭包解决
for (var i = 0; i < 5; i++) {
(function(j) { // j = i
setTimeout(function() {
console.log(new Date, j);
}, 1000);
})(i);
}
console.log(new Date, i);
2、使用 setTimeout 的第三个参数
for (var i = 0; i < 5; i++) {
setTimeout(function(j) {
console.log(new Date, j);
}, 1000, i);
}
console.log(new Date, i);
3、使用参数传递
var output = function (i) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
};
for (var i = 0; i < 5; i++) {
output(i); // 这里传过去的 i 值被复制了
}
console.log(new Date, i);
4、使用 let
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i); // 这种其实会报错,因为 let i 这里访问不到 i
具体看参考2
参考
从闭包说起 这个很好