首先在了解闭包之前,我们要了解一下环境和作用域
1.环境和作用域
日常生活中我们生活的环境由周边建设如公园,小区,超市构成的。这就构成了环境
在计算机当中环境就是一块内存的数据。
环境是有作用范围的,eg:武汉周边的建设一般只服务武汉生活的人们,这就是作用域。作用域是闭包的基础
环境存在的价值是被需要,当环境不被需要的时候就会被回收
在js中全局的环境是不会被回收的,全局环境在很多时候是被依赖的
1) 函数被执行后里面的环境变量将会从内存中删除。下面函数在每次执行后将删除函数内部的 total 变量。
function count(){
let total=0
}
count()
函数在每次执行的时候都会创建一个环境,都会产生一个新的内存地址 【函数没有调用就不会开辟内存空间或者称之为环境】
作用域链只向上查找,找到全局 window 即终止,应该尽量不要在全局作用域中添加变量。
2) 如果子函数被使用时父级环境将被保留
function mushu () {
let n = 1
return function sum () { // 当有return之后,里面的数据就在一直被使用 就不会被摧毁
let m = 1
return function show () {
console.log(++m) // 2 3
console.log('n', ++n) // 2 3
}
show()
}
}
let a = mushu()()
a()
a()
// 调用了两次,被外部引用只会数据不会被销毁,所以一直进行了累加
函数定义的数据,其作用域是函数及其子函数,子函数中的数据不会向父级进行传递,向父级进行传递,有可能会覆盖父级的数据
3) let/const
使用 let/const
可以将变量声明在块作用域中(放在新的环境中,而不是全局中)
let 块作用域中
var 函数作用域护着
{
let a = 9;
}
console.log(a); //ReferenceError: a is not defined
if (true) {
var i = 1;
}
console.log(i);//1
//**************************其他例子**********************
let arr = [];
for (let i = 0; i < 10; i++) {
arr.push((() => i));
}
console.log(arr[3]()); //3 如果使用var声明将是10
2.闭包
闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。
直观的说就是形成一个不销毁的栈环境。
闭包可以实现属性的私有化
3.闭包使用
闭包指子函数可以访问外部作用域变量的函数特性,即使在子函数作用域外也可以访问。如果没有闭包那么在处理事件绑定,异步请求时都会变得困难。
- JS 中的所有函数都是闭包
- 闭包一般在子函数本身作用域以外执行,即延伸作用域
eg:1
let lessons = [
{
title: "媒体查询响应式布局",
click: 89,
price: 12
},
{
title: "媒体查询响应式布局",
click: 84,
price: 120
}, {
title: "Flex布局",
click: 68,
price: 90
}, {
title: "你好呀!!",
click: 15,
price: 34
},
{
title: "hello world",
click: 89,
price: 67
}
]
function bwtween (a, b) {
return function (v) { // 这里的v 就是数组里面的元素
//function (v)这是between函数的子函数 利用闭包特性可以访问到父级的变量
return v.price >= a && v.price <= b
}
}
console.table(lessons.filter(bwtween(50, 100)));
eg2:实现属性私有
// 普通形式 统计函数调用的次数
let i = 0
function fn () {
i++
console.log(`函数调用了${i}次`)
}
function count () {
let i = 0
function fn () {
i++
console.log(`函数调用了${i}次`)
}
return fn
}
const fun = count()
4.闭包内存泄漏的问题
function fn () {
let count = 1
function fun () {
count++
console.log(`函数被调用了${count}次`) //函数被调用了2次 函数被调用了3次
}
return fun
}
// res是一个全局变量,代码执行完成之后不会立即销毁,并且res调用了fn函数,fn调用了fun,fun里面使用到了count,count被引用就不会被回收,所以一直存在
// 此时:闭包引起了内存泄露
const res = fn()
res()
res()
//注意不是所有的内存泄露都要手动回收,react中的很多闭包不能被回收
上级作用域会为函数保存数据,从而造成的如下所示的内存泄漏问题
<body>
<div desc="annanan~~">周日了</div>
<div desc="hahahahah!!!">我也不知道!! </div>
</body>
<script>
let mushu = document.querySelectorAll("div")
mushu.forEach(function (item) {
let desc = item.getAttribute("desc")
item.addEventListener("click", function () { // 这个函数是事件处理函数,是一直存在的,
// 根据闭包的特性父级作用域的元素也会一直存在,
console.log(item.getAttribute("desc")) // getAttribute 属性名
console.log(item)
})
})
下面通过清除不需要的数据解决内存泄漏问题
<body>
<div desc="annanan~~">周日了</div>
<div desc="hahahahah!!!">我也不知道!! </div>
</body>
<script>
let mushu = document.querySelectorAll("div")
mushu.forEach(function (item) {
let desc = item.getAttribute("desc")
item.addEventListener("click", function () { // 这个函数是事件处理函数,是一直存在的,
// 根据闭包的特性父级作用域的元素也会一直存在,
// console.log(item.getAttribute("desc")) // getAttribute 属性名
console.log(desc)
console.log(item)
})
item = null
})
</script>
5.闭包一定存在内存泄露吗?闭包一定有return?
闭包可能引起内存泄露,但是不一定所以的内存泄露都会被回收
答案是不一定
闭包经常被函数包裹,里面的变量是局部的,外面不能直接使用,所以我们会用到return,将函数返回出去,外部可以使用内部的变量,但是不能修改里面的变量。