五、闭包

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)包装函数的返回值必须至少包括一个对内部函数的引用,这样就可以创建涵盖整个包装函数内部作用域的闭包。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值