什么是闭包?(写给前端小白的理解笔记)

闭包指的是那些 引用了另一个函数作用域中变量的函数通常是在嵌套函数中实现的.

10.14.1. 闭包函数的作用域链

javascript 复制代码
function createComparisonFunction(propertyName) { 
  return function(object1, object2) { 
    let value1 = object1[propertyName]; 
    let value2 = object2[propertyName]; 
    if (value1 < value2) { 
      return -1; 
    } else if (value1 > value2) { 
      return 1; 
    } else { 
      return 0; 
    } 
 }; 
} 

上面的代码3,4行 在返回的匿名函数中 引用了外部函数的变量propertyName .。在这个内部函数被返回并在其他地方被使用 后,它仍然引用着那个变量。这是因为内部函数的作用域链包含 createComparisonFunction()函数的作用域。

每一个上下文都有一个关联的一对一的变量对象. 上下文中的代码在执行的时候,会创建变量对象 的一个作用域链. 这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序.

  • 代码正在执行的上下文的变量对象始终位于作用域链的最前端.
  • 作用域链中的下一个变量对象来自包含上下文,再下一个对象来自再下一个包含上下文,以此类推直至全局上下文.
  • 全局上下文的变量对象 始终是作用域链的最后一个变量对象.
  1. 普通函数 作用域链创建和使用的细节
javascript 复制代码
function compare(value1, value2) { 
 if (value1 < value2) { 
 return -1; 
 } else if (value1 > value2) { 
 return 1; 
 } else { 
 return 0; 
 } 
} 
let result = compare(5, 10); 

这里定义的 compare()函数是在全局上下文中调用的。当调用 compare()函数时,会为它创建一个包含参数 arguments、value1 和 value2 局部变量、和this指向等内容的活动对象,这个对象是其作用域链上的第一个对象。

而全局上下文的变量对象则是 compare()作用域链上的第二个对象,其中包含 compare、 result 和 this 等内容。

定义 compare()函数时,就会为它创建作用域链,作用域链中预装载全局变量对象,并保存在内部的[[Scope]]中。

调用这个函数时,会创建相应的执行上下文,然后通过复制函数的[[Scope]]来创建其作用域链。接着会创建函数的活动对象(用作变量对象)并将其推入作用域链的前端。在这个例子中,这意味着 compare() 函数执行上下文的作用域链中有两个变量对象:局部变量对象和全局变量对象。

作用域链其实是一个包含指针的列表,每个指针分别指向一个变量对象,但物理上并不会包含相应的对象,只是存储对象的引用地址 指向该对象。

compare函数内部 在访问变量时 就会根据给定的名称从作用域链中查找变量。函数执行完毕之后 该函数对应的局部活动对象会被销毁,内存中只剩下全局作用域。

不过,闭包就不一样了。

  1. 闭包函数 作用域链创建和使用的细节
javascript 复制代码
function createComparisonFunction(propertyName) { 
  return function(object1, object2) { 
    let value1 = object1[propertyName]; 
    let value2 = object2[propertyName]; 
    if (value1 < value2) { 
      return -1; 
    } else if (value1 > value2) { 
      return 1; 
    } else { 
      return 0; 
    } 
 }; 
} 

在一个函数内部定义的函数 会把其包含函数的活动对象添加到自己的作用域链中。

因此,在 createComparisonFunction()函数中,匿名函数的作用域链中实际上包含 createComparisonFunction()的活动对象。 如下图所示

javascript 复制代码
let compare = createComparisonFunction('name'); 
let result = compare({ name: 'Nicholas' }, { name: 'Matt' }); 

在 createComparisonFunction()返回匿名函数后(即第一行代码执行后),它的作用域链被初始化为包含 createComparisonFunction()的活动对象和全局变量对象。这样,匿名函数就可以访问到 createComparisonFunction()可以访问的所有变量。另一个有意思的副作用就是createComparisonFunction()的活动对象并不能在它执行完毕后销毁,因为匿名函数的作用域链中仍然有对它的引用。在 createComparisonFunction()执行完毕后,其执行上下文的作用域链会销毁,但它的活动对象仍然会保留在内存中,直到匿名函数被销毁后才会被销毁. 如下图所示

javascript 复制代码
// 创建比较函数 
let compareNames = createComparisonFunction('name'); 
// 调用函数 
let result = compareNames({ name: 'Nicholas' }, { name: 'Matt' }); 
// 解除对函数的引用,这样就可以释放内存了 
compareNames = null; 

这里,创建的比较函数(即createComparisonFunction函数返回的匿名函数)被保存在变量 compareNames 中。把 compareNames 设置为等于 null 会解除对该匿名函数的引用,从而让垃圾回收程序可以将内存释放掉。作用域链也会被销毁,其他作用域(除全局作用域之外)也可以销毁。

注意 因为闭包会保留它们包含函数的作用域,所以比其他函数更占用内存。过度使用闭包可能导致内存过度占用,因此建议仅在十分必要时使用。

相关推荐
JarvanMo5 分钟前
Flutter:使用图像作为屏幕背景
前端
Mintopia6 分钟前
💰 金融Web应用中的AIGC风险控制技术与合规适配
前端·javascript·aigc
Mintopia10 分钟前
🚀 Next.js 压力测试与性能调优实战
前端·javascript·全栈
江城开朗的豌豆10 分钟前
TypeScript 类型系统漫游指南:从入门到爱上类型安全
前端·javascript
江城开朗的豌豆15 分钟前
从 JavaScript 到 TypeScript:我为什么选择了类型守护
前端·javascript
杨超越luckly31 分钟前
HTML应用指南:利用POST请求获取全国爱回收门店位置信息
大数据·前端·python·信息可视化·html
lkbhua莱克瓦241 小时前
Java练习——正则表达式2
java·开发语言·笔记·正则表达式·github·学习方法
冷崖1 小时前
QML-Model-View
javascript·c++
degen_1 小时前
DXE流程
c语言·笔记·bios
鹏多多1 小时前
解锁flutter弹窗新姿势:dialog-flutter_smart_dialog插件解读+案例
前端·flutter·客户端