什么是闭包?可以在自己定义的词法作用域以外的地方执行,就是闭包。
```
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
baz(); //2
```
bar很明显被执行了,但是它在自己定义的词法作用域以外的地方执行了。
foo()执行后,期待foo()的整个内部作用域都被销毁,看起来foo()的内容不会再使用,应当被回收。但是,闭包的神奇之处就在于可以阻止这件事情发生。事实上内部作用域依然存在,所以无法被回收。bar声明的位置导致bar拥有涵盖整个foo内部作用域的闭包,它一直在引用着该作用域。正是因为它无法被销毁,才使得函数可以继续访问定义时的词法作用域。
闭包的常见场景就是在一个函数内的回调函数:
```
function wait(message){
setTimeout(function(){
console.log(message);
}, 1000)
};
wait('hello')
//在1000毫秒后,匿名函数仍然能够获取到message的值,这是因为wait的内部作用域不会消失,匿名函数一直拥有对它的引用。
function setupBot(name, selector){
$(selector).click(function(){
console.log(name);
})
};
setupBot('qc', DIV);
//setupBot执行完后,函数并不会被回收,当你点击的时候匿名函数仍然能获得当初的name。
```
#### 循环与闭包
```
for(var i = 0; i < 5; i++){
setTimeout(function(){console.log(i)}, 1000);
}
//结果很明显是5个6,因为i是被共用的
```
如何解决?思路是每次循环时为i创建一个副本来保存当前i的值。立即执行函数会自动创建自己的作用域。
```
for(var i = 0; i < 5; i++){
(function(){
setTimeout(function(){
console.log(i)
}, 1000);
})();
}
```
然而这是不够的,结果依旧是5个6。立即执行函数内部的作用域是空的,需要为作用域内添加点东西:
```
for(var i = 0; i < 5; i++){
(function(j){
var j = i;
setTimeout(function(){
console.log(j)
}, 1000);
})();
}
//也可以写成
for(var i = 0; i < 5; i++){
(function(j){
setTimeout(function(){
console.log(j)
}, 1000);
})(i);
}
```
现在好了,每一次迭代时都会通过立即执行函数创建内部作用域保存i的值,在循环结束后,闭包仍然能够访问内部作用域的值。
#### let
let拥有自动创建作用域的功能:
```
for(let i = 0; i < 5; i++){
setTimeout(function(){console.log(i)}, 1000);
}
```
问题解决!
#### 模块
```
function module(){
var something = 'cool';
var another = [1, 2, 3];
function doSomething(){
console.log(something);
}
function doAnother(){
console.log(another);
}
return {
doSomething: doSomething,
doAnother: doAnother
}
}
var foo = module();
foo.doSomething();
foo.doAnother();
//函数必须执行,否则无法创建内部作用域和闭包
//该函数返回的是含有对内部函数引用的对象,而不是对内部数据变量引用的对象。
```
模块模式需要具备两个必要条件
1. 必须有外部的封闭函数,该函数必须至少被调用一次(每调用一次都会创建一个新的模块)。
2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
由此可以看出来,一个拥有函数属性的对象并不是真正的模块。
由于module每执行一次都会创建新的模块实例,当只需要一个实例的时候,可以使用立即执行函数来实现单例模式:
```
var foo = (function module(){
var something = 'cool';
var another = [1, 2, 3];
function doSomething(){
console.log(something);
}
function doAnother(){
console.log(another);
}
return {
doSomething: doSomething,
doAnother: doAnother
}
})()
foo.doSomething();
```
模块的另一个强大的用法是可以修改模块内的任意东西:
```
var foo = (function(id){
function change(){
api.identify = identify2;
}
function identify1(){
console.log(id);
}
function identify2(){
console.log(id.toUpperCase());
}
var api = {
change: change,
identify: identify1
}
return api;
})('foo');
foo.identify(); //foo
foo.change();
foo.identify(); //FOO
```
由于闭包的存在,整个模块的作用域会一直存在着,api这个变量永远不会销毁。当调用change后,api的identify就由1替换成了2。
#### 现代模块机制
请认真思考下面的代码原理
```
var Module = (function(){
var modules = {};
function define(name, deps, impl){
for(var i = 0; i < deps.length; i++){
deps[i] = modules[deps[i]]
}
modules[name] = impl.apply(impl, deps);
}
function get(name){
return modules[name];
}
return {define: define, get: get};
})()
```
```
Module.define("bar", [], function(){
function hello(name){
return name;
}
return {hello: hello};
})
Module.define("foo", ["bar"], function(bar){
function hi(){
console.log('name is: ' + bar.hello('qc'));
}
return {hi: hi};
})
```
```
var bar = Module.get("bar");
var foo = Module.get("foo");
console.log(bar.hello('mescal')); // mescal
foo.hi(); // name is: qc
```
这种模块机制是运行时执行的,它的api只有在运行时才被创建,我们可以在运行时修改api,参考identify那个例子。而ES6的模块机制是基于文件的,它的api就是静态的,因此错误检查在编译阶段就执行了,而上述代码的模块机制的错误检查在运行时才开始。
- 你不知道的JS上
- 第一部分 第三章 函数作用域和块作用域
- 第一部分 第四章 提升
- 第一部分 第五章 闭包
- 第二部分 第一章 关于this
- 第二部分 第二章 this全面解析
- 第二部分 第三章 对象
- 第二部分 第五章 原型
- 第二部分 第六章 行为委托
- 你不知道的JS中
- 第一部分 第二章 值
- 第一部分 第三章 原生函数
- 第一部分 第四章 强制类型转换
- 第一部分 第五章 语法
- 第二部分 第一章 异步
- 第二部分 第三章 Promise
- 第二部分 第四章 生成器
- 第二部分 第五章 性能
- 你不知道的JS下
- 第一部分 总结
- 第二部分 第二章 语法
- 第二部分 第三章 代码组织
- 第二部分 第四章 Promise
- 第二部分 第五章 集合
- 第二部分 第六章 新增API
- 第二部分 第七章 元编程
- 第二部分 第八章 ES6之后