1.闭包
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
函数在定义时的词法作用域以外是地方被调用,闭包使得函数可以继续访问定义时的词法作用域。
function foo(){
var a=2;
function bar(){
console.log(a);//2
}
bar();
}
foo();
基于词法作用域的规则,函数bar( )可以访问外部作用域中的变量a(这个例子是一个RHS引用查询)。这只是闭包的一部分。函数bar( )具有一个涵盖foo( )作用域的闭包(事实上,涵盖了它能访问的所有作用域,比如全局作用域)。
也可以讲bra( )被封闭在了foo( )的作用域中。
下面用一段代码展现闭包:
function foo(){
var a=2;
function bar(){
console.log(a);
}
return bar;
}
var baz=foo();
baz(); //2,这就是闭包的效果
函数bar( )的词法作用域能够访问foo()的内部作用域,然后我们将bar()函数本身当作一个值类型进行传递。
foo( )执行后,通常会期待一个foo()的整个内部作用域被销毁,因为引擎有垃圾回收器来释放不再使用的内存空间。
闭包的“神奇”之处是可以阻止这样事情的发生。事实上内部作用域依然存在使用(bar() ),因此没有被回收。bar()存在对该作用域的引用,这个引用就叫作闭包。
无论任何方式对函数类型的值进行传递,当函数在别处被调用时,都可以及观察到闭包。
function foo(){
var a=2;
function baz(){
console.log(a);//2
}
bar(baz);
}
function bar(fn){
fn(); //这就是闭包。无论使用任何类型的值进行传递,当函数在别处被调用时都可以观察到闭包
}
将内部函数baz传递给bar,当调用这个内部函数(baz,现在叫fn)时,它涵盖的foo()内部作用域的闭包形成,可以访问a。
2.循环闭包
当循环内部包含函数定义时,代码格式检查器经常发出警告。
正常情况下,对下面这段代码的预期是分别输出1-5,每一秒一次,每次一个。但实际上,代码在运行时会以每秒一次的频率输出五个6。
for (var i=1;i<=5;i++){
setTimeout(function timer(){
console.log(i);
},i*1000);
}
此上代码的缺陷:试图循环中的每个迭代在运行时都给自己“捕获”一个 i的副本。据作用域工作原理,循环中的五个函数虽是在各个迭代中分别定义的,但是,它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i 。
所有延迟函数的回调函数在循环结束时才执行,因此每次输出一个6。
对代码进行改进,预想效果:(代码如下)
在迭代中使用IIFE为每次迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个正确值的变量供我们访问。
for(var i1;i<=5;i++){
(function(j){
setTiemout(function timer(){
console.log(j);
},j*1000);
})(i);
}
3.模块
function foo(){
var something="cool";
var another=[1,2,3];
function doSomething(){
console.log(something);
}
function doAnother(){
console.log(another.join("!"));
}
}
如上段代码中。没有明显的闭包,只有两个私有数据变量something和another,以及doSomething()和 doAnother()两个内部函数,它们的词法作用域(这里是闭包)也就是foo()的内部作用域。
function CoolModule(){
var something="cool";
var another=[1,2,3];
function doSomething(){
console.log(something);
}
function doAnother(){
console.log(another.join("!"));
}
return{
doSomething:doSomething,
doAnother:doAnother
};
}
var foo=CoolModule();
foo.doSomething(); //cool
foo.doAnother(); //1!2!3
这个模式在JS中被称为模块,最常见的实现模块模式的方法是通常被称为模块暴露。
CoolModule()只是一个函数,必须通过调用它来创建一个模块实例,如果不执行外部函数,内部函数和闭包都无法被创建。
从模块中返回一个实际的对象并不是必须的,也可以直接返回一个内部函数。jQuery就是一个很好是例子。jQuary和$标识符就是jQuery模块的公共API,但它们本身都是函数(由于函数也是对象,它们本身也可以拥有属性)。
模块模式需要两个必要条件:
1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
2.封闭函数必须返回一个内部函数,这样函数才能再私有作用域中形成闭包,并且可以访问或修改私有的状态。
4.现代的模块机制
大多数模块依赖加载器/管理器本质上都是这种模式定义封装进一个友好的API。这里简单介绍一些核心概念:
这段代码的核心是modules[name]=impl.apply(impl,deps)。为了模块的定义引入了包装函数(可以传入任何依赖),并且将返回值(也就是模块的API),存储在一个根据名字来管理的模块列表中。
var MyModules=(function Manager(){
var modules={};
function define(name,deps,ipl){
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
};
})();
下面展示了如何使用进行定义模块:
MyModules.define("bar",[],function(){
function hello(who){
return "let me introduce:"+who;
}
return{
hello:hello
};
});
MyModule.define("foo",["bar"],function(bar){
var hungry="hippo";
function awesome(){
console.log(bar.hello(hungry).toUpperCase());
}
return{
awesome:awesome
};
});
var bar=MyModules.get("bar");
var foo=MyModules.get("foo");
console.log(
bar.hello("hippo")
); //let me introduce hippo
foo.awesome(); //LET ME INTPOOUCE :HIPPO
“foo” 和 “bar”模块都是通过一个返回公共API的函数来定义的。“foo”甚至接受”bar”的示例作为依赖参数,并能相应的使用它。
5.未来的模块机制:
ES6中为模块增加了一级语法支持。通过模块系统进行加载时,ES6会将文件作为独立的模块来处理,每个模块都可以导入其他模块或特定的API成员,同样也可以导出自己的API。
另一个强大功能:用多种形式来实现模块等模式
模块有两个主要特征:
(1)为创建内部作用域而调用了一个包装函数;
(2)包装函数的返回值必须至少包括一个对内部函数的引用,这样就可以创建涵盖整个包装函数内部作用域的闭包。