闭包相关问题
闭包是一个函数, 其可以记住并访问外部变量.
在函数被创建时, 函数的隐藏属性 [[Environment]] 会记住函数被创建时的位置, 即当时的词法环境 Lexical Environment
这样, 无论在哪里调用函数, 都会去到 [[Environment]] 所引用的词法环境
当查找变量时, 先在词法环境内部查找, 当没有找到局部变量时, 前往当前词法环境所记录的外部词法环境查找
应用场景
- 封装私有变量
手写一个闭包
js
function outer() {
let i = 0
return function inner() {
console.log(i++)
}
}
const add = outer()
add()
add()
闭包经典问题
改造下面的代码,使之输出 0-9,写出你能想到的所有解法
js
// 解决问题
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, 1000)
}
解决办法
js
// 方法一
// 1. 利用 setTimeout 函数的第三个参数,会作为回调函数的第一个参数传入
// 2. 利用 bind 函数的部分执行的特性
// code1
for (var i = 0; i < 10; i++) {
setTimeout((i) => {
console.log(i)
}, i)
}
// code2
for (var i = 0; i < 10; i++) {
setTimeout(console.log, 1000, i)
}
// code3
for (var i = 0; i < 10; i++) {
setTimeout(console.log.bind(Object.create(null), i), 1000)
}
js
// 方法二
// 1. 利用 let 变量块级作用域特性
// code1
for (let i = 0; i < 10; i++) {
setTimeout((i) => {
console.log(i)
}, 1000)
}
js
// 方法三
// 1. 利用函数自执行的方式,将当前 for 循环过程中的 i 传递进去,构建出块级作用域。
// IIFE 其实并不属于闭包的范畴
// 参考:
// - [difference-between-closures-and-iifes-in-javascript](https://stackoverflow.com/questions/41228824/difference-between-closures-and-iifes-in-javascript)
// - [IIFE 是闭包?](https://bit.ly/2NXNT56)
// 2. 利用其他方式构建出块级作用域
// code1
for (var i = 0; i < 10; i++) {
;((i) => {
setTimeout(() => {
console.log(i)
}, 1000)
})(i)
}
// code2
for (var i = 0; i < 10; i++) {
try {
throw new Error(i)
} catch ({ message: i }) {
setTimeout(() => {
console.log(i)
}, 1000)
}
}
// code3
for (var i = 0; i < 10; i++) {
try {
throw i
} catch (i) {
setTimeout(() => {
console.log(i)
}, 1000)
}
}
// code4
for (var i = 0; i < 10; i++) {
customFn(i)
}
function customFn(i) {
setTimeout(() => {
console.log(i)
}, 1000)
}
js
// 方法四
// 很多其他方案只是把 console.log(i) 放到一个函数里面
// 因为 setTimeout 函数的第一个参数只接收函数及字符串
// 如果是 js 语句的话,js 引擎应该会自动在该语句外面包裹一层函数
// code1
for (var i = 0; i < 10; i++) {
setTimeout(console.log(i), 1000)
}
// code2
for (var i = 0; i < 10; i++) {
setTimeout(
(() => {
console.log(i)
})(),
1000
)
}
// code3
for (var i = 0; i < 10; i++) {
setTimeout(
((i) => {
console.log(i)
})(i),
1000
)
}
// code4
for (var i = 0; i < 10; i++) {
setTimeout(
((i) => {
console.log(i)
}).call(Object.create(null), i),
1000
)
}
// code5
for (var i = 0; i < 10; i++) {
setTimeout(
((i) => {
console.log(i)
}).apply(Object.create(null), [i]),
1000
)
}
// code6
for (var i = 0; i < 10; i++) {
setTimeout(
((i) => {
console.log(i)
}).apply(Object.create(null), { lenght: 1, 0: i }),
1000
)
}
js
// 方法五
// 利用 eval 或 new Function 执行字符串,然后执行过程同方法 四
// code1
for (var i0; i < 10; i++) {
setTimeout(eval('console.log(i)'), 1000)
}
// code2
for (var i0; i < 10; i++) {
setTimeout(new Function('i', 'console.log(i)')(i), 1000)
}
// code3
for (var i0; i < 10; i++) {
setTimeout(new Function('console.log(i)')(), 1000)
}
扩展阅读
知识点
- 什么是闭包?
- MDN 定义
- 闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。
- 换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。
- 在 JavaScript 中,闭包会随着函数的创建而被同时创建。
- 定义一: 闭包是指可以访问其所在作用域的函数
- 定义二: 闭包是指有权访问另一个函数作用域中的变量的函数
- 定义三: 闭包是指在函数声明时的作用域以外的地方被调用的函数
- 严格来说,闭包需要满足三个条件:【1】访问所在作用域;【2】函数嵌套;【3】在所在作用域外被调用
- MDN 定义
- 什么是 IIFE?
- 立即调用函数表达式,Immediately-Invoked Function Expression,简写为 IIFE。顾名思义,这个函数定义之后马上被执行。
- IFE 最大的好处在创建出一个新的作用域
- IIFE 有助于避免多个函数访问全局变量时的全局变量污染
- 闭包和 IIFE 的区别?
- IIFE 是创建闭包的一种特定方式
- IIFE 是闭包吗?
- 严格来讲 IIFE 并不算闭包,因为函数并没用在本身的词法作用域以外执行。
思考题
TODO: 参考文档,后续整理
参考