了解闭包:
一个函数的高级应用
官方的定义: 闭包就是指有权访问另一个函数作用域中的变量的函数
函数的两个步骤
1. 函数定义
1-1. 在堆里面开辟一个空间
1-2. 把函数体内的所有代码当作字符串存储在这个空间中
1-3. 把空间地址赋值给栈里面的变量(函数名)
2. 函数调用
2-1. 按照存储的地址找到 函数存储空间
2-2. 再 调用栈(不是栈内存) 里面再次开辟一个 函数执行空间
2-3. 在函数执行空间内进行 形参复制
2-4. 在函数执行空间内进行 预解析
2-5. 把 函数存储空间 的代码复制一份拿到 函数执行空间 里面执行
2-6. 代码全部执行完毕, 这个新开辟的函数执行空间销毁
function fn() {
var num = 100;
console.log('hello world');
num++;
console.log(num);
}
fn();
fn();
复制代码
一个不会被销毁的函数执行空间
函数的每一次执行会创建一个函数执行空间
当函数内部返回一个 复杂数据类型 的时候, 并且函数外部还有变量在接收
这个函数执行空间不会被销毁,从而延长了函数的生命周期。
function fn() {
var num = 100
console.log('hello world')
// 函数内部返回一个复杂数据类型
return {}
}
// obj 接收的 fn 函数执行空间内部的 对象
const obj = fn()
// 创建了一个新的对象返回
const obj2 = fn()
console.log(obj)
复制代码
形成闭包的条件
1. 需要有一个不会被销毁的函数执行空间
2. 函数内部 直接 或者 间接 的返回一个函数
3. 内部函数操作(访问, 赋值)着外部函数的变量
当以上三个条件都满足的时候
内部的函数叫做外部函数的 闭包函数
闭包的作用
1. 定义函数变量在函数内部就是私有变量
2. 利用闭包函数访问帮助函数外部访问函数内部的私有变量
闭包的特点
1. 保护变量私有化
优点: 不去污染全局
缺点: 外部不能访问, 需要闭包函数
2. 可以在函数外部访问函数内部的变量
优点: 不局限于私有变量
缺点: 外部访问需要闭包函数
3. 变量的生命周期
优点: 变量的声明周期被延长了
缺点: 一个不会被销毁的函数空间
致命缺点: 一个不会被销毁的函数空间会导致内存泄漏
所以要慎用闭包
function fn() {
var num = 100
// fn 函数内部返回了一个 a 函数
return function a() {
// 访问外部函数 fn 的私有变量 num
// 并且把 num 的值返回
return num
}
}
// 此时, res 接收的是 - fn 函数内部的 a 函数
// 我们管 res 或者 a 叫做 fn 的闭包函数
const res = fn()
// 返回 100
// fn 函数的私有变量 num
console.log(res())
复制代码
既然上面写到了闭包的缺点和比较致命的问题,接下来写一下如何销毁闭包。
闭包销毁
1. 不销毁的空间
2. 返回函数
3. 内部函数引用外部函数变量
你想销毁闭包的时候, 只要这个不销毁的空间不存在了, 闭包就没了
什么是不销毁的空间:
返回复杂数据类型的空间
收有外部变量的空间
如何销毁空间
外部变量不再接收
外部接收的变量被重新赋值
function fn() {
var num = 100
return function a() {
return num
}
}
// res 是 fn 的闭包函数
let res = fn()
// res 存储的不再是 fn 函数内部返回的 函数了
// fn 的执行空间销毁了
res = 100
复制代码
闭包的应用
沙箱模式
是设计模式的一种: 为了解决特定问题给出的简洁而优化的解决方案
目的是为解决 变量私有化以后的访问和操作问题。
function fn() {
var num = 100
var str = 'hello world'
function inner1() {
console.log('我是 inner1 函数')
}
// 间接返回一个函数
return {
getNum: function () {
return num
},
setNum: function (val) {
num = val
}
}
}
// res 接收的是 ? 一个对象, 对象里面有函数, 对象里面的函数, 使用着外部函数的变量
const res = fn()
// 你想拿到 fn 函数内部的 100
console.log(res.getNum())
// 利用闭包函数修改 fn 函数内部的私有变量 num
res.setNum(200)
// 当我再次利用 getNum 闭包函数去访问 fn 里面得 num 的时候
console.log(res.getNum())
复制代码
闭包的语法糖
语法糖: 使用起来方便, 但是看起来不舒服
闭包的语法糖: getter 获取器和 setter 设置器
作用: 把你制作闭包想做的事情, 伪装成了一个对象内部的成员
使用方法:
1. 要形成闭包
2. 返回值是一个对象
3. 在对象里面以 getter 和 setter 的语法形式返回函数
{
get 函数名() {},
set 函数名() {}
}
function fn() {
var num = 100
return {
getNum () {
return num
},
setNum (val) {
num = val
}
}
}
const res = fn()
console.log(res.getNum())
res.setNum(500)
console.log(res.getNum())
function fun() {
var num = 100
return {
get num() {
return num
},
set num(val) {
num = val
}
}
}
// res 接收的还是 fun 里面返回的对象
// 对象里面除了有 getNum 函数
// 还把 getNum 作为一个成员存储起来了
// 把 getNum 的返回值作为 getNum 这个成员的值使用
// 把原先的 getNum 函数作为了获取器的存在
// 对象里面会有一个 setNum 设置器
// 当你给 setNum 设置器赋值的时候, 就是在调用 setNum 函数, 传递参数
const res2 = fun()
console.log(res2.num)
res2.num = 500 // 相当于原先的 res2.setNum(500)
console.log(res2.num)
console.log(res2)
复制代码
评论