目录
闭包
?认识函数 :?
函数的两个阶段分别做了什么事情?:?
函数的特点?:?
一个不会被销毁的函数执行空间 :
闭包的作用 :
闭包的缺点 :
间接返回一个函数 :?
闭包 _?语法糖 :?
闭包 _ 简图 :?
闭包 _ 面试题 :?
闭包
是一种函数的高阶应用 官方 :? 函数内的函数? 私人解释 : 需要一个不会被销毁的函数执行空间 函数内直接或间接的返回一个新函数 内部函数 使用 外部函数 的 私有变量 我们就说 内部函数 是 外部函数 的 闭包函数?
?认识函数 :?
+?函数分成两个阶段 :
??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 ) :?
|