这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战
闭包是什么?对页面有什么影响?
- 闭包有三个特性
- 函数嵌套函数
- 函数内部可以引用外部的参数与变量
- 参数和变量不会被垃圾回收机制回收
- 闭包的缺点
- 常驻内存,增大内存使用量,使用不当会造成内存泄漏
- 闭包的优点
- 变量长期驻扎在内存中
- 避免全局变量的污染
- 私有成员的产生
为什么要使用闭包?
- 设计私有方法和变量
- 避免全局变量污染
- 希望变量长期驻扎在内存中
应用
- 模块化代码,减少全局变量的污染
// 模块化代码,减少全部变量的污染
var abc = (function () {
var a = 1
return function () {
a++
alert(a)
}
})()
abc() // 2
abc() // 3
- 私有成员的存在(不想要方法外面访问的成员)
// 私有成员
var a = function () {
var b = 1
function cc() {
console.log(b++)
}
function dd() {
console.log(b++)
}
return {
c:cc,
d:dd
}
}
var m = a()
m.c() // 1
m.d() // 2
- 使用匿名函数实现累加
// 使用匿名函数使用累加
function box () {
var num = 1
return function () {
num++
console.log(num)
}
}
var add = box()
add() // 2
add() // 3
- 在循环中直接找到对应元素的索引
// 在循环中直接找到对应元素的索引
// 方法1
window.onload = function () {
var li = document.getElementsByTagName('li')
for (var i = 0; i < li.length; i++) {
li[i].index = i
li[i].onclick = function (){
alert(this.index)
}
}
}
// 方法2
window.onload = function () {
var li = document.getElementsByTagName('li')
for (var i = 0; i < li.length; i++) {
li[i].onclick = (function (a){
return function () {
alert(a)
}
})(i)
}
}
增加
- 其实你写的每一个
js函数都是闭包,一个js函数的顶层作用域就是window对象,js的执行环境本身就是一个scope(浏览器的window/node的global),我们通常称之为全局作用域。 - 每个函数,不论多深,都可以认为是全局
scope的子作用域,可以理解为闭包。
哪些操作会造成内存泄漏?
- 内存泄露:不再用到的内存,没有及时释放
- 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为0(没有其他对象引用过该对象),或该对象的惟一引用是循环的,那么该对象的内存即可回收
操作:
- 意外的全局变量:全局变量引用、变量未申明。当全局变量使用不当,没有及时回收(手动赋值
null),或者拼写错误等将某个变量挂载到全局变量时,也就发生内存泄漏了 - 被遗忘的计时器或回调函数:
DOM元素的生命周期正常是取决于: 是否挂载在DOM树上,当从DOM树上移除时,也就可以被销毁回收了。- 但如果某个
DOM元素,在js中也持有它的引用时,那么它的生命周期就由js和是否在DOM树上两者决定了,记得移除时,两个地方都需要去清理才能正常回收它 setTiemout也会有同样的问题,所以,当不需要interval或者timeout时,最好调用clearInterval或者clearTimeout来清除
// 也就是调用了 clearInterval。如果没有被 clear 掉的话,就会造成内存泄漏。不仅如此,如果回调函数没有被回收,那么回调函数内依赖的变量也没法被回收。所以在上例中,someResource 就没法被回收。
// 获取数据
let someResource = getData()
setInterval(() => {
const node = document.getElementById('Node')
if(node) {
node.innerHTML = JSON.stringify(someResource))
}
}, 1000)
- 脱离
DOM的引用:使用变量缓存DOM节点引用后删除了节点,如果不将缓存引用的变量置空,依然进行不了 GC,也就会出现内存泄漏。
<div id="root">
<ul id="ul">
<li></li>
<li></li>
<li id="li3"></li>
<li></li>
</ul>
</div>
<script>
let root = document.querySelector('#root')
let ul = document.querySelector('#ul')
let li3 = document.querySelector('#li3')
// 由于ul变量存在,整个ul及其子元素都不能GC
root.removeChild(ul)
// 虽置空了ul变量,但由于li3变量引用ul的子节点,所以ul元素依然不能被GC
ul = null
// 已无变量引用,此时可以GC
li3 = null
</script>
- 闭包: 不正当的使用闭包可能会造成内存泄漏,记得最后将对象置空
// 1.闭包
function fn () {
var num = new Array(10).fill('ok')
return function () {
console.log(num)
}
}
let fn1 = fn()
fn1()
// 函数调用后,将外部的引用关系置空
// 就可以释放函数和外部函数的num变量
fn1 = null
拓展
- 垃圾回收的必要性(引自《JavaScript权威指南(第四版)》)
由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
- JS垃圾回收的机制:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。
- 不使用的对象写成
null