千锋教育-做有情怀、有良心、有品质的职业教育机构

400-811-9990
手机站
千锋教育

千锋学习站 | 随时随地免费学

千锋教育

扫一扫进入千锋手机站

领取全套视频
千锋教育

关注千锋学习站小程序
随时随地免费学习课程

上海
  • 北京
  • 郑州
  • 武汉
  • 成都
  • 西安
  • 沈阳
  • 广州
  • 南京
  • 深圳
  • 大连
  • 青岛
  • 杭州
  • 重庆
当前位置:重庆千锋IT培训  >  技术干货  >  JavaScript全解析——闭包

JavaScript全解析——闭包

来源:千锋教育
发布人:lxl
时间: 2023-05-10 11:33:38

  闭包

  重新认识函数

  ●一个函数分为函数的定义和函数的执行

  函数的定义

  ●我们书写一个函数, 但是不会执行函数体内的代码

  ●那定义一个函数做了什么事情

  ○在堆内存中开辟一段存储空间

  ○把你书写在函数体内的代码, 全部以字符串的形式存储在这存储空间中, 此时不会解析变量

  ○把函数存储空间地址, 赋值给变量名(函数名)

  函数的执行

  ●使用(调用)函数, 会把函数体内的代码执行

  ●调用函数又具体做了什么事情

  ○按照变量名存储的地址, 找到函数存储空间

  ○直接开辟一个新的函数执行空间

  ○在执行空间内进行形参赋值

  ○在执行空间内进行函数内部的预解析

  ○把之前存储的代码在执行空间内完整执行一遍, 此时才会解析变量

  ○执行完毕这个开辟出来的执行空间销毁

闭包

let num = 100
function fn(a, b) {
// 证明确实没有解析 num 存储
// 如果这里在存储的时候, 是解析了 num 存储的, 那么存起来的就是 100
// 后面的 num = 200 这句代码不会影响到这里的值
// 将来打印出来就是 100
// 如果这里在存储的时候, 没有解析 num 存储, 那么存起来的就是 num 变量
// 后面的 num = 200 这句代码会影响到这里的值
// 将来打印出来就是 200
// console.log(num)
const res = a + b
console.log(res)
}
num = 200
fn(10, 20)
fn(100, 200)

   函数形参赋值和与解析先后问题

function fn(a) {
console.log(a)
function a() { console.log(111) }
}
fn(100)

   分析

  ●当函数调用的时候, 如果先进行的 形参赋值

  ○1、给 a 变量赋值为 100

  ○2、进行函数内与解析的时候, 给 a 变量赋值为一个 函数体

  ■此时给 a 变量赋值的函数体就会把 100 覆盖

  ○3、执行的第一行代码, 打印 a 变量

  ■在这里就会打印出 函数体

  ●当函数调用的时候, 如果先进行的 预解析

  ○1、进行函数内预解析的时候, 给 a 变量赋值为一个 函数体

  ○2、进行形参赋值的时候, 给 a 变量赋值为 100

  ■此时给 a 变量赋值的 100 就会把函数体覆盖

  ○3、执行的第一行代码, 打印 a 变量

  ■在这里就会打印出 100

  ●结论

  ○如果控制台打印出 100, 说明 先 预解析 后 形参赋值

  ○如果控制台打印出 函数体, 说明 先 形参赋值 后 预解析

  不会销毁的函数执行空间

  ●当你的函数内返回一个 复杂数据类型

  ●并且在函数外面有变量接受这个复杂数据类型的时候

  ●此时函数的执行空间不会销毁

  不会销毁的函数执行空间有什么作用

  ●延长了变量的生命周期

  如何能让这个空间销毁

  ●让你的外部变量指向别的位置, 只要不在保存这个内部地址

  ●这个执行空间就会被 浏览器 销毁掉

闭包

function fn() {
const num = 100
const obj = { name: 'Jack' }
return obj
}
let res = fn()
const res2 = fn()
// 销毁了执行空间
res = 200

   闭包

  定义

  ●官方定义:就是能够读取其他函数内部变量的函数

  ●个人理解:

  ○需要一个不会销毁的函数执行空间

  ○函数内 直接 或者 间接 的返回一个函数

  ○内部函数使用着外部函数的私有变量

  ○我们称内部函数是外部函数的闭包函数

  作用(优点)

  ●延长变量的生命周期

  ●在函数外部操作函数内部的私有变量

  1.直接返回一个函数

function outer() {
var num = 100
function inner() {
return num
}
return inner
}
const res = outer()
// 当你需要在 outer 外面访问 outer 内的私有变量 num 的时候
// 没有办法直接访问
// 但是你可以通过调用 res 来访问
// 因为 res 存储的就是 inner 函数地址
// 所以这里就是在调用 outer 内的 inner 函数
// 又因为 inner 内没有私有变量 num, 会自动去到 父级作用域查找
// 也就是去到 outer 内查找私有变量 num , 发现有
// 索引返回的就是 outer 函数内的 私有变量 num
// n 得到的就是 outer 内私有变量 num 的值
const n = res()

 

闭包

  2.间接返回一个函数

  ●不直接返回函数, 把函数放在一个容器内返回

function outer() {
var num = 100
var str = 'hello world'
// 如果我向对象内放置一个函数, 那么返回的虽然是对象, 但是对象内有函数
var obj = {
getNum: function () { return num },
setNum: function (val) {
// 给 num 变量进行赋值
// 自己作用域内有, 给自己的赋值, 自己没有给父级的赋值
// 因为 setNum 内没有 num 变量
// 所以, 这里的赋值实际上是给 outer 函数内的私有变量 num 赋值
num = val
}
}
return obj
}
// res 接受的就是 outer 函数内返回的 obj 对象的地址
// res 和 outer 函数内的 obj 指向一个对象存储空间
var res = outer()
// 当你调用 res.getNum() 的时候, 就是在调用 outer 函数内的 obj 里面的 getNum 函数
// 因为 getNum 函数是 outer 的子级作用域
// 所以 getNum 内没有 num 的时候, 访问的还是 outer 函数内的私有变量 num
var n = res.getNum()
console.log(n)
// 因为 res 就是 outer 函数内的 obj 对象
// 所以, res.setNum 就是 obj 内的 setNum 函数
// 调用了 obj 内的 setNum 函数, 并且给 val 赋值为了 500
// 因为 setNum 函数内是在给 outer 函数的私有变量 num 赋值
// 所以这里的 500 被赋值给了 outer 函数的私有变量 num
// 在 outer 函数外面给 outer 函数内部的 私有变量 num 的值修改了
res.setNum(500)
console.log(res.getNum())

 

闭包

  沙箱模式

  ●沙箱:

  ○就是一个箱子里面装的都是沙子

  ○筛子会一点儿一点儿的漏出来

  ○不会一下子都漏出来

  ●利用 函数内 间接返回一个函数

  ○间接就是不直接

  ○把函数包裹在另一个数据结构中返回

  ○一般是 一个函数内返回一个对象 , 对象中书写多个函数

  ○意义就是为了返回多个函数

function outer() {
let a = 100
let b = 200
let str = 'hello world'
// 创建一个对象
const obj = {
// 书写一个函数, 目的是为了获取私有变量 a
getA: function () { return a },
getB () { return b },
setA: function (val) { a = val }
}
return obj
}
// 目的是为了得到 "箱子"
const res = outer()

// 利用 "箱子" 去访问 outer 函数内的指定数据
// 需要访问 a
console.log(res.getA()) // 100
console.log(res.getB()) // 200
// 需要修改 a
res.setA(1000)
console.log(res.getA()) // 1000
// 重新做了一个 "箱子"
const res2 = outer()
console.log(res2.getA()) // 100

   沙箱模式语法糖

  ●尽可能的简化沙箱模式的语法

  ●利用的是 getter 和 setter 来进行操作数据

  ●所有的框架的底层使用的大部分是这样的操作

<script>
/*
沙箱语法糖
+ 尽可能的简化沙箱模式的语法
+ 利用的是 getter 和 setter 来进行操作数据
*/


function outer() {
let a = 100
let b = 200

// 准备对象的时候, 按照 getter 获取器 和 setter 设置器的方式来设置
const obj = {
// getA 目的为了获取 a 成员, 使用 getter 获取器
get a () { return a },
// getB 目的为了获取 b 成员, 使用 getter 获取器
get b () { return b },
// setA 目的是为了设置 a 成员, 使用 setter 设置器
set a (val) { a = val },
set b (val) { b = val }
}

return obj
}

// res 得到的是一个 对象数据类型
const res = outer()

// 因为闭包的形式, 导致我们的所有操作
// 对象.方法名()

console.log(res)
// console.log(res.a)

// 在对象内, 直接给设置器名称赋值即可
// res.a = 1000
// console.log(res.a)
</script>

   函数柯理化

  ●最简单的柯理化函数就是把

  ●一次传递两个参数 , 变成两次每次传递一个参数

  ●利用了闭包 , 把第一次传递的参数保存下来(延长变量的生命周期)


// 需求: 求 10 + x 的和
// 之前的方式
function sum(x) {
return 10 + x
}
// 使用
const res = sum(10)
console.log(res);
// 需求: 求 20 + x 的和
function sum1(x) {
return 20 + x
}
// 使用
const res1 = sum1(10)
console.log(res1);

   ●柯理化函数实现


// 我们利用闭包(柯理化)实现
function curry(init) {
return function (num) {
return num + init
}
}
// 使用
// 求 10 + x 的和
// const sum10 = curry(10);
// console.log(sum10(20));
// 代码优化
console.log(curry(10)(20));
// 求 20 + x 的和
// const sum20 = curry(20)
// console.log(sum20(20));
// 代码优化
console.log(curry(20)(20));

   函数柯理化封装

  ●通过我们上面使用的柯理化

  ●我们发现柯理化有两个功能

  ○一个是用来保存变量(收集变量)

  ○一个是用来实现逻辑(功能函数)

  ●我们柯理化函数的封装有可能是多个参数在相互利用

  柯理化函数封装

  ●把一个功能函数按照 柯理化的方式 封装起来

  ●让这个功能函数变成一种 柯理化的 调用方式

// 准备功能函数
function fn(a, b, c, d) {
return a + '://' + b + ':' + c + d
}
// 准备柯理化函数
function currying(fn, ...arg) {
// ...arg 把除了功能函数以外的参数全部收集了
// console.log('功能函数 : ', fn)
// console.log('收集的基础参数 : ', arg)
// 保存外部 arg 变量
let _arg = arg || []
// 记录: 功能函数需要几个参数
// 语法: 函数名.length
// 得到: 该函数的 形参的个数
const len = fn.length
// 返回一个内部函数
return function (...arg) {
// 这个函数将来被调用的时候, 依旧需要接受参数
// 把本次调用的时候接受的实参, 和之前准备好的拼接在一起
_arg = [ ..._arg, ...arg ]
// 目前 _arg 就是基础准备的参数 和 本次调用时候传递的参数 放在一起的 数组
if (_arg.length < len) {
// 再次调用 currying 函数, 继续去收集参数
return currying(fn, ..._arg)
} else {
// 执行功能函数
return fn(..._arg)
}
}
}
// 将来使用的时候
// const r1 = currying(fn, 'http', 'localhost', '8080')
// const r2 = currying(fn, 'http', 'localhost')
// const r3 = currying(fn, 'http')
// const r4 = currying(fn)
// 提前准备三个参数
// const r1 = currying(fn, 'http', 'localhost', '8080')
// const r2 = r1()
// const r3 = r2()
// const r4 = r3('/a')
// console.log(r4)
// 提前准备两个参数
// const r1 = currying(fn, 'https', 'localhost')
// const r2 = r1()
// const r3 = r2('9999')
// const r4 = r3('/b/c/d')
// console.log(r4)
// 提前准备两个参数
// const r1 = currying(fn, 'https', 'localhost')
// const r2 = r1()
// const r3 = r2('8080', '/a/b/c')
// console.log(r3)
// 提前准备一个参数
// const r1 = currying(fn, 'https')
// console.log(r1('baidu.com', '8080', '/a'))
// console.log(r1('baidu.com')('8080')('/a'))

 

声明:本站稿件版权均属千锋教育所有,未经许可不得擅自转载。

猜你喜欢LIKE

如何进行mysql数据备份?

2023-05-30

从零开始学Java之Java中的内部类是怎么回事?

2023-05-29

什么是事件流以及事件流的传播机制 ?

2023-05-29

最新文章NEW

什么是servlet的生命周期?servlet请求处理流程是怎样的?

2023-05-30

在java中,super关键字怎样使用

2023-05-29

什么是JavaScript伪数组?

2023-05-25

相关推荐HOT

更多>>

快速通道 更多>>

最新开班信息 更多>>

网友热搜 更多>>