闭包是基于词法作用域书写代码产生的自然结果
当函数可以记住并访问所在的此法作用域的时候,就产生了闭包
var a=2;
(function () {
console.log(a);
})()
由上面的定义就知道,上面的代码并不是严格意义上的闭包。因为并没有在函数本身创建的此法作用域以外执行。
。也就是当前词法作用域之外执行。
function foo() {
var a=2;
function bar() {
console.log(a)
}
bar();
}
foo();
但是上面的这个不是严格意义上的闭包,下面这个程序是严格意义上的闭包
function foo() {
var a=2;
function bar() {
console.log(a)
}
return bar;
}
var baz=foo();
baz();
一个实际意义上的完整的闭包。
bar()的此法作用域能够访问foo() 的内部作用域。然后将bar()函数本身当作一个值进行传递。也就是在这个例子中,bar所引用的函数对象本身当作返回值。所以bar在自己定义的词法作用域以外的地方被执行。
闭包的缺点:
在foo执行之后,我们通常会期待foo的整个作用域都被销毁。但是实际上并没有。foo()的内部作用域依然存在,因此没有被 回收。bar()本身还在使用这个作用域。这个作用域会一直存活,以供bar()在任何时间被调用。
闭包:bar()依然持有该作用域的的引用,这个引用就是闭包。
简单的说闭包就是那个会一直存在的作用域。。
闭包的其他出现的形式;
函数类型作为参数进行传递
无论使用何种方式对函数的值进行传递,当函数在别出被调用时可以观察到闭包。
function foo() {
var a=2;
function baz() {
console.log(a) //2
}
bar(baz)
}
function bar(fn) {
fn(); //这也是闭包
}
foo();
所以闭包在定时器、事件监听器、AjAx请求,跨窗口通信,web workers或者任何异步或者同步的任务中,只要使用回调函数,实际上就是在使用闭包。
循环和闭包
for(var i=0;i<=5;i++){
setTimeout(function timer() {
console.log(i);
},i*1000);
}
运行的结果是每隔一秒就会输出一个6.
原因:延迟函数的回调会在循环结束的时候才执行。尽管循环中的5个函数是在各个迭代中分别定义的,但是他们被封闭在一个共享的全局作用域中,因此实际上只有一个i。
解决办法是:每次循环的时候都要分别为自己保留一个i的副本。
for(var i=0;i<=5;i++){
(function (i) {
setTimeout(function timer() {
console.log(i);
},i*1000);
})(i)
}
以上代码就实现了每隔一秒就输出0,1,2,3,4,5
for(var i=0;i<=5;i++){
let j=i;
setTimeout(function timer() {
console.log(j);
},j*1000);
}
以上代码就实现了每隔一秒就输出0,1,2,3,4,5,使用let关键字让每一次循环都新生成一个作用域,执行延迟函数。
for(let i=0;i<=5;i++){
setTimeout(function timer() {
console.log(i);
},i*1000);
}
以上代码就实现了每隔一秒就输出0,1,2,3,4,5。变量在循环的过程中不止被声明一次每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。
模块
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 module=new CoolModule();
module.doSomething();
module.doAnother();
这种模式就被称为模块。CoolModule这个名字一定要起得非常有意义和独特。
分析上面的这种模块的模式:
首先:CoolModule()只是一个函数,必须要通过调用它创建一个模块的实例。如果不执行外部函数,内部作用域和闭包都无法创建。
其次:CoolModule()返回一个用对象字面量的语法{key:value}来表示对象。这个返回的对象中含有对内部函数而不是内部数据变量的引用。这样内部的数据还是处于隐藏的状态。可以将这个对象类型的返回值看作本质上是模块的公共API。(一个很好的例子就是jquery)
所以模块模式需要两个必要的条件:
1、必须有外部的封闭函数(CoolModule),该函数必须至少被调用一次(每次创建一个新的模块的时候,都会创建一个新的模块实例)
2、封闭函数至少要返回一个内部函数,这样内部函数才能在私有作用域中形成闭包。
注意:
模块也是普通的函数,因此可以接收参数
function CoolModule(id) {
function identify() {
console.log(id);
}
return {
identify:identify
}
}
var cc1=new CoolModule('sa');
cc1.identify(); //sa
var cc2=new CoolModule('ffdfd');
cc2.identify(); //ffdfd
模块模式的另一个强大的用法:
命名将要作为公共API返回的对象
function CoolModule(id) {
function change() {
PublicAPI.identify=identify2
}
function identify1() {
console.log(id);
}
function identify2() {
console.log(id.toUpperCase());
}
var PublicAPI={ //命名将要作为公共API返回的对象
identify:identify1,
change:change
}
return PublicAPI;
}
var cc=new CoolModule("sasas");
cc.identify(); //sasas
cc.change();
cc.identify(); //SASAS
也就是给模块的返回值起了一个名字,这样的话就可以修改。
现代模块机制
var Mymodules=(function Manager() {
var modules={};
function define(name,deps,impl) { //deps为依赖的模块
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};
});
Mymodules.define("test",[],function () {
function testConsole() {
return "we are testing";
}
return {testConsole:testConsole};
});
Mymodules.define("foo",["bar","test"],function (bar) {
function awesome() {
var cc="dsdasa";
console.log(test.testConsole().toUpperCase())
console.log(bar.hello("cc").toUpperCase())
}
return {
awesome:awesome
}
});
var test=Mymodules.get("test");
var bar=Mymodules.get("bar");
var foo=Mymodules.get("foo");
foo.awesome();
console.log(bar.hello("hippo"));
console.log(2121);
块作用域的替代方案
在ES3发布以来,js中就有了块作用域,with和catch语句就是块作用域的两个例子,但是随着ES6的let语句的引入,我们的代码终于有了创建完整、不受约束的块作用域的能力。
{
let a=3;
console.log(a); //3
}
console.log(a); //a is not defined
let让程序形成了一个块级作用域。
{
var a=3;
console.log(a); //3
}
console.log(a); // 3
var 就不可以形成一个块级作用域。
以上是ES6解决块级作用域的方案,但是在ES6之前怎样解决块级作用域?
try{
throw 2;
}catch (a){
console.log(a); //2
}
console.log(a);// a is not defined
总结
当函数可以记住并访问所在的此法作用域,即使函数在当前的词法作用域外执行,这时就产生了闭包。