Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript之闭包 #3

Open
chenyong9528 opened this issue Jul 15, 2020 · 0 comments
Open

JavaScript之闭包 #3

chenyong9528 opened this issue Jul 15, 2020 · 0 comments

Comments

@chenyong9528
Copy link
Owner

chenyong9528 commented Jul 15, 2020

闭包的定义

维基百科中的定义:

闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。

MDN中的定义:

函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。

可以看出闭包不单指某个函数,而是一个组合:

闭包 = 函数 + 词法环境

每当定义一个函数时,就会绑定一个词法环境,所以,理论上JavaScript中的所有函数都是闭包。

注意:词法环境,见执行上下文

实践闭包

我们常说的一般指实践闭包,它(函数)有如下特点:

  1. 访问了自由变量(自由变量指非函数自身词法环境中的变量)
  2. 这些自由变量不会随着它们所在的上下文一起被销毁
function outer() {
  let name = "faker"

  function internal() {
    console.log(5) // 没有访问自由变量
  }
  
  return internal
}

let res = outer()
function outer() {
  let name = "faker"

  function internal() {
    console.log(name) // 访问自由变量
  }
  
  return internal
}

outer() // 没有internal的引用,outer上下文销毁的同时,其中的变量也不存在了。

以上两个例子都不是实践闭包:

第一例,没有访问自由变量
第二例,访问了自由变量,但是,由于不存在internal的引用,outer中的变量也就伴随上下文一起被销毁了

function outer() {
  let name = "faker"

  function internal() {
    console.log(name) // 访问自由变量
  }
  
  return internal
}

let res = outer() // 引用了internal
res() // "faker"

以上例子是一个实践闭包,既访问了自由变量,又引用了内部函数internal,引用的存在导致了自由变量无法被回收。所形成的闭包等于函数internal和它所捆绑的outer的词法环境这样一个结构体。

通过上面的例子,我们可以得出结论:

如果存在访问自由变量的函数的引用,那么,无论该自由变量所在的执行上下文是否被销毁,自由变量将继续存在。换句话说,因为存在函数的引用,JavaScript引擎将继续维护函数中的变量所在的词法环境,那么垃圾回收机制也就没办法将它们从内存中删除,这样就形成了闭包。

经典闭包问题

var data = []

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i)
  }
}

data[0]()
data[1]()
data[2]()

以上代码均打印3,匿名函数既访问了自由变量,又在data中引用了该函数,符合闭包的特点。

分析一下:

data[0]执行前,全局词法环境情况:

globalVariableEnvironment = {
  EnvironmentRecord: {
    Type: "Object",
    data: [ fn0, ... ],
    i: 3
  }
}

执行data[0]时,在自己的词法环境中没有找到i,于是顺着外部环境的引用outer找到了全局环境中的i,值为3。

data[1]data[2]同理

引用的三个函数形成了三个闭包,它们有一个共同点,就是维护同一个词法环境(全局环境)。换句话说,就是它们引用的是同一个外部环境,访问的值自然也是同一个。
闭包
上图中,data[0]data[1]data[2]三个闭包引用同一个词法环境(全局词法环境)

另一种情况:

var data = []

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
    return function(){
      console.log(i)
    }
  })(i)
}

data[0]()
data[1]()
data[2]()

以上代码打印0、1、2,与之前有什么不同呢?

同样引用三个函数形成了三个闭包,但三个闭包访问的不再是同一个词法环境,而是自执行函数所形成的词法环境,三次循环形成了三个不同词法环境,也就是说三个闭包绑定了三个不同词法环境,i的值通过参数传递变成了自执行函数的局部变量(javascript中参数都是按值传递,也就是一个赋值的过程),所以i的值与全局中的i再无任何关系,我们只需观察自执行函数词法环境创建时i的值,便能得出答案。

闭包

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant