一、简单介绍
闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
通俗来说,闭包 = 内层函数 + 外层函数的变量
二、作用域
想要理解闭包,首先必须理解JS的作用域,作用域分为全局作用域和局部作用域。ES6新增了块级作用域。
1. 全局作用域
全局作用域中声明的变量,在代码的任何地方都可以访问,其生命周期伴随着页面的生命周期。
const num = 10
function fn() {
console.log(num)
}
// 此处可以打印出结果,结果为10
fn()
function getNum() {
// 因为没有声明变量,所以num变成了window的属性
num = 10
}
getNum()
// 可以获取到num的值,打印结果为10
console.log(num)
2. 局部作用域
2.1 函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
function getNum() {
const num = 10
}
// 无法访问
console.log(num)
2.2 块级作用域
{ } 包裹起来的代码称为代码块, 使用 let 或 const 声明的变量, 在 { } 会产生块级作用域。
// 1. 只有let、const会产生块级作用域,var声明不会产生块级作用域
{
function getNum() {
const num = 10
}
// 可以访问
console.log(num)
}
// 2. 块级作用域的外部不能访问内部的变量
for (let i = 0; i < 5; i++) {
// 可以访问
console.log(i)
}
// 无法访问
console.log(i)
// 3. 不同代码块之间的变量无法相互访问
for (let i = 0; i < 3; i++) {
console.log(i)
} // 0 1 2
for (let i = 0; i < 5; i++) {
console.log(i)
} // 0 1 2 3 4
那么我们怎样才能从外部读取局部作用域呢?这就涉及到了作用域链这个概念。
3. 作用域链
在很多情况下,我们需要得到函数内的局部变量,因为函数是可以嵌套函数的,每个函数都有一个局部作用域,这样也会形成作用域的嵌套。所以我们只需要在函数的内部,再定义一个函数。
function fn1() {
// 局部作用域
let num = 10
function fn2() {
// 局部作用域
// num = 20
console.log(num)
}
fn2()
}
fn1() // 10
因为 fn2 嵌套在 fn1 中,这时 fn1 内部的所有局部变量,对 fn2 都是可见的。反之,fn2 内部的局部变量,对 fn1 就是不可见的。
所以,作用域的查找查找规则是:子对象会往上层作用域中查找变量,直到全局作用域。它的本质就是底层变量的查找机制。
三、闭包
上述代码中的 fn2 函数和外层函数变量的集合就是闭包。
1. 闭包的简单写法
function outer() {
let num = 10
function fn() {
console.log(num)
}
return fn
}
const fun = outer()
fun()
外层函数就相当于把这个整体包裹起来了,所以叫闭包。
2. 闭包的应用
- 保护函数内的变量安全。以最开始的例子为例,函数 outer 中的 num 只有函数 fn才能访问,而无法通过其他途径访问到,因此保护了 num 的安全性。
- 在内存中维持一个变量。
eg:for循环中的定时器
for(let i = 0; i < 3; i++){
;(function(i) {
setTimeout(function() {
console.log(i)
},1000)
})(i)
}
使用闭包将 i 长期存储在内存中。
闭包可能会引起的问题:内存泄漏。