# 闭包

## 定义

闭包是指那些能够访问自由变量的函数。

自由变量是指在函数中使用的，但既不是函数参数也不是函数的局部变量的变量。

```javascript
var a = 1;

function foo() {
    console.log(a);
}

foo();
```

上例函数 foo + foo 函数访问的自由变量 a也算是闭包。所以在《JavaScript权威指南》中就讲到：**从技术的角度讲，所有的JavaScript函数都是闭包**。

但是从实践上意义的闭包定义为：

1. 即使创建它的上下文已经销毁，它仍然存在（比如，内部函数从父函数中返回）
2. 在代码中引用了自由变量

## 闭包的例子

```javascript
function foo() {
    var a = 2; 
    function bar() { 
        console.log( a ); 
    } 
    
    return bar;
} 

var baz = foo(); 
baz(); // 2 —— 朋友，这就是闭包的效果。
```

在 foo() 执行后，通常会期待 foo() 的整个内部作用域都被销毁，因为我们知道引擎有垃 圾回收器用来释放不再使用的内存空间。由于看上去 foo() 的内容不会再被使用，所以很 自然地会考虑对其进行 回收 。

而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域 没有被回收。谁在使用这个内部作用域？原来是 bar() 本身在使用。

拜 bar() 所声明的位置所赐，它拥有涵盖 foo() 内部作用域的闭包，使得该作用域能够一 直存活，以供 bar() 在之后任何时间进行引用。

bar() 依然持有对该作用域的引用，而这个引用就叫作闭包。

## 陷阱1

```javascript
for ( var i=1; i<=5; i++) { 
    setTimeout( function timer() { 
        console.log( i ); 
    }, i*1000 ); 
}
```

上诉代码，最后只会连续的输出6，而不是1到5。

这背后的原因是根据作用域的工作原理，实际情况是尽管循环中的五个函数是在各个迭代中分别定义的， 但是**它们都 被封闭在一个共享的全局作用域中 ，因此实际上只有一个 i** 。

这样说的话， 当然所有函数共享一个 i 的引用。循环结构让我们误以为背后还有更复杂的机制在起作用，但实际上没有。**如果将延迟函数的回调重复定义五次，完全不使用循环， 那它同这段代码是完全等价的。**

{% hint style="info" %}
个人更多的理解是，参考event-loop机制，setTimeout是宏任务，不会被同步执行，会先注册。又由于在i\*1000所有同步执行完后，真正执行timer时，i值已经被改变了
{% endhint %}

解决问题的方式是，更改成立即执行函数

```javascript
for(var i=1;i<=5;i++){
    (function(j){
        setTimeout( function timer() { 
            console.log( j ); 
        }, j*1000 ); 
    })(i)
}
```

当然，更酷的是es6的写法：

```javascript
for ( let i=1; i<=5; i++) { 
    setTimeout( function timer() { 
        console.log( i ); 
    }, i*1000 ); 
}
```

## 陷阱2

```javascript
function Foo() {
    var i = 0;
    return function() {
        console.log(i++);
    }
}
 
var f1 = Foo(),
    f2 = Foo();
f1();
f1();
f2();
```

道题考察**闭包**和**引用类型对象**的知识点：\
1.一般来说函数执行完后它的局部变量就会随着函数调用结束被销毁，但是此题foo函数返回了一个匿名函数的引用（即一个**闭包**），它可以访问到foo()被调用产生的环境，而局部变量i一直处在这个环境中，只要一个环境有可能被访问到，它就不会被销毁，所以说闭包有延续变量作用域的功能。这就好理解为什么：

```javascript
f1();//0
f1();//1
```

2.我一开始认为f1和f2都=foo()是都指向了同一个function引用类型，所以顺理成章就会答错认为：

```javascript
f2();//2
```

但其实foo()返回的是一个匿名函数，所以f1,f2相当于指向了两个不同的函数对象，所以结果也就顺理成章的变为：

```javascript
f2();//0
```

### 注意

如果上面的代码改成下面，结果会改变

```javascript
const Foo = (function() {
    var i = 0;
    return function() {
        console.log(i++);
    }
})()
 
var f1 = Foo,
    f2 = Foo;
f1();  // 0
f1();  // 1
f2();  // 2
```

其本质区别为， Foo变成立即运行函数，已经生成了确定的匿名函数地址，再分别分配给 f1和 f2

## 闭包的应用场景

vue 源码中 全局Api中的$watch方法中使用闭包

```javascript
// 根据Vue的$watch原理实现的简易版$watch
Vue.prototype.$watch = function(exp, cb, options = {immediate: true, deep: false}) {
    let watcher = new Watcher(this, exp, cb, options);
    return () => {
        watcher.unWatch();
    };
}
```

应用

```javascript
let obj = {
	name: 'kiner',
	age: 20
};
 
//  监听obj.name的改变
let unWatchName = this.$watch("obj.name",function(newVal, oldVal){
	console.log(`新值：${newVal};旧值：${oldVal}；`);
});
 
// 取消监听，取消后，obj.name的改变不再会通知
unWatchName()
```

这样实现的巧妙之处在于，我们无须关心要调用谁去取消监听，你怎么监听的，他就给你返回一个取消监听的方法，直接调用这个方法取消就可以了。

## 闭包应用

### 节流

### 防抖

### 缓存

```javascript
const Id2NameMap = {};
const getNameById = function (id) {

  if (Id2NameMap[id]) return Id2NameMap[id];

  return new Promise(resolve => {
    mockGetNameById(id, function (name) {
      Id2NameMap[id] = name;
      resolve(name);
    })
  });
}
Promise.resolve(getNameById(id)).then(name => {
  console.log(name);
});
```

### Once

```javascript
/**
 * Ensure a function is called only once.
 */
export function once (fn: Function): Function {
  let called = false
  return function () {
    if (!called) {
      called = true
      fn.apply(this, arguments)
    }
  }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mm.ricky.moe/javascript/js-concept/bi-bao.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
