第五章 作用域闭包
5.作用域闭包
5.1 启示
- function foo(){
- var a = 2;
- function bar(){
- console.log(a);
- }
- return bar;
- }
- var baz = foo();
- baz(); // 2 闭包的效果
- // 函数bar在定义时的词法作用域外被调用。闭包使得它可以继续访问定义时的词法作用域。
闭包实际上只是一个标准--关于如何在函数作为值按需传递的此法环境中书写代码
当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。
5.2 实质问题
无论使用何种方式对函数类型的值进行传递,当函数在别处调用时都可以观察到闭包。
- function foo(){
- var a = 2;
- function baz(){
- console.log(a); //2
- }
- bar(baz);
- }
- function bar(fn){
- fn(); //闭包
- }
间接调用
- var fn;
- function foo(){
- var a = 2;
- function baz(){
- console.log(a);
- }
- fn = baz; //将baz分配给全局变量
- }
- function bar(){
- fn();
- }
- foo();
- bar();//2
5.3 I got it
在定时器、事假监听器、Ajax请求、跨窗口通信、Web Woekers或者任何其他的异步(或同步)任务中,使用了回调函数就是在使用闭包。
IIFE
- var a = 2;
- (function IIFE(){
- console.log(a);
- })();
严格来说,并不是闭包。IIFE不是在它本身的词法作用域以外执行的。a是通过普通的词法作用域查找而非闭包发现的。
但它的确创建了闭包,并且也是最常用来创建可以封闭起来的闭包的工具。
5.4 循环和闭包
for(var i =1; i<=5; i++){
setTimeout(function timer(){
console.log(i);
},i*1000
);}
输出6. 延迟函数的回调会在循环结束时才执行,即使第二个参数是0,所有的回调函数依然是在循环结束之后才会被执行。
缺陷是:再循环过程中每个迭代都需要一个闭包作用域。要想得到想要的结果--每个一秒输出一个数字(递增1)--1~5
for(var i =1;i<=5;i++){
(function(){
var j = i;
setTimeout(function timer(){
console.log(j);
- },j*1000);
- })();
}
在迭代内使用IIFE会为每个迭代都生成一个新的作用域,使延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。
块作用域
let:用来劫持块作用域,并且在这个块作用域中声明一个变量。
for(let i = 1; i<=5; i++){
setTimeout(function(){
console.log(i);
},i*1000);
}
!!!可以得到想要的效果 1,2,3,4,5 块作用域和闭包
5.5 模块
模块模式需要具备两个必备条件:
- 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用于中形成闭包,并且可以访问或者修改 私有的状态。
一个从函数调用所返回的,只有数据属性而没有闭包函数的对象并不是真正的模块。
模块有两个主要特征:
- 为创建内部作用域而调用了一个包装函数
- 包装函数的返回值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包。
5.5.1 现代的模块
大多数模块依赖加载器/管理器本质上都是将这种模块定义封装进一个有好的API里。
5.5.2 未来的模块
import可以将一个模块中的一个或多个API导入到当前作用域中,并分别绑定在一个变量上。
moddle会将整个模块的API导入并绑定在一个变量上。
export会将当前模块的一个标识符(变量、函数)导出为公共API
模块文件中的内容会被当做好型包含在作用域闭包中一样来处理,和函数闭包模块一样