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

发布于:2025-03-06 ⋅ 阅读:(13) ⋅ 点赞:(0)

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

10.14.1. 闭包函数的作用域链

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. 普通函数 作用域链创建和使用的细节
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. 闭包函数 作用域链创建和使用的细节
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()的活动对象。 如下图所示

let compare = createComparisonFunction('name'); 
let result = compare({ name: 'Nicholas' }, { name: 'Matt' }); 

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

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

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

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


网站公告

今日签到

点亮在社区的每一天
去签到