javascript-closure

作者:ManfredHu
链接:http://www.manfredhu.com/2016/01/22/6-javascript-closure/index.html
声明:版权所有,转载请保留本段信息,谢谢大家

闭包

词法作用域(Lexical scoping)、声明上下文对象(Declaration context object)

说到变量,我们会关注变量的两个东西。

  • 作用域-空间来理解变量的存在
  • 生命周期-时间来理解变量的存在

变量的词法作用域是指在程序源代码定义的这个变量的区域。通常分为函数全局两部分。

1
2
3
4
5
var scope = "global"; //全局作用域
function func() {
var scope = "local"; //函数作用域,只在这个函数内存活
return scope;
}

那如果换成下面这样的呢?

1
2
3
4
5
6
7
8
9
10
var scope = "global"; //全局作用域
function checkscope(){
var scope = 'local'; //外层函数的作用域
function nested() {
var scope = 'nested'; //内层函数的作用域
return scope;
}
return nested();
}
console.log(checkscope()); //nested

结论:当代码被编译器读取,词法作用域就已经确定了
而这跟下面的变量作用域和闭包又会有关联,词法作用域到函数执行时依然是有效的。浏览器提前做好了大部分的工作,所以JS很快,so fast!!

编译过程干了什么?

传统的编译器会干这些东西

  1. 词法分析过程,将代码读取并且扫描代码,分解成一个一个的词
  2. 语法分析过程,分解语法短语构造语法树
  3. 语义分析过程,审查代码是否有错,收集各个阶段类型的信息
  4. 代码优化过程,对中间代码进行优化,使其更加高效
  5. 生成目标代码,即二进制的本地代码

具体到浏览器的引擎,也不外乎是这几个过程。
我们能明显感受到的就是下面几个过程。

  1. 声明函数、变量——声明提升
  2. 检查代码,优化——代码执行效率大大提高,有ERROR

上面两个过程,很明显可以体会到上面1-4步的过程。而最后一步,学过计算机的都懂的。
我们来看下面的代码体会下编译过程干的事情——声明提升

1
2
3
4
5
6
var scope = "global";
function f() {
console.log(scope); //undefined
var scope = "local";
console.log(scope); //local
}

这段代码跟下面是等价的,只是下面的看起来更清晰,编译器干的事情也更少。

1
2
3
4
5
6
7
var scope = "global";
function f() {
var scope;
console.log(scope); //undefined
scope = "local";
console.log(scope); //local
}

变量的作用域和生命周期

JS中变量的生命周期跟函数有关,函数内定义的变量在函数执行结束后会释放。
JS中变量的作用域也跟函数有关,内层函数可以随作用域链访问外层函数的变量。具体原因就是作用域链的存在。

所以你不难看出,为什么JS里面函数非常非常重要,也许你应该听过了:

JavaScript的世界里函数是一等公民

正正因为你用的变量的生命周期和作用域都跟函数有关。你平常是否想到了呢?
但是闭包是个bug,它为你提供了破坏这种已定规则的限制。为你的代码提供无限可能,这也许就是JavaScript真正的魅力所在,它让一切变得可能。

函数如果嵌套的话,随作用域链来定,如果作用域链上没有则访问不到。会出现ReferenceError

执行环境(execution context)、作用域链(scope chain)的创建

上面都是程序还没运行的时候干的事情,下面来说下程序运行起来会发生什么。

当JavaScript在运行的时候,也就是某个函数被调用时,会创建一个执行环境(execution context),以及相应的作用域链。然后使用arguments和其他命名参数的值来初始化函数的活动对象(activation object),也有称为作用域对象(scope object)的。这东西你摸不到看不见,但是它却真正存在着——就像真理

活动对象(activation object)、作用域对象(scope object)

当我们的代码有多个函数嵌套的时候,一个个作用域对象就被串联起来形成作用域链(Scope chain)

闭包

我至少见过N种定义闭包概念的版本,让我们来欣赏一下:

函数对象可以通过作用域相互关联,函数体内部的变量可以保存在函数作用域内

这种观点引申出来的就是,所有JavaScript函数都是闭包。因为它们都是对象,都关联到作用域链。

函数变量可以被隐藏于作用域链之内,因此看起来是函数将变量包裹起来了

这是从计算机科学文献中引申出来的闭包的解释。

闭包是指有权访问另一个函数作用域中的变量的函数

这是《JavaScript高级程序设计第三版》的定义。也就是说至少需要2个函数才能构成闭包

闭包是指函数有自由独立的变量。换句话说,定义在闭包中的函数可以“记忆”它创建时候的环境。

这是MDN的解释。闭包由两部分构成:函数以及创建改函数的环境
环境由闭包创建时在作用域中的任何局部变量组成。

现在觉得,上面说的其实都有道理。特别是第二个和第三个解释,所以我觉得最最准确描述就是下面两句了。

闭包是指有权访问另一个函数作用域中的变量的函数


Copyright © 2015 - 2019 ManfredHu胡文峰的个人博客

All rights reserved. Designed and powered by ManfredHu.

粤ICP备18133029号