闭包:简单来说,就是为变量提供一个安全的、外部无法访问的作用域
本文:将通过一个累加器的案例帮助理解闭包
实现一个累加器
现在要做一个累加的功能。但是变量命名很容易重复,下面的代码不建议使用,因为变量很容易被别人定义,导致命名污染。
var count = 0
function add() {
return ++count
}
在函数内部定义函数
使用函数嵌套函数的形式
function add() {
var count = 0 // 私有变量,外界无法访问
return function () { // 这是一个闭包函数
return ++count
}
}
// count是add返回的函数
const count = add()
// 所以必须加括号才能得到count
// 每次使用count()都会进行累加
console.log(count()) // 1
console.log(count()) // 2
// 注意这里如果使用两次add()都是得到1
设置形式参数
function add(count = 0) { // 如果不传默认为0
return function () {
return ++count
}
}
const f = add(100)
console.log(f()) // 101
console.log(f()) // 102
进阶:以对象的形式返回
如果功能较多,建议采用模块化的形式以增加代码的维护性
function add(count) {
return {
add: function () {
return ++count
},
double: function () {
count *= 2
},
reset: function () {
count = 0
},
print: function () {
console.log(count)
},
}
}
const f = add(100)
f.add()
f.add()
f.add()
f.double()
f.print() // 输出 206
闭包的场景(一般写法)
模拟查询和更新用户的消费积分的场景
// 用户默认有50个积分,默认的权限等级为1,默认每次消费会增加5个积分
function userInfoPoints(point = 50, roleLevel = 1, num = 5) {
return {
// 查询用户的当前积分
get: function () {
return point
},
// 每次增加num个积分
add: function () {
point += num
},
// 对当前的积分进行翻倍
addDouble: function () {
point *= 2
},
// 设置用户的当前积分
setPoints: function () {
// 如果达到管理员的权限等级
// 实际项目中可能更复杂!!!
if (roleLevel >= 5) {
point = 500
alert('积分设置成功')
} else {
alert('权限不足')
}
},
// other
otherFunction: function () {
// 积分兑换活动...
},
// 输出
print: function () {
console.log(`当前消费积分的余额是:${point}`)
},
}
}
const user1 = userInfoPoints(100, 5, 5)
user1.add()
user1.add()
user1.print() // 输出 110
user1.setPoints()
user1.print() // 输出 500
// 再创建一个用户
const user2 = userInfoPoints()
user2.print() // 输出 50
user2.add()
user2.addDouble()
user2.print() // 输出 110
// 注意:这里的两个对象user1和user2并不影响,是完全独立的
运行结果如下
立即执行函数写法
;(function (w) {
w.userInfoPoints = function (point = 50, roleLevel = 1, num = 5) {
return {
// 查询用户的当前积分
get: function () {
return point
},
// 每次增加num个积分
add: function () {
point += num
},
// 对当前的积分进行翻倍
addDouble: function () {
point *= 2
},
// 设置用户的当前积分
setPoints: function () {
// 如果达到管理员的权限等级
// 实际项目中可能更复杂!!!
if (roleLevel >= 5) {
point = 500
alert('积分设置成功')
} else {
alert('权限不足')
}
},
// other
otherFunction: function () {
// 积分兑换活动...
},
// 输出
print: function () {
console.log(`当前消费积分的余额是:${point}`)
},
}
}
})(window, 100, 5, 10)
const user1 = window.userInfoPoints(100, 5, 5)
user1.add()
user1.add()
user1.print() // 输出 110
user1.setPoints()
user1.print() // 输出 500
// 再创建一个用户
const user2 = window.userInfoPoints()
user2.print() // 输出 50
user2.add()
user2.addDouble()
user2.print() // 输出 110
// 注意:这里的两个对象user1和user2并不影响,是完全独立的
运行结果如下
总结
首先来看下MDN官方的描述
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
总结:在一个作用域中可以访问另一个函数内部的变量
作用:实现变量的私有化,从而避免全局变量的污染
特点:
- 函数嵌套函数:闭包必须有函数嵌套函数的结构。
- 内部函数可以访问外部函数的变量:内部函数可以访问外部函数的变量,即使外部函数执行完毕后,这些变量仍可以被访问。
- 外部函数返回内部函数:外部函数必须返回内部函数,才能形成闭包。
注意事项
- 内存泄漏:闭包会一直持有外部作用域中的变量和参数,如果不及时释放,就会导致内存泄漏。
- 性能问题:由于闭包会持有外部作用域中的变量和参数,因此会占用更多的内存和 CPU 资源。
- 作用域链问题:由于闭包可以访问外部作用域中的变量和参数,因此会导致作用域链的变长,从而影响代码的执行效率。