闭包与立即执行函数的应用
一.闭包
在前文中已经对闭包做了一个定义和解释,包括如何实现一个闭包,具体可以参阅前文:[路飞]每日一答:什么是闭包?
闭包的核心作用就是将变量私有化,防止外部访问。
二.立即执行函数
立即执行函数即 IIFE (Immediately-Invoked Function Expressions)。
顾名思义就是在定义的时候就会立即执行。立即执行函数的特点就是它不需要生命,而是在定义的时候就会立即执行。在执行完后会立即销毁,不会占用内存。
三.闭包和立即执行函数的应用
下面我们来谈谈立即执行函数的应用,以及立即执行函数如何配合闭包一起使用,他们可以达到什么样的效果。
1. 场景1
对于某一个变量 number
进行计数,每隔1秒计数+1,并且打印 number
在通常情况下,我们可能会这么写:
var number = 0
setInterval(function () {
console.log(++number) // 1 2 3 4 5 6 7 8 9 10 ...
}, 1000)
这么写固然没有问题,但是 number
被暴露在了全局作用下,这时我们可以利用立即执行函数,将 number
作为临时变量“隐藏”
setInterval((function () {
var number = 0
return function () {
console.log(++number) // 1 2 3 4 5 6 7 8 9 10 ...
}
})(), 1000)
仔细观察这部分代码,实际上他在定时器中执行的函数依然是
括号中的函数,只不过利用了立即执行函数的返回的形式传递给了定时器。这样一来,number
就被立即执行函数包裹在了定时器里面,在定时器外面是访问不到的。
2. 场景2
构造一个函数,返回是一个数组,这个数组第i
个元素是打印 i
的一个 function.
function getLogsArray () {
var arr = []
for (var i = 0 ; i < 100 ; i++) {
arr[i] = function () {
console.log(i)
}
}
return arr
}
const logger = getLogsArray()
logger[1]() // 100
logger[2]() // 100
logger[50]() // 100
很显然这样的结果是错误的,也不是我们想要的结果
logger[1]()
应该打印的是 1
而不是 100
导致这样结果的原因一定是作用域的问题,实际上我们发现,再循环中 i
是通过 var
去声明的,所以 i
的作用域得到了提升,i
即称为整个函数作用域下唯一的变量值,也就是说数组里面所有的 i
指向的都是当前作用域下同一个i
,当循环执行完之后, i
变成了 100
,所以整个数组无论哪个一元素都会打印 100
的。
如何去解决这个问题呢?
最简单的一个方法就是将 var
关键字改为 let
关键字,使i
作用域仅仅存在在循环当中,每一轮循环就相当于是新的变量,这样一来,数组里面所打印的i
就是不同的。
function getLogsArray () {
var arr = []
for (let i = 0 ; i < 100 ; i++) { // cahnge var to let
arr[i] = function () {
console.log(i)
}
}
return arr
}
const logger = getLogsArray()
logger[1]() // 1
logger[2]() // 2
logger[50]() // 50
另一种解决方案就是当前要说的重点,就是利用立即执行函数去解决。
function getLogArray () {
var arr = []
for (var index = 0 ; index < 10; index++) {
(function (i) {
arr[i] = function () {
console.log(i)
}
})(index)
}
return arr
}
const loggerArray = getLogArray()
loggerArray[1]() // 1
loggerArray[2]() // 2
loggerArray[50]() // 50
在函数中,虽然 i 的值变了,但是我们可以通过利用立即执行函数的传参,将打印这个方法设置一个独立的作用域,接收 index
这个参数,那么在打印的方法中,i
就是当前立即执行函数内独立的参数。无论 index
如何去边,都不会影响打印。
四:总结
立即执行函数最终不会创建和保存新的变量,配合闭包使用可以为闭包创建独立的作用域。