闭包
闭包(Closure):函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包
- 特点:可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员
- 好处:延长了外部函数中成员变量的作用范围
- 本质:函数在执行的时候会放到一个执行栈上,当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员
通过示例演示:
// 函数作为返回值
<!--场景一-->
// makeFn函数是之前我们写过的一个函数作为返回值的示例,
// 当我们把其内部的return部分去掉后,此时makeFn就是一个在普通不过的函数,
// 此时当我们执行完makeFn时成员变量msg就会被释放
<!--场景二-->
// 当不去掉return时,执行makeFn后得到的结果是makeFn返回的一个函数
// 而在此函数中使用(或持有)了外部函数(makeFn)的成员变量
// 当makeFn执行完成返回内部函数后由于内部函数次有msg,此时msg不会被释放
// 此时我们在另一个作用域调用了一个函数的内部函数并访问到了该函数作用域中的成员
// 也就是闭包延长了一个函数内部成员的作用范围
function makeFn() {
let msg = 'Hello function'
return function() {
console.log(msg)
}
}
const fn = makeFn()
fn()
// once函数
// 根据闭包的特点我们看下之前实现的once函数
// 函数执行完成后返回一个函数,返回的函数持有了执行函数的成员,那么once符合这个特点
<!--分部示例-->
// 当我们调用once函数时,创建局部变量done,返回一个函数
// 通过pay变量指向once返回的内部内部函数(即外部作用域中的pay对once函数内部有引用)
// 此时当执行完once后done不会被释放
function once(fn) {
let done = false
return function() {
if (!done) {
done = true
return fn.apply(this, arguments)
}
}
}
const pay = once(function(money) {
console.log('支付:$money')
})
pay(10)
闭包案例分析(执行顺序以及调用栈)
// 需求:简单计算员工工资,工资分为基本工资+绩效,按照工资标准可将工作分级
// 如:12000位1级, 15000位2级等等
// 工资示例(基本工资, 绩效):(12000, 2000)、(15000, 3000)、(15000, 5000)
// 分析:按照正常逻辑,我们可以定义一个函数 function getSalary(salary, performance) {}
// 此时我们可以通过getSalary多次调用配合传递两个参数来完成
// 思考:当数据多时有没有可优化的呢? 可以通过分级来优化每次传递参数个数
function makeSalary(base) {
return function (performance) {
return base + performance
}
}
// 按照工资分级
const salaryLevel1 = makeSalary(12000)
const salaryLevel2 = makeSalary(15000)
// 使用:此时我们只需要按照级别只传入绩效即可
console.log(salaryLevel1(2000))
console.log(salaryLevel2(3000))
console.log(salaryLevel2(5000))
// 以上步骤我们可以在浏览器中运行代码通过开发这工具,
// 添加断点来观察调用栈(Call Stack)和作用域(Scope)内变量的变化,
// 通过变化情况能很清楚看到makeSalary在调用栈中的调用、移除操作
// 以及执行salaryLevel1和salaryLevel2时调用闭包(Closure)的情况