前端 JavaScript 之 闭包 _ 讲解版

30 篇文章 0 订阅
10 篇文章 0 订阅

目录

闭包

 认识函数 : 

函数的两个阶段分别做了什么事情 : 

函数的特点 : 

一个不会被销毁的函数执行空间 :

闭包的作用 :

闭包的缺点 :

间接返回一个函数 : 

闭包 _ 语法糖 : 

闭包 _ 简图 : 

闭包 _ 面试题 : 


闭包

是一种函数的高阶应用
官方 :  函数内的函数 
私人解释 :
需要一个不会被销毁的函数执行空间
函数内直接或间接的返回一个新函数
内部函数 使用 外部函数 的 私有变量
我们就说 内部函数 是 外部函数 的 闭包函数 


 认识函数 : 

+ 函数分成两个阶段 :

  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 ) : 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值