[路飞]每日一答:闭包与立即执行函数的应用?

275 阅读3分钟

闭包与立即执行函数的应用

一.闭包

在前文中已经对闭包做了一个定义和解释,包括如何实现一个闭包,具体可以参阅前文:[路飞]每日一答:什么是闭包?

闭包的核心作用就是将变量私有化,防止外部访问。

二.立即执行函数

立即执行函数即 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)

仔细观察这部分代码,实际上他在定时器中执行的函数依然是

image.png

括号中的函数,只不过利用了立即执行函数的返回的形式传递给了定时器。这样一来,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如何去边,都不会影响打印。

四:总结

立即执行函数最终不会创建和保存新的变量,配合闭包使用可以为闭包创建独立的作用域。