什么是闭包?闭包的应用场景?闭包为什么会造成内存泄露?怎样解决?
程序员鱼乐,全网同名,目前全网粉丝2000+,我也会坚持写博客拍视频,分享编程知识与生活日常。还是个小菜狗,如有错误还请大佬们指出,欢迎大佬评论区补充知识。
闭包的概念其实很简单,一句话就能说完。但是理解起来稍稍有点抽象,看完这篇文章,闭包在你面前就是个弟弟
什么是闭包?
闭包就是一个函数可以访问另外一个函数内部的变量。
为什么需要闭包?
在js中,函数内部可以访问到全局变量,但是函数外部访问不到函数内部的变量。当我们想获得函数内部的变量时,可以让这个函数将需要的变量return出去,或者在这个函数内部写一个子函数,子函数内部也可以访问到父函数内部的变量。
例如:
// 直接return
function fn1() {
let num = 1
return num
}
console.log(fn1()) // 1
// 子函数
function fn2() {
let num = 1
function fn3 (){
console.log(num)
}
fn3()
}
fn2() // 1
也许你会问,既然这两个方法都可以使一个函数访问到另一个函数内部的变量,那还要闭包干嘛?别急,接着往下看...
// 直接return
function fn1() {
let num = 1
return num++
}
console.log(fn1())
console.log(fn1())
console.log(fn1())
console.log('-----------------')
// 子函数
function fn2() {
let num = 1
function fn3 (){
console.log(num++)
}
fn3()
}
fn2()
fn2()
fn2()
console.log('-----------------')
// 闭包
function fn4() {
let num = 1
function fn5 (){
console.log(num++)
}
return fn5
}
const fn6 = fn4()
fn6()
fn6()
fn6()
分析上面代码,我们可以发现使用闭包,会让局部变量类似于全局变量,具有持久性。所以闭包既可以解决使用全局变量可能会带来的变量污染问题,又可以解决使用局部变量用完后会被垃圾回收掉的问题。
我们稍后聊聊这个垃圾回收。
闭包的应用场景?
-
缓存(比如商品搜索)
代码:
const search = (id) => { const goodsList = [ { id: 1, name: 'A' }, { id: 2, name: 'B' } ] return ()=>{ const goods = goodsList.find((item)=>item.id === id) if(goods){ console.log(`本地缓存`) console.log(`商品名称是:${goods.name}`) }else { // 假设如果没有在goodsList找到该商品,就去调接口在数据库查询 console.log('调用接口啦!!!') const goods = {id: 3, name: 'C'} // 拿到商品后添加到goodsList goodsList.push(goods) console.log(`商品名称是:${goods.name}`) } } } const fn = search(3) fn() fn() fn() // 执行结果: // 调用接口啦!!! // 商品名称是:C // 本地缓存 // 商品名称是:C // 本地缓存 // 商品名称是:C // 分析结果: // 第一次goodsList没有id为3的商品,然后从数据库中查到后追加到了goodsList,假设如果goodsList被销毁,那么每次都要去数据库中查数据。第二次,第三次都是直接在goodsList中拿到的值,因此可以证明在函数执行完后goodsList并没有被销毁 -
实现变量的私有化
-
防抖节流(这部分我会再写一篇博客)
垃圾回收机制是什么?
垃圾回收机制其实就是引用数据类型例如函数在创建的时候会在内存占用一些空间,当这个函数执行完毕后,对应内存就会释放。
为什么闭包会发生内存泄漏?
内存泄漏其实就是因为变量的值在内存中,并且这个变量因为闭包一直被引用,垃圾回收机制不能回收。
借助chrome调试器查看内存情况
如何解决内存泄漏?
-
闭包:
将事件处理函数定义在外部,解除闭包,并且减少循环引用。 在循环中的函数表达式,能复用最好放到循环外面,使用完成后将对相应的变量置为null,解除引用
// bad for (var k = 0; k < 10; k++) { var t = function (a) { // 创建了10次 函数对象。 console.log(a) } t(k) } // good function t(a) { console.log(a) } for (var k = 0; k < 10; k++) { t(k) } t = null -
vue的全局对象:
声明的全局变量在切换页面的时候没有清空
<template> <div id="home">这里是首页</div> </template> <script> export default { mounted() { window.test = { // 此处在全局window对象中引用了本页面的dom对象 name: 'home', node: document.getElementById('home'), } }, } </script>解决方案:在页面卸载的时候顺便处理掉该引用。
destroyed () { window.test = null // 页面卸载的时候解除引用 } -
Echarts:
每一个图例在没有数据的时候它会创建一个定时器去渲染气泡,页面切换后,echarts 图例是销毁了,但是这个 echarts 的实例还在内存当中,同时它的气泡渲染定时器还在运行。这就导致 Echarts 占用 CPU 高,导致浏览器卡顿,当数据量比较大时甚至浏览器崩溃。
解决方法:加一个 beforeDestroy()方法释放该页面的 echarts 资源,使用 clear()方法则清空图例数据,能够释放内存,切换的时候就很顺畅了。
beforeDestroy () { this.chart.clear() }
总结
面试官经常会问:什么是闭包?闭包的应用场景?闭包为什么会造成内存泄露?怎样解决?他也会问你是否遇到过内存泄漏,如何解决的?
因为像Vue这种SPA单页面应用,浏览器其实不会主动去跳转和刷新的,页面内容的更新是通过vue-router实现的,所以比较容易出现内存泄漏,在开发时确实要注意,内存泄漏可能会引起浏览器卡顿,甚至崩溃!