前置知识:Javascript的词法作用域

  1. function createIncrementor(start) {
  2. return function () {
  3. return start++;
  4. };
  5. }
  6. var inc = createIncrementor(5);
  7. inc() // 5
  8. inc() // 6
  9. inc() // 7

概念

MDN:
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

JavaScript权威指南
从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。

JavaScript高级教程:
闭包是指有权访问另一个函数作用域中的变量的函数。

你不知道的JavaScript
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数在当前词法作用域之外执行。

  1. 闭包就是子函数可以有权访问父函数的变量、父函数的父函数的变量、一直到全局变量。归根结底,就是利用词法(静态)作用域,即作用域链在函数创建的时候就确定了。
  2. 子函数如果不被销毁,整条作用域链上的变量仍然保存在内存中。

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

JavaScript中的闭包https://segmentfault.com/a/1190000011612140 https://mp.weixin.qq.com/s/G8r5DL19ypOY-HCRpqv4BA

原理

  1. function func() {
  2. const one = '111';
  3. function func2() {
  4. const two = '222';
  5. function func3 () {
  6. const three = '333';
  7. debugger
  8. console.log(one)
  9. console.log(two)
  10. }
  11. return func3;
  12. }
  13. return func2;
  14. }
  15. const func2 = func();
  16. func2()()

闭包是返回函数的时候扫描函数内的标识符引用,把用到的本作用域的变量打成 Closure 包,放到 [[Scopes]] 里。

所以上面的函数会在 func3 返回的时候扫描函数内的标识符,把 one、two 扫描出来了,就顺着作用域链条查找这俩变量,过滤出来打包成两个 Closure(因为属于两个作用域,所以生成两个 Closure),再加上最外层 Global,设置给函数 func3 的 [[scopes]] 属性,让它打包带走。

调用 func3 的时候,JS 引擎 会取出 [[Scopes]] 中的打包的 Closure + Global 链,设置成新的作用域链, 这就是函数用到的所有外部环境了,有了外部环境,自然就可以运行了。
image.png

使用

  1. //封装私有变量
  2. function Person(name) {
  3. var _age;
  4. function setAge(n) {
  5. _age = n;
  6. }
  7. function getAge() {
  8. return _age;
  9. }
  10. return {
  11. name: name,
  12. getAge: getAge,
  13. setAge: setAge
  14. };
  15. }
  16. var p1 = Person('张三');
  17. p1.setAge(25);
  18. p1.getAge() // 25

缺点

image.png