zoukankan      html  css  js  c++  java
  • javascript中的闭包、模块与模块加载

    一、前言

    闭包是基于词法作用域(  和动态作用域对应,词法作用域是由你写代码时,将变量写在哪里来决定的,因此当词法分析器处理代码时,会保持作用)书写代码时所产生的自然结果,甚至不需要为了利用闭包而有意地创建闭包。闭包的创建和使用在动态语言的代码中随处可见。你缺少的只是识别,拥抱和使用闭包的思维。

    当函数可以记住并访问所在的词法作用域,即使函数在当前词法作用域之外执行。就产生了闭包。

    一般情况下,当函数执行完毕,垃圾回收机制会期待函数的整个内部作用域被销毁,但当闭包存在时,会阻止这件事情的发生,事实上内部作用域依旧存在,此时内部函数依旧持有对外部函数作用域的引用,这个引用就叫做闭包。无论通过何种方式将内部函数传递到所在的词法作用域之外,他都会持有对 原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

    所以说,在javascript,python这种动态语言中,因为函数是一级对象,无论何时何地,只要将函数当做第一级的值类型到底传递,都会看到闭包的运用,可以说,闭包无处不在。

    二、循环与闭包

    关于闭包,一段很经典的代码是:

    var i=0;
    for(i=0;i<5;i++){
        setTimeout(function(){
            console.log(i);
        },1000);
    }
    var result=[];
    for(var i=0;i<5;i++){
        result[i]=function(){
            console.log(i);
        }
    }
    for(var j=0;j<5;j++){
        result[j]();
    }

    上面的代码不会如预期分别输出1-5,而是输出4次5;

    首先解释下5是怎么来的,循环终止的条件是i<5;因此条件成立时候i的值等于5;

    事实上这种情况是必然的,setTimeout,以及result中的函数都是在循环结束的时候才会执行,哪怕是setTimeout(,0).

    我们试图假设循环中的每个迭代在运行时,都会给自己“捕获一个i的副本”。但是根据作用域的工作原理,实际情况是尽管循环中的5个函数实在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i。

    再考虑下面的代码:

    var i;
    for(i=0;i<5;i++){
        setTimeout(function(i){
            console.log(i);
        },1000);
    }

    上面代码的执行结果是输出5个undefined,同样的道理,实际上延迟函数是在循环结束之后才执行的,执行的时候,并没有为其传入参数,因此执行的结果只能是undefined.

    如果想要上面的代码如预期执行,需要在循环中为每个迭代都创建一个闭包作用域。

    var i;
    for(i=0;i<5;i++){
        (function(){
            var j=i;//得到一个副本
            setTimeout(function(){
                console.log(j);
            },1000);
        })();
    }
    
    //或者进一步简化
    //形参传递本质上是传递只的一个副本;
    //作用域冒泡到ITFE,找到了参数j,就不再向上冒泡
    for(i=0;i<5;i++){
        (function(j){
            //此时形参命名为j,还是叫i都无所谓
            setTimeout(function(){
                console.log(j);
            },1000);
        })(i);
    }

    三、模块

    模块是一个利用闭包的典型案例:

    模块模式至少具备两个条件:

    1)必须有外部的封闭包装函数来创建内部作用域,该函数至少被调用一次(每次调用都会创建一个新的模块作用域),如果是ITFE调用就只产生一个实例(单例模式?);

    2)封闭函数返回至少一个内部函数的引用(可以直接返回该内部函数,如jQuery;也可以返回一个对象,该对象至少包含一个属性,指向内部函数的引用),这样内部函数才能在私有作用域形成闭包,而且可以访问或者修改私有的状态;

    比如模块的一个很常见的应用就是返回作为公共API返回的对象:

    var foo=(function(){
        function change(){
            console.log('change');
        }
        function identify(){
            console.log('identify');
        }
        return{
            change:change,
            identify:identify,
        }
    })();

    四、模块加载器/管理器

    模块管理器本质上并没有任何的“魔力”,本质上就是讲模块定义封装进一个友好的API。下面是一个简单的模块加载器的实现:

    // 尝试实现简单的模块加载器,这种方法只考虑了模块的本地加载,事实上require.js等加载器,还需要动态创建script标签来远程加载模块
    var MyModules=(function(){
        var modules=[]
        //参数name:模块名称
        //参数deps:依赖的模块名称
        //impl:名为name的模块实现
        var define=function define(name,deps,impl){
            var i=0;
            for(i;i<deps.length;i++){
                deps[i]=modules[deps[i]];
            }
            modules[name]=impl.apply(impl,deps);
        };
        var get =function(name){
            return modules[name];
        };
        return {
            define:define,
            get:get
        }
    })();
    
    //调用
    MyModules.define('bar',[],function(){
        function hello(){
            return 'hello';
        }
        return {
            hello:hello,
        };
    });
    MyModules.define('foo',['bar'],function(){
        function awesome(){
           console.log('foo'+bar.hello()); 
        }
        return{
            awesome:awesome,
        }    
    })
    var bar=MyModules.get('bar');
    var foo=MyModules.get('foo');
    foo.awesome();
  • 相关阅读:
    svn客户端使用
    svn服务端搭建
    数组和链表
    旅行商算法
    大O表示法
    交互页面的一些设计规则
    正则表达式大全
    初识算法—二分法初探
    log4j配置
    品优购商城项目(二)mybatis分页插件
  • 原文地址:https://www.cnblogs.com/bobodeboke/p/6127650.html
Copyright © 2011-2022 走看看