闭包
闭包(Closure)
它与 JavaScript 中的垃圾回收(每门语言的垃圾回收是不一样的)有点联系
什么是环境呢?
就比如一个 城市,在城市里面,我们会有很多设施:公园、医院、学校、商业广场...,那么这些设施产生了之后,就构成了这个城市的环境
那么这些环境存在的依据是什么,那是有人生活在这个城市,如果没有人生存在这里了,那么这个城市就会被摧毁掉,要么人为摧毁,要么历经日晒雨淋被摧毁。
反正就是如果没有人住了,这个环境就没有存在的意义了,那么它就应该被摧毁。
同样的,在程序中也是这样的道理,环境存在的依据是被需要
还有要注意的一点,环境里面的设施,诸如公园、医院,它也是有作用范围的,它的作用范围就只有那么大,那么这个作用范围就可以理解为程序中的作用域
我们来看一下这个例子
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Title</title>
<style> </style>
</head>
<body>
<script>
let name = 'jack'
console.log(name)
</script>
</body>
</html>
这里呢,就有一个全局环境(这里 name 就相当于上面所说的 城市里面的设施公园、学校等)
那么我这里输出了 name 之后,name 这个变量它会不会被删掉呢?
不会,这主要取决于它后续是否会被用到,因为我们这里是一个 HTML 页面,控制台有可能随时访问这些变量,所以不会,除非你把这个页面关了
环境,在计算机当中,就是给我们开辟了一块内存
让我们再来看一个例子
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Title</title>
<style></style>
</head>
<body>
<script>
let name = 'jack'
function sayHello(){
let word = 'hello world'
console.log(word)
}
sayHello()
</script>
</body>
</html>
在 js 当中,函数也是有自己的环境的
但是这个环境,要注意了,只有在这个函数执行的时候,它才有这个环境,计算机才会给你开辟一个内存空间
默认情况下,外部的环境是无法访问到函数内部环境的变量的,因为函数内部变量的作用域就只是在这个函数里面
所以,当我们把这个函数执行完了,该函数里面的变量就没人能够用到了是吧?所以这个环境,即这片内存,就会被清理掉
因为上文我们说到,当环境不被需要,就会被清理掉
还有一个要注意的地方
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Title</title>
<style></style>
</head>
<body>
<script>
let name = 'jack'
function sayHello(){
let word = 'hello world'
console.log(word)
}
sayHello()
sayHello()
sayHello()
</script>
</body>
</html>
函数每调用一次,就会开辟一次内存空间,每一次都是一个全新的内存空间
上面例子中,函数执行了三次,那么就是开辟了三次内存空间
闭包的使用
实现累加
function test() {
let n = 1
function sum() {
console.log(++n);
}
sum()
}
test()
test()
test()
那么这个例子可以累加吗?
不能,因为每一次函数的调用,都是开辟了一个环境,即一片新的内存,并且这片内存执行完,就会被清理
function test() {
let n = 1
function sum() {
console.log(++n);
}
return sum
}
let a = test()
a()
a()
a()
我们可以看出,test() 函数的返回值赋值给了变量 a
相当于把 sum 函数一直被 a 用着
在计算机当中,只要这个数据在被使用,那么就不会被清理
相应的,sum 函数所在的环境中所有的变量都不会被清理,注意不是 sum 函数里面的变量,而是 sum 函数所在的环境的变量
我们再来看一个例子
function test() {
let n = 1
function sum() {
console.log(++n);
}
return sum
}
let a = test()
a()
a()
a()
let b = test()
b()
b()
b()
这个是因为 函数执行之后 都创建了一块新的内存空间
还有一个例子
function one() {
let n = 1
function two() {
let m = 1
function three() {
console.log(++n);
console.log(++m);
}
return three
}
return two
}
let a = one()()
a()
a()
a()
可以看出 n 也是保留的
构造函数中使用闭包
function Person() {
let n = 1
this.sum = function () {
console.log(++n);
}
}
let p = new Person()
p.sum()
p.sum()
p.sum()
只要没有被清理就会形成闭包,这个例子跟下面这个例子是一样的
function Person() {
let n = 1
function sum(){
console.log(++n);
}
return {sum}
}
let p = new Person()
p.sum()
p.sum()
p.sum()
引用数据类型 才能形成闭包,基本数据类型 不行
例如
function test() {
let n = 1
function add() {
console.log(++n);
}
return {add}
}
let a = test()
a.add()
a.add()
a.add()
可以用闭包封装私有变量
function Person() {
var age = 0
this.getAge = function () {
return age
}
this.addAge = function () {
age++
}
}
const p1 = new Person()
const p2 = new Person()
p1.getAge()
p2.getAge()
这样外部就无法直接访问变量 age,只能通过闭包访问,我们就近乎实现了面向对象语言中私有变量的效果
总结
闭包的形成必备条件:😕
该内存没有被清理,被外部引用着
并且是有一种物理嵌套的关系(函数里面套函数、函数里面返回对象也行)
闭包的作用:
使得一个外部函数有权访问一个内部函数的作用域
可以说,闭包让函数的生命周期变长了
使用闭包的注意点:
1、虽然闭包非常有用,但是在使用时,它里面的信息会一直存储在内存中,只有 JavaScript 引擎确保这些信息不在使用时,才会清理这些信息。你可以在使用完毕之后,手动赋值为null,这就是手动回收
2、在 IE 中,闭包会导致内存泄漏(是指你用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来),这是 IE 的 BUG,因为在 IE 中,当我们使用完闭包之后,它会出现回收不了闭包里面的变量。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
相关链接:
https://www.bilibili.com/video/BV1YJ411R7ap?p=3&share_source=copy_web