参考
- 学习Javascript闭包(Closure)
- JavaScript 高级程序设计(第三版)P178
定义函数
定义函数有两种方式:一是函数声明,而是函数表达式。
函数声明重要的特征是,函数声明提升——在执行代码之前会先读取函数声明,这就意味着可以把函数声明放在调用此函数的语句后面。
sayHi()
function sayHi() {
console.log('hi')
}
// hi
然而,函数表达式没有这种特性,比如说:
sayHi()
const sayHi = function() {
console.log('hi')
}
//error: Uncaught TypeError: sayHi is not a function
闭包
定义
闭包是指有权访问另一个函数作用域中的变量的函数 ——JavaScript 高级程序设计(第三版)
看了红宝书的闭包,和很多关于闭包的文章。觉得还是红宝书的描述规范一点,怎么理解这句话呢?简单来说就是函数能访问别的函数的变量,这个函数就是闭包。
把闭包简单理解成"定义在一个函数内部的函数"。—— 学习Javascript闭包(Closure)——(阮一峰)
闭包用途——读取函数内部的变量
function A() {
var value = 999
function B() {
console.log('value', value)
}
B()
}
A() //999
函数 B 在函数 A中定义,那么函数 B 可以访问 A 中的变量(还可以访问全局变量)。函数 B 就是闭包,很简单吧。再看:
function A() {
var value = 999
}
function B() {
console.log('value', value)
}
B()
//报错: Uncaught ReferenceError: value is not defined
此时,函数 B 不能访问函数 A 中的 value,B就不是闭包。就是这样子
经常会看到用两个括号的方式使用闭包,例如:
(function(value) {
function B() {
console.log(value)
}
B()
})(999)
// 999
以前一直不明白这是什么意思,其实仔细看下。就是一个立即执行的匿名函数,匿名函数中的函数 B 能访问匿名函数的变量(参数),函数 B 就是闭包。
闭包用途—— 让变量的值始终保持在内存中
function f1() {
var name = 'MyName'
function sayHello() {
alert('hello ' + name)
}
sayHello()
}
f1() // hello MyName
这段代码,当 f1执行后,变量 name就会回收。再看下面:
function f1() {
var name = 'MyName'
function sayHello() {
alert('hello ' + name)
}
return sayHello
}
var result = f1()
result() // hello MyName
函数 f1 返回了函数 sayHello,并保存在全局变量 result 中,这里 result 就是 sayHello 函数闭包。无论调用多少次 result 都能访问 f1函数内的变量 name,这就意味着name 一直在内存中,并没有在f1执行后被回收。
经典面试题
要求每隔一秒打印 1、2、3、4、5,很容易写出这样的代码来:
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, 1000 * i)
}
结果是每隔一秒打印了一个6出来。由于setTimeout是异步的,首先会把循环执行完。等到打印时,变量 i 已经是6了,于是打印了5个6。
解决办法:闭包
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
这里为什么就不是打印6呢?timer就是闭包,time 访问的是 匿名函数的参数 j 。相当于循环里面调用了5次匿名函数,每次传给匿名函数的参数分别是1、2、3、4、5。可以理解为每次调用匿名函数,都给其参数 j 赋值 i。所以最终可以正确打印1、2、3、4、5。
解决这个问题还要其他的办法。
方法一:setTimeout 第三个参数
for (var i = 1; i <= 5; i++) {
setTimeout(function(j) {
console.log(j)
}, i * 1000, i)
}
方法二: 用 let 声明变量
for (let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i)
}, i * 1000)
}