• 高级javascript---模块化编程


    随着网站逐渐变成“互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂

    网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等......开发者不得不使用软件工程的方法,管理网页的业务逻辑。

    Javascript模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。

    但是,Javascript不是一种模块化编程语言,它不支持""(class),更不要说"模块"(module)了。(正在制定中的ECMAScript标准第六版,将正式支持"类"和"模块",但还需要很长时间才能投入实用。)

    Javascript社区做了很多努力,在现有的运行环境中,实现"模块"的效果。本文主要通过自己动手编程,实现一个简易版的require.js来讲述怎么应用模块化编程,以及模块化的原理,以便更好的理解require.js 及seajs等现有的模块化管理框架。虽然这不是初级教程,但是只要稍稍了解Javascript的基本语法,就能看懂。

    一、原始写法

    模块就是实现特定功能的一组方法。

    只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。

    function module1(){
        //...
    }
    function module2(){
        //...
      }

    上面的函数module1()和mmodule2(),组成一个模块。使用的时候,直接调用就行了。

    这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。

    二、对象写法

    为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。

    var module1 = new Object({
        _count : 0,
        m1 : function (){
          //...
        },
        m2 : function (){
          //...
        }
      });

    上面的函数m1()和m2(),都封装在module1对象里。使用的时候,就是调用这个对象的属性。

    module1.m1();

    但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。

    module1._count = 5;

    三、立即执行函数写法

    使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。

     var module1 = (function(){
        var _count = 0;
        var m1 = function(){
          //...
        };
        var m2 = function(){
          //...
        };
        return {
          m1 : m1,
          m2 : m2
        };
      })();

    module1就是Javascript模块的基本写法。但是上面的例子只是演示了”模块“这个概念,而我们工作中遇到的更多的问题是如何组织这些模块。如果有过php的编程经验的话,你就会想到php中有一个require()函数(也有人说这不是函数,因为括号可以省略),当我们在编写模块A时,如果要用到之前定义过的模块B时,只要require('b.php')就可以了。但是在javascript中是没有这种现成的方法的。不过我们知道,javascript的语法是非常灵活的,只要有运用一定的技巧,就可以模拟其它编程语言中的特性,比如我们熟知的extend() 继承就是一个很好的例子。当然,现在的这个模块化require方法,也有现成的库可以使用了,比如require.js, seajs等等,但是它们大都有一个巨大的身躯,我们一下子难以看清它是怎么实现的。 

    下面我贴下我实现的简易版源码

    /**
     * 模块化编程简易框架
     */
    ;(function() {
        //存放所有声明过的模块
        var modules  = {};
        //存放已解析的模块
        var dn = {}
    
        /**
         * 判断是否为一个数组
         */
        function isArray(arr){
            return Object.prototype.toString.call(arr) == '[object Array]';
        }
    
        /*
         * 解析申请的模块
         * @param {array} ids 模块id
         * @param {function} callback 回调 
         */
        function makeRequire(ids, callback) {
            var r = ids.length,
                shim = [];
            ids.forEach(function(name,i) {
                shim[i] = build(modules[name])
            })
            if (callback) {
                callback.apply(null,shim);
            } else {
                shim = null;
            }
        }
    
        /**
         *解析依赖关系
         * @param {object} module 模块对象
         * @return {array} 一个由所有依赖模块所返的对象所组成的数组
         */
        function parseDeps(module) {
            var deps = module['deps'],
                temp = [];
            deps.forEach(function(id, index) {
                temp.push(build(modules[id]))
            })
            return temp;
        }
    
        /**
         * 返回模块中定义的对象
         * @param {object} module 定义的模块
         * @return 模块返回的对象
         */
        function build(module){
            var exist,exports;
            factory = module['factory'],
            id = module['id'];
    
            exist = dn[id];
    
            if(exist){
                return exist;
            }
    
            if(module['deps']){
                depsList = parseDeps(module);
                exports = factory.apply(module, depsList);
            }else{
                exports = factory() || {};
            }
    
            dn[id] = exports;
            return exports;
        }
    
        /**
         * 导入模块
         * @param {id} id 模块ID
         * @param {function} callback 回调函数
         * @return {object} 
         */
        function require(id,callback){
            if(isArray(id)){
                return makeRequire(id,callback);
            }
            if (!modules[id]) {
                throw "module " + id + " 模块不存在!";
            }
    
            if(callback){
                var module = build(modules[id]);
                callback(module);
            }else{
                if(modules[id].factory){
                    return modules[id];
                }
            }
        }
        /**
         * 定义一个模块
         * @param {string} id   模块ID
         * @param {array}  deps 依赖列表
         * @param {function} factory 模块方法
         */
        function define(id,deps,factory){
            if(modules[id]){
                throw "module " + id + " 模块已存在!";
            }
    
            //如果有依赖
            if(arguments.length > 2){
                modules[id] = {
                    id : id,
                    deps : deps,
                    factory : factory
                }
            }else{
                modules[id] = {
                    id : id,
                    factory : deps
                }
            }
        }
    
    
        window.require = require;
        window.define = define;
    
    })();

    这里的实现忽略了javascript文件的加载和循环依赖的问题,因为我的重点是放在模块之间的导入和团队协作开发的问题上。个人认为也没有必要做成按需加载,因为不会用到的模块,是根本没有必要为它编写模块的。在开发模式下,需要一个功能,就在index.html中导入一个js模块,在产品模式下,就把index.html中的所有js文件进行合并压缩,这样做到只加载一次,根本没有按需加载出场的余地。

    下面我来介绍下这个简易版的require.js框架是怎么用的。

    其实非常简单,只要知道define()是定义一个模块,require是调用一个模块就可以了。

    具体的看下面几个典型示例:

    //定义一个模块 a
    define('a',function(){
          //模块a中的私有变量
           var name = 'module a';
    
           //返回这个模块所实现的方法
        return {
            a : function(){
                            //根据需要返回模块中的内容
                console.log(name)
            }
        }
    })
    //定义一个模块 b
    define('b',function(){
           var name = 'module b';
    
        return {
            b : function(){
                console.log(name)
            }
        }
    }) 
    
    //定义一个模块 c, 依赖 a,b两个模块
    define('c',['a','b'],function(a,b){
            //a,b是模块c需要依赖的模块
           //只要以数组的方式依次例出模块Id就可以了
        console.log(a,b)
        return {
            c : function(){
                console.log('module c')
            }
        }
    })
    
    //同时调用a,b两个模块
    require(['a','b'],function(a,b){
            //a就是前面定义的模块a返回的对象
           //b同上
         //此处就可以自由的使用a,b模块中所公布的方法了
        console.log(a,b)
    })
    
    //调用c模块
    require('c',function(c){
        console.log(c)
    })      

    这样我们就实现了一个简单的模块化功能。从上面的示例来看,进行单元测试是不是很容易呢? 代码看起来是不是很清爽呢?

    从我这一百来行的代码中也不难看出实现的原理,先是定义modules用来收集所定义的模块,然后用 define来定义一个模块,通过id进行区分,factory返回这个模块提供的对象

    而require则根据请求的id,去modules中进行查找,如果没有依赖则直接返回找到的对象,如果有依赖,则先查找依赖模块中返回的对象,最后把它们传给回调函数。其中还考虑了id是数组的情况,也就是可以同时请求多个模块,这也只不过是循环解析这个过程而已。有些书上说,define就像是内鬼,而require则是侵略者。它们之间的关系就是里应外合,说的非常形象。浓缩才是精华,从这一百来行的代码中体验模块化的实现原理,是不是要比从几千行的require.js 中慢慢模索要容易地多呢?

    如果您觉得这文章对您有帮助,请点击推荐!谢谢,想跟我一起进步么?那就【关注】我吧!

  • 相关阅读:
    android控件
    解决C#项目出现“此项目引用这台计算机上缺少的 NuGet 程序包。使用 NuGet 程序包还原可下载这些程序包。有关详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 ..packagesMicrosoft.Net.Compilers.1.0.0uildMicrosoft.Net.Compilers.props”
    C#解密退款req_info结果通知
    解决C#中调用WCF方法报错:远程服务器返回错误 (404) 未找到
    解决网页出现 net::ERR_ABORTED 404 (Not Found)问题
    winform执行程序报错:已停止工作,windows正在检查该问题的解决方案
    C#操作数据表中XML格式的数据
    为了开篇
    iOS隐私政策
    在Android中调用KSOAP2库访问webservice服务出现的服务端传入参数为null的问题解决
  • 原文地址:https://www.cnblogs.com/afrog/p/4052645.html
Copyright © 2020-2023  润新知