目录
闭包
是一种函数的高阶应用
官方 : 函数内的函数
私人解释 :
需要一个不会被销毁的函数执行空间
函数内直接或间接的返回一个新函数
内部函数 使用 外部函数 的 私有变量
我们就说 内部函数 是 外部函数 的 闭包函数
认识函数 :
+ 函数分成两个阶段 :
1. 函数定义阶段
2. 函数调用阶段
浏览器的内存使用情况 :
+ 浏览器在运行的时候, 会把电脑分配的内容进行再次分配
+ 会有两个空间关联比较紧密 :
1. 浏览器的 存储空间 - 关联着函数的 定义阶段
2. 浏览器的 运行空间 - 关联着函数的 调用阶段
函数的两个阶段分别做了什么事情 :
1. 函数定义阶段 :
1-1. 在 堆内存 中开辟一段存储空间, 用来保存函数
1-2. 把 函数体内的代码, 一模一样的复制一份, 以字符串的形式保存在这个函数空间内
=> 此时任何变量不会进行解析
1-3. 把函数 堆内存 中的存储空间地址赋值给变量
2. 函数调用阶段 :
2-1. 按照 变量名(函数名) 内, 存储的地址找到函数 堆内存 中对应的存储空间
2-1. 在 调用栈(运行空间) 内, 再次开辟一个新的函数执行空间地址,
把原始函数内的代码复制一份一模一样的到新的执行空间地址
2-3. 在 新的 执行空间地址内 先进行形参赋值
2-4. 在 新的 执行空间地址内 后进行函数内的预解析
2-5. 在 新的 执行空间地址内 从上到下依次把函数体内的代码当做 js 代码执行一遍
2-6. (等到函数所有代码执行完毕)会把 开辟在调用栈 内的 执行空间 销毁
// 函数的两个阶段
// 1. 函数定义阶段
function fn(a, b) {
// 如果是先形参赋值
// 那么函数 fn 执行的时候, 先给 a 赋值为 10
// 然后在预解析的时候, 给 a 变量赋值为一个函数
// 然后执行代码, 打印的 a 是 函数体
// 如果是先预解析
// 那么函数 fn 执行的时候, 先给 a 赋值为一个函数
// 然后在进行形参赋值的时候, 给 a 赋值为 10
// 那么执行代码, 打印的 a 是 10
console.log(a) // 控制台打印 => ƒ a() {}
// 是先形参赋值 => 打印的 a 是 函数体
console.log(b) // 控制台打印 => 20
function a() {}
}
// 2. 函数调用阶段
fn(10, 20)
函数的特点 :
1. 保护私有变量
=> 因为每一个函数会生成一个独立的私有作用域
=> 在函数内定义的变量, 我们叫做 私有变量
=> 该变量只能在该函数作用域及下级作用域内使用, 外部不能使用
2. 函数定义时不解析变量
=> 函数定义的时候, 函数体内的代码完全不执行
=> 任何变量不做解析
=> 直到 执行 的时候才会 解析变量
// 1. 保护私有变量
function fn() {
// 这个 num 就变成了 私有变量
var num = 100
// 只能在这个 fn 区间内 使用
}
fn()
console.log(num)
// 2. 函数定义时不解析变量
var num = 100
function fn() {
// 使用 num 变量的值, 因为自己作用域内没有
// 所以使用的是全局变量 num
console.log(num) // 控制台打印 => 200
// 所以 => 函数在定义阶段, 不解析变量
}
num = 200
// 函数在定定义阶段的时候, 如果解析了变量
// 那么函数体内保存的代码就是 console.log(100)
// 这里执行的时候, 出现的就是 100, num = 200 这句代码不会生效
// 函数在定义阶段的时候, 如果没有解析变量
// 那么函数体内保存的代码就是 console.log(num)
// 在这里调用的时候, 因为上一行把 num 修改为 200 了, 所以出现的就是 200
fn()
函数的两个阶段 :
一个不会被销毁的函数执行空间 :
+ 当一个函数内返回了一个 复杂数据类型
+ 并且该复杂数据类型, 在函数外部有变量接受的情况
+ 此时函数执行完毕的执行空间, 不会被销毁掉
+ 特点: 延长变量的生命周期
function fn() {
const obj = { name: 'Jack', age: 18, gender: '男' }
// 在函数内返回了一个 复杂数据类型 obj
// 把一个复杂数据类型当做 fn 函数的返回值
return obj
}
// res 接受的就是 fn 函数内执行后的返回值内容
// 也就是 fn 函数内返回的 复杂数据类型( obj 对象 )
// 当函数体内的 return 执行完毕
// 函数就已经执行完毕了, 此时应该销毁函数的执行空间了
// 为了保证今后 res 随时可以访问到 函数内的 obj 空间
// 所以, 函数的执行空间就不允许被销毁
const res = fn()
// 如何让这个空间销毁
// 让外部接受的变量, 不在指向内部的复杂数据类型
// 只要你再次给 res 进行赋值的时候
// fn 之前的执行空间就销毁了
res = null
闭包的作用 :
1. 可以延长变量的声明周期
=> 因为是一个不会销毁的函数空间
=> 优点: 变量声明周期延长了
=> 缺点: 需要一个 闭包 结构
2. 可以在函数外部访问函数内部的私有变量
=> 需要利用闭包函数
=> 优点: 访问和使用变得更加灵活了
=> 缺点: 需要一个 闭包 结构
3. 可以保护变量私有化
=> 因为只要是函数, 就可以保护私有变量
=> 优点: 变量不会污染全局
=> 缺点: 外部没有办法访问, 如果想要使用, 就得需要写一个 闭包 结构
闭包的缺点 :
+ 内存泄漏
+ 因为闭包的形成必须伴随一个不会销毁的空间
+ 当闭包过多的时候, 就会造成内存过满的情况
+ 如果闭包继续增加, 就会导致内存溢出
// 闭包
function fnA() {
// 外部函数的 私有变量
let num = 100
// 私有函数
function fnB() {
// 内部函数使用着外部函数的私有变量
console.log(num) // 控制台打印 => 100
return num
}
// 返回的是一个复杂数据类型
// 并且是一个函数
return fnB
}
// 外部接受
const res = fnA()
// 需求: 在全局访问 fn 函数的 num 变量
// 因为作用域的原因, 只能在 fn 函数内部才可以访问 num 变量
// 全局不能访问
// console.log(num) // 报错 => num is not defined
// 当我调用 res 这个函数的时候
// 你在函数 fnA 的外面是没有办法拿到 num 这个私有变量的
// res 因为是 fn 函数的内部函数, res 可以访问到 fn 函数的私有变量
// n 接受的就是 fnA 函数内定义的 私有变量 num 的值
const n = res()
console.log(n) // 控制台打印 => 100
间接返回一个函数 :
闭包 - 间接返回一个函数 :
+ 在闭包内, 不是直接返回 函数数据类型
+ 而是返回一个 对象 或者 数组 数据类型
+ 在对象内或者数组内, 书写函数数据类型
function fn() {
// 准备私有变量
let num = 100
let num2 = 200
// 准备一个对象数据结构
const obj = {
getNum: function () { return num },
getNum2: function () { return num2 },
setNum: function (val) { num = val }
}
// 返回这个对象数据结构
return obj
}
// o 接受的是 fn 函数内的 obj 数据类型
// o 内部有一个叫做 getNum 的函数
const o = fn()
console.log(o) // {getNum: ƒ, getNum2: ƒ, setNum: ƒ}
// 当我需要访问 fn 内的 num 变量的时候
const n = o.getNum()
console.log(n) // 100
// 当我需要修改 fn 函数内部的 num 变量的时候
o.setNum(1000)
// 再次访问闭包内的 num
console.log(o.getNum()) // 1000
// =============================================
const o2 = fn()
console.log(o2.getNum()) // 100
function outer() {
// 准备私有变量
var n = 100
var s = 'hello world'
// 准备一个对象数据结构
const obj = {
getN: function () { return n },
getS: function () { return s },
setN: function (val) { n = val }
}
// 返回这个对象数据结构
return obj
}
const res = outer()
// 我需要用到 outer 内的 n 变量的时候
console.log(res.getN())
// 我需要用到 outer 内的 s 变量的挥手
console.log(res.getS())
// 我调用 res 内的 setN 函数的时候
res.setN(200)
// 再次访问 outer 内的 n 变量的时候
console.log(res.getN())
// ==========================================
const res2 = outer()
console.log(res2.getN()) // 100
( 1 )
( 2 )
( 3 )
( 4 )
闭包 _ 语法糖 :
闭包 - 语法糖 :
+ 在外部函数内返回的对象, 以 getter ( 获取器 ) 和 setter ( 设置器 ) 的形式
+ 返回应该返回的闭包函数
+ 语法:
{
get 名字 ( ) { }
set 名字 ( ) { }
}
+ 作用:
=> 把 getter 获取的 名字 当做一个对象成员名字
=> 只不过值是一个 ( ... ) 的形式
=> 因为是从后面的函数内返回值得到的数据
function fn() {
let num = 100
return {
// 当你使用 getter 和 setter 的时候, 建议把两个函数名
// 书写成你要操作的那个变量的名字
get num () { return num }, // 等价于 num: function () { return num }
set num (val) { num = val }
}
}
// res 接受的是一个 fn 函数内的 对象
const res = fn()
console.log(res)
console.log(res.num) // 100
// 当你给 res.num 进行赋值的时候
// 就是在调用 setter 设置器的函数
// 1000 就是给 val 进行赋值
res.num = 1000
console.log(res.num) // 1000
闭包 _ 简图 :
闭包 _ 面试题 :
function fun(n, o) {
console.log(o)
const obj = {
fun: function(m) {
return fun(m, n)
}
}
return obj
}
var a = fun(0) // undefined
a.fun(1) // 0
a.fun(2) // 0
a.fun(3) // 0
// undefined 0 1 2
var b = fun(0).fun(1).fun(2).fun(3) //
// undefined 0
var c = fun(0).fun(1) //
c.fun(2) // 1
c.fun(3) // 1
( 1 ) :
( 2 ) :
( 3 ) :