闭包作用与使用整理

210 阅读2分钟

这是我的第一篇掘金博客,开启掘金写作之路。

闭包

闭包可以理解为:

  1. 访问另一个函数的作用域的变量或函数;
  2. 延长变量生命周期(异步环境使用)剖析 ;
  3. 封装对象的私有属性和私有方法(进阶)

闭包用途示例

访问另一个函数的作用域的变量或函数:实现一个计数器

let current = 0
function add(){
    current += 1
}
add()
add()
add()
console.log(current) // 3

以上示例看似实现了,但是我们可以在代码中的任意一个位置改变current的值,似乎并不完美,那么放到函数里面会怎么样

function add(){
    let current = 0
    current += 1
}
add()
add()
add()
console.log(current)  //报错 current is not defined

这是为什么呢?因为ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let,这里不做赘述,详情请戳:let 和 const 命令

所以我们该怎么实现这个计数器呢?这时候我们就要用到闭包去实现计数器这个问题,代码如下

// 同时实现计数增加,计数减少,获取计数当前的值
const c = (function () {
    let current = 0
    return {
        // 增加
        add: function () {
            current += 1
        },
        // 减少
        cut: function () {
            current -= 1
        },
        // 获取当前值
        value: function () {
            return current
        }
    }
})()
console.log(c.value()) // 0 
c.add()
c.add()
console.log(c.value()) // 2 
c.cut()
console.log(c.value()) // 1 

延长变量生命周期(异步环境使用)剖析:

看到很多关于闭包用途的博客发现,常见的闭包使用举例如下

<div>
    <li>1</li>
    <li>2</li>
    <li>3</li>
</div>
var liList = document.querySelectorAll('li')
for (var i = 0; i < liList.length; i++) {
    liList[i].onclick = function () {
        console.log(i)
    }
} 

最终结果是不管单击哪个li元素都会打印 3,为什么呢?

因为var申明变量作用域的问题,var申明变量,变量申明提前导致,闭包可以解决这个问题,示例如下

// 循环遍历添加点击事件
var liList = document.querySelectorAll('li')
for (var i = 0; i < liList.length; i++) {
    (function (i) {
        liList[i].onclick = function () {
            console.log(i)
        }
    })(i)
} 
// 依次点击,分别打印  0  1  2 

但是还有一个更简便的方法,使用let申明变量,示例如下

const liList = document.querySelectorAll('li')
for (let i = 0; i < liList.length; i++) {
    liList[i].onclick = function () {
        console.log(i)
    }
}  
// 依次点击,分别打印  0  1  2 

这是为什么呢?因为ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let,这里不做赘述,详情请戳:let 和 const 命令

封装对象的私有属性和私有方法(进阶):

实现只读,只写,可读可写 实现代码如下

const property = (function (opts) {
    let value = '闭包'
    const hasSet = opts.hasSet || false
    const hasGet = opts.hasGet || false
    const res = {}
    if (hasGet) {
        res.get = function () {
            return value
        }
    }
    if (hasSet) {
        res.set = function (v) {
            value = v
        }
    }
    return res
})({ hasSet: true, hasGet: true })
console.log(property.get()) // 闭包
property.set('闭包DEMO')
console.log(property.get()) // 闭包DEMO

闭包的原理其实还是作用域

使用闭包的优点是可以避免全局变量污染

闭包的缺点:1、内存泄露;2、存在循环引用;

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。