一、闭包是指内部函数总是可以访问其所在的外部函数中声明的变量,即使在其外部函数被返回(寿命终结)了之后。
1、如上所述,在 JavaScript 中,所有函数都是天生闭包的(只有一个例外,用“new Function” 创建的函数)。
(1)在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
(2)如果我们使用 new Function 创建一个函数,那么该函数的 [[Environment]] 并不指向当前的词法环境,而是指向全局环境。
2、也就是说:JavaScript 中的函数会自动通过隐藏的[[Environment]]属性记住函数创建时的词法环境,所以它们都可以访问外部变量。

闭包的产生

一、外部函数可以访问外部函数的作用域,因此当内部函数生存周期大于外部函数时,外部函数中定义的变量和函数的生存周期将比内部函数执行时间长。当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。
1、简单地说,在A函数中定义了B函数,在B函数中引用了A中的变量,就会产生闭包。
二、如果一个闭包的函数定义了一个和外部函数的某个变量名称相同的变量,那么这个闭包将无法引用外部函数的这个变量。

| 【示例】20201214-aSuncat:看不太明白???
20211224-aSuncat:期望pet.getName()得到的是Oliver,因为name=name的时候,是希望把Oliver赋值给外部的name,然后getName的时候返回这个Oliver值。```jsx var createPet = function(name) { // Outer function defines a variable called “name” return { setName: function(name) { // Enclosed function also defines a variable called “name” console.log(‘inside name:’, name) // => inside name: Oliver name = name; // ??? How do we access the “name” defined by the outer function ??? }, getName: function() { return name; }, } }

var pet = createPet(‘Vivie’) pet.setName(‘Oliver’) pet.getName() // => Vivie

  1. |
  2. | --- |
  3. 三、闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。
  4. | 【示例】```javascript
  5. function makeFunc() {
  6. var name = "Mozilla";
  7. function displayName() {
  8. alert(name);
  9. }
  10. return displayName;
  11. }
  12. var myFunc = makeFunc();
  13. myFunc();

在本例子中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用。displayName 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用。因此,当 myFunc 被调用时,变量 name 仍然可用,其值 Mozilla 就被传递到alert中。 | | —- |

| 【示例】```javascript function makeAdder(x) { return function(y) { return x + y; }; }

var add5 = makeAdder(5); var add10 = makeAdder(10);

console.log(add5(2)); // 7 console.log(add10(2)); // 12

1、在这个示例中,我们定义了 makeAdder(x) 函数,它接受一个参数 x ,并返回一个新的函数。返回的函数接受一个参数 y,并返回x+y的值。<br />2、从本质上讲,makeAdder 是一个函数工厂 — 他创建了将指定的值和它的参数相加求和的函数。在上面的示例中,我们使用函数工厂创建了两个新函数 — 一个将其参数和 5 求和,另一个和 10 求和。<br />3、add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10。 |
| --- |

<a name="Tt7UX"></a>
# 闭包的写法
一、闭包的写法

1. 一个函数(即外部函数)里面包含另一个函数(即内部函数),并且return返回这个内部函数, 然后内部函数在定义内部函数之外的作用域被调用,这时的内部函数就是一个闭包了。
1. 内部函数引用了外部函数的变量,这个变量不会被销毁,因为闭包需要这个变量, 所以通过闭包可以访问闭包保存的变量
| 【示例】```javascript
function foo(){ 
  var n = 1; 
  function innerFoo(){ 
    n += 1;
    console.log(n);
  } 
  return innerFoo; 
} 
var func = foo(); 
func(); // 产生闭包innerFoo(),变量n保存在内存中 func();

| | —- |

| 【示例】```jsx var pet = function(name) { //外部函数定义了一个变量”name” var getName = function() {
//内部函数可以访问 外部函数定义的”name” return name; } //返回这个内部函数,从而将其暴露在外部函数作用域 return getName;
}; myPet = pet(“Vivie”);

myPet(); // 返回结果 “Vivie”

 |
| --- |

| 【示例】返回了一个包含可以操作外部函数的内部变量方法的对象```jsx
var createPet = function(name) {
  var sex;

  return {
    setName: function(newName) {
      name = newName;
    },

    getName: function() {
      return name;
    },

    getSex: function() {
      return sex;
    },

    setSex: function(newSex) {
      if(typeof newSex == "string" 
        && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
        sex = newSex;
      }
    }
  }
}

var pet = createPet("Vivie");
pet.getName();                  // Vivie

pet.setName("Oliver");
pet.setSex("male");
pet.getSex();                   // male
pet.getName();                  // Oliver

1、上面的代码中,外部函数的name变量对内嵌函数来说是可取得的,而除了通过内嵌函数本身,没有其他任何方法可以取得内嵌的变量。 | | —- |

| 【示例】内部函数可以不被分配给一个变量,不一定要有名字。```jsx var getCode = (function(){ var secureCode = “0]Eal(eh&2”; // A code we do not want outsiders to be able to modify…

return function () { return secureCode; }; })();

getCode(); // Returns the secret code

 |
| --- |

| 【示例】现有如下html结构 ,依次点击4个li标签,哪一个选项是正确的运行结果?```javascript
<ul>
 <li>click me</li>
 <li>click me</li>
 <li>click me</li>
 <li>click me</li>
</ul>
var elements=document.getElementsByTagName('li');
var length=elements.length;
for(var i=0;i<length;i++){
    elements[i].onclick=function() {
        alert(i);
    }
}

A. 依次弹出1,2,3,4
B. 依次弹出0,1,2,3
C. 依次弹出3,3,3,3
D. 依次弹出4,4,4,4
答案:D
解析:```javascript //权威解答——来自javascript语言精粹 var elements=document.getElementsByTagName(‘li’); var length=elements.length; for(var i=0;i<length;i++){ elements[i].onclick=function(){ alert(i); } }

解答:<br />一、《JavaScript高级程序设计》解释:这是由于作用域链的这种配置机制引出的一个副作用,即闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量。 这里的闭包函数`elements[i].onclick=function(){alert(i);}`的作用域链中保存着闭包的活动对象(这里为空)和全局变量对象(主要是i), <br />所以,它们引用的是同一个变量i;当点击完成后,变量i=4,也就是每个内部函数i的值都是4;<br />1、通俗理解:这里的事件,绑定的并非是i的值,而是i本身(alert里的i),所以当程序执行完,i的值变为4,去执行onclick事件时,执行alert(i) ,自动查找i,值为4,所以依次弹出4。 <br />二、可以通过创建另一个匿名函数强制让闭包的行为符合预期: ```javascript
window.onload  = function() {
    var elements=document.getElementsByTagName('li');
    var length=elements.length;
    for(var i=0;i<length;i++){
        elements[i].onclick=function(num){
        return function() {
                alert(num);
        };
    }(i);
    }
}

在调用匿名函数时,我们传入变量i,由于参数是按值传递的,所以就会将变量i的当前值复制给参数num。而这个匿名函数内部,又创建并alert了一个访问num的闭包。这样,每一次点击都有num变量的一个副本,因此可以返回各自不同的数值。 | | —- |

闭包的作用/为什么要用闭包

一、闭包的作用

  1. 可以读取函数内部的变量
  2. 可以把变量始终保存在内存中

通俗地说就是,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。

闭包的应用

一、闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。
1、因此,通常你使用只有一个方法的对象的地方,都可以使用闭包。
二、应用场景

  • 【见】高阶函数#应用;https://www.yuque.com/webfront/js/tguuei
    • 防抖
    • 节流
    • 惰性函数
    • 级联函数
    • 柯里化
  • es5 for循环事件监听
  • 函数里使用了定时器
  • 封装许多高级的功能集:减少闭包使用可以用立即执行函数传递变量
  • 事件监听
  • 用闭包模拟私有方法

    事件监听

    一、在 Web 中,你想要这样做的情况特别常见。大部分我们所写的 JavaScript 代码都是基于事件的 — 定义某种行为,然后将其添加到用户触发的事件之上(比如点击或者按键)。我们的代码通常作为回调:为响应事件而执行的函数。

| 【示例】假如,我们想在页面上添加一些可以调整字号的按钮。一种方法是以像素为单位指定 body 元素的 font-size,然后通过相对的 em 单位设置页面中其它元素(例如header)的字号:```javascript body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; }

h1 { font-size: 1.5em; }

h2 { font-size: 1.2em; }

我们的文本尺寸调整按钮可以修改 body 元素的 font-size 属性,由于我们使用相对单位,页面中的其它元素也会相应地调整。```javascript
function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12,size14 和 size16 三个函数将分别把 body 文本调整为 12,14,16 像素。我们可以将它们分别添加到按钮的点击事件上。如下所示:```javascript document.getElementById(‘size-12’).onclick = size12; document.getElementById(‘size-14’).onclick = size14; document.getElementById(‘size-16’).onclick = size16;

```javascript
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>

| | —- |

用闭包模拟私有方法

一、编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。
1、而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

| 【示例】使用闭包来定义公共函数,并令其可以访问私有函数和变量。这个方式也称为模块模式(module pattern):```javascript var Counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } })();

console.log(Counter.value()); / logs 0 / Counter.increment(); Counter.increment(); console.log(Counter.value()); / logs 2 / Counter.decrement(); console.log(Counter.value()); / logs 1 /

1、在之前的示例中,每个闭包都有它自己的词法环境;而这次我们只创建了一个词法环境,为三个函数所共享:Counter.increment,Counter.decrement 和 Counter.value。<br />2、该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。<br />3、这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。<br />4、你应该注意到我们定义了一个匿名函数,用于创建一个计数器。我们立即执行了这个匿名函数,并将他的值赋给了变量Counter。我们可以把这个函数储存在另外一个变量makeCounter中,并用他来创建多个计数器。```javascript
var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

5、请注意两个计数器 Counter1 和 Counter2 是如何维护它们各自的独立性的。每个闭包都是引用自己词法作用域内的变量 privateCounter 。
6、每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。 | | —- |

二、以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装。

在循环中创建闭包:一个常见错误

错误

一、在 ECMAScript 2015 引入 let关键字 之前,在循环中有一个常见的闭包创建问题。

| 【☆-1示例】```javascript

Helpful notes will appear here

E-mail:

Name:

Age:

```javascript
function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp();

1、数组 helpText 中定义了三个有用的提示信息,每一个都关联于对应的文档中的input 的 ID。通过循环这三项定义,依次为相应input添加了一个 onfocus 事件处理函数,以便显示帮助信息。
2、运行这段代码后,您会发现它没有达到想要的效果。无论焦点在哪个input上,显示的都是关于年龄的信息。
3、原因是赋值给 onfocus 的是闭包。这些闭包是由他们的函数定义和在 setupHelp 作用域中捕获的环境所组成的。这三个闭包在循环中被创建,但他们共享了同一个词法作用域,在这个作用域中存在一个变量item。这是因为变量item使用var进行声明,由于变量提升,所以具有函数作用域。当onfocus的回调执行时,item.help的值被决定。由于循环在事件触发之前早已执行完毕,变量对象item(被三个闭包所共享)已经指向了helpText的最后一项。 | | —- |

| 【☆-2示例】```javascript for(var i=0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 1000); }

代码执行1s后,依次打印10个"10". |
| --- |

<a name="ptiUU"></a>
## 解决方案
<a name="VbFXX"></a>
### 使用更多的闭包
一、解决这个问题的一种方案是使用更多的闭包:特别是使用前面所述的函数工厂:

| 【☆-1示例】```javascript
function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp();

这段代码可以如我们所期望的那样工作。所有的回调不再共享同一个环境, makeHelpCallback 函数为每一个回调创建一个新的词法环境。在这些环境中,help 指向 helpText 数组中对应的字符串。 | | —- |

匿名闭包/自执行函数

| 【☆-1示例】```javascript function showHelp(help) { document.getElementById(‘help’).innerHTML = help; }

function setupHelp() { var helpText = [ {‘id’: ‘email’, ‘help’: ‘Your e-mail address’}, {‘id’: ‘name’, ‘help’: ‘Your full name’}, {‘id’: ‘age’, ‘help’: ‘Your age (you must be over 16)’} ];

for (var i = 0; i < helpText.length; i++) { (function() { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } })(); // 马上把当前循环项的item与事件回调相关联起来 } }

setupHelp();

 |
| --- |

| 【☆-2示例】利用自执行函数,将i作为参数传入```javascript
for(var i=0; i < 10; i++) {
 (function(i) {
     setTimeout(function() {
         console.log(i);
 }, 1000);
 })(i);        
}

| | —- |


块级作用域(推荐)

如果不想使用过多的闭包,你可以用ES2015引入的let关键词:

| 【☆-1示例】```javascript function showHelp(help) { document.getElementById(‘help’).innerHTML = help; }

function setupHelp() { var helpText = [ {‘id’: ‘email’, ‘help’: ‘Your e-mail address’}, {‘id’: ‘name’, ‘help’: ‘Your full name’}, {‘id’: ‘age’, ‘help’: ‘Your age (you must be over 16)’} ];

for (var i = 0; i < helpText.length; i++) { let item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } } }

setupHelp();

这个例子使用let而不是var,因此每个闭包都绑定了块作用域的变量,这意味着不再需要额外的闭包。 |
| --- |

| 【☆-2示例】```javascript
for(let i=0; i < 10; i++) {                
 setTimeout(function() {
 console.log(i);
 }, 1000);
} 
// 依次打印0 - 9

| | —- |

forEach()遍历

| 【☆-1示例】另一个可选方案是使用 forEach()来遍历helpText数组并给每一个

添加一个监听器```javascript function showHelp(help) { document.getElementById(‘help’).innerHTML = help; }

function setupHelp() { var helpText = [ {‘id’: ‘email’, ‘help’: ‘Your e-mail address’}, {‘id’: ‘name’, ‘help’: ‘Your full name’}, {‘id’: ‘age’, ‘help’: ‘Your age (you must be over 16)’} ];

helpText.forEach(function(text) { document.getElementById(text.id).onfocus = function() { showHelp(text.help); } }); }

setupHelp();

 |
| --- |

<a name="A8YDk"></a>
### 利用promise
| 【☆-2示例】```javascript
for(var i=0; i < 10; i++) {
  new Promise((resolve, reject) => {
    var j = i;
    setTimeout(function() {
      console.log(j)
    }, 1000);
  })
}

| | —- |

async函数

| 【☆-2示例】```javascript async function foo() { for(var i=0; i < 10; i++) { let result = await new Promise((resolve, reject) => { setTimeout(function() { resolve(i); }, 1000); }); console.log(result); } } foo(); // 每隔1s打印数字 0 - 9

 |
| --- |

<a name="ZvEYO"></a>
### setTimeout的第三个参数(只针对含setTimeout的闭包)
一、利用setTimeout的第三个参数,将i作为参数传入function

| 【☆-2示例】```javascript
for(var i=0; i < 10; i++) {
  setTimeout(function(j) {
    console.log(j);
  }, 1000, i);
}

| | —- |

闭包的缺点/性能考量

一、闭包在处理速度和内存消耗方面对脚本性能具有负面影响。
1、闭包的变量保存在内存中,内存泄漏,对内存的消耗很大

| 【示例】例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用时,方法都会被重新赋值一次(也就是说,对于每个对象的创建,方法都会被重新赋值)。
考虑以下示例:```javascript function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function() { return this.name; };

this.getMessage = function() { return this.message; }; }

在上面的代码中,我们并没有利用到闭包的好处,因此可以避免使用闭包。修改成如下:```javascript
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

但我们不建议重新定义原型。可改成如下例子:```javascript function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype.getName = function() { return this.name; }; MyObject.prototype.getMessage = function() { return this.message; };

在前面的两个示例中,继承的原型可以为所有对象共享,不必在每一次创建对象时定义方法 |
| --- |


| 【示例】在浏览器控制台中执行以下代码,输出的结果是```javascript
function test() {
    var n = 4399;
    function add(){
        n++;
        console.log(n);
    }
    return {n:n,add:add}
}
var result = test();
var result2 = test();
result.add();
result.add();
console.log(result.n);
result2.add();

A. 4400 4401 4399 4400
B. 4400 4401 4401 4402
C. 4400 4400 4399 4400
D. 4400 4401 4399 4402
E. 4400 4401 4401 4400
答案:A
解析:考察点:闭包、词法作用域链
1、test构成了一个闭包,result跟result2各自有自己的test作用域,所以最后result2.add()结果是4400
2、闭包的两个最大的用处:一是可以读取到函数内部的变量,另一个就是让这些变量的值始终保存在内存中。
3、add中的n其实应该是var num = n; num++; console.log(num)
4、n是基本类型,存的是值,不是引用。{n: n}是对变量n设定了值4399,而不是本身n这个指针变量,这样生成n的时候n指向的值是多少,{n: n}里的值就是多少
5、词法作用域链:内部函数访问外部函数的变量时,会先在自己的词法作用域中寻找有没有这个变量,如果没有,就往上层找,如果也没有,就继续往上找,直到全局作用域,如果依旧没有,就会声明一个全局变量n(严格模式会报错) | | —- |