Skip to content

Latest commit

 

History

History
98 lines (68 loc) · 4.15 KB

closure.md

File metadata and controls

98 lines (68 loc) · 4.15 KB

前言

在es5中,js的知识点就那么几个,而闭包就是其中一个已经被说烂的知识点,但是每一次认真的回顾它,都会让我的理解更深一步。

曾经对于其它知识点也写过很多篇笔记,现在看来似乎理解的深度又不够了。同理,在此写下的这篇文章,可能在日后看来也只是冰山一角。

闭包的本质

2019/12/05 更新

闭包是属于词法作用域范围的,因为将嵌套的函数返回,该函数并未被执行。所以在词法作用域的情况下,判断出函数引用了外部变量,这个函数和外部变量(词法作用域内的变量)形成了闭包。


私以为,闭包是一种结论的现象,而不是一个过程。换句话说,执行完某段代码产生了闭包,而不是在执行的过程中创建了闭包去完成了某件事情。

概念引用 MDN 的原文:

闭包是函数和声明该函数的词法环境的组合。

我觉得上面的表述还是不严谨的,例如:

const a = (x) => {
  let b = 1
  return () => {
    console.log(x)
  }
}

a(1)()

由概念此处的代码产生了闭包:匿名函数和声明该函数的词法环境,即

{
  anonymous: Function,
  b: undefined,
  arguments: {  // 形参在函数初始化时就已经被确立,而此时的变量对象还未被赋值
    x: 1,
    length: 1
  }
  this: undefined,
  scope: [Context.a, Global Context]
}

而实际上,变量 b 是不存在的,也就是说这段代码产生的闭包,不应该包含 b,它包含了:匿名函数和形参 x。

词法环境

与之相关的一个概念是词法作用域,也称为静态作用域,也就是说在词法分析的时候,就被确定的作用域。例如:

var x = 10
function getX() {
  alert(x)
}
function foo() {
  var x = 20
  getX()
}
foo()

按照静态作用域来分析,结果是 10;而按照动态作用域来看,结果是 20。很显然,js 是前者,接下来我们按步骤分析下这段代码的创建与执行:

  • 全局环境,编译时,创建词法作用域 getX.scope = Global Context; foo.scope = Global Context;
  • 声明并初始化函数 function getXfunction foo、 声明 var x 并初始化为 undefined
  • 赋值 x = 10
  • 执行 foo,将 foo 压入栈,生成执行上下文,初始化变量对象,确定 this = undefined
  • 拷贝词法作用域,并将当前上下文与作用域结合初始化作用域链,
  • 然后变量对象被赋值成为活动对象,调用 getX,在当前活动对象中不存在,根据作用域链向上查找,在 Global Context 中查找到定义
  • 执行 getX,函数初始化过程同理,在执行变量 x 时,在当前活动对象中未找到,根据作用域链向上查找,所以是 10

理论上,在全局环境中,此时的 getX 和 x 就属于闭包。

但是词法环境的概念不同于我们讲的变量对象、作用域等,它分为:环境记录 和 外部引用。

但是在这里,我把它理解为:

  • 环境记录是 变量对象 + this
  • 外部引用是 作用域链

还未验证的疑问

存在于闭包中的变量与声明都会一直保存在内存中,直到垃圾回收机制认为它可以被回收。如何有效的验证?

es6 中的 WeakSet / WeakMap 对于对象是弱引用,垃圾回收机制会忽视它的引用,可以用来作为评判标准吗?但是垃圾回收的时间是不可控的,反过来说,一个变量或函数的销毁并不等同于真的立马就被销毁了,只能等待垃圾回收机制后才能算是被真正销毁。

links