2

前言:很久没写文章总结了,这次主要粗略的总结一下 js中的模块化

1.模块

模块的职责:封装实现,暴露接口,声明依赖。先来一个对比,下面代码没有应用任何模块系统

(1)无封装

math.js:(1)没有封装性(2)接口不明显

    function add(a,b){
        return a+ b
    }
    function sub(a,b){
        return a - b
    }

caculator.js:(1)依赖math.js但是没有依赖声明 (2)使用全局状态

    var action = "add";
    function compute(a,b){
        switch (action){
            case "add": return add(a,b)
            case "sub": return add(a,b)
        }
    }

(2)字面量

math.js:(1)结构性好(2)访问控制没有

    var math = {
        add:function add(a,b){
            return a+ b
        },
        sub:function sub(a,b){
            return a - b
        }
    }

caculator.js:(1)依赖math.js但是没有依赖声明 (2)无法标明属性是私有,无封装

    var caculator = {
        action:'add';
        compute:function compute(a,b){
            switch (action){
                case "add": return math.add(a,b)
                case "sub": return math.add(a,b)
            }
        }
    }

(3)IIFE:字值型的函数表达式

可以创建一个局部作用域,封装内部成员变量,通过return输出需要输出的接口

版本一:caculator-1.js:(1)实现了访问控制(2)依然没有依赖声明

    var caculator = (function(){
        var action = "add";
        return{
        compute:function compute(a,b){
            switch (action){
                case "add": return math.add(a,b)
                case "sub": return math.add(a,b)
            }
        }
    }
})()

版本二:caculator-2.js:(1)显示依赖声明(2)仍然污染了全局变量(3)必须手动进行依赖管理
揭露模块模式:return部分与版本一不太一样,把方法定义在函数体里面,return的只是方法。具体可参考这位童鞋的文章:Javascript 设计模式 -- Revealing Module(揭示模块)模式

    var caculator = (function(m){
        var action = "add";
        function compute(a,b(){
            switch (action){
                case "add": return m.add(a,b)
                case "sub": return m.add(a,b)
            }
        }
        return{
            compute:compute
    }
})(math)

(4)命名空间

解决暴露全局变量的问题,只暴露一个类似namespace的全局变量就实现所有模块的声明

math.js:(1)第一个参数是模块声明;(2)第二个参数是声明依赖,目前是没有依赖的;(3)第三个参数是模块的构成

    namespace("math",[],function(){
        function add(a,b){
            return a+ b
        }
        function sub(a,b){
            return a - b
        }
        return{
            add:add,
            sub:sub
        }
    })

caculator.js:(1)有依赖生命(2)依赖math被当做参数传入

    namespace("caculator",["math"],function(m){
        var action = "add"
        function compute(a,b){
            return m[action](a,b)
        }
        return{
            compute:compute
        }
    })

namespace的代码:还是没有解决依赖管理的问题,如果各个模块分散在不同的文件中,就要对脚本加载顺序进行手动的排序

    var namespace = (function(){
        //缓存所有的模块
        var cache = {}
        function createModule(name,deps,definition){  //参数是:模块名,依赖列表,定义
            //先对参数进行判断,如果只有一个参数,就返回
            if(arguments.length === 1){
                return cache[name]
            }
            //必须取得所有依赖的模块,要保证前面的模块已经被定义好了
            deps = deps.map(function(depName){
                return ns(depName)
            })
            //初始化模块并返回
            cache[name] = definition.apply(null,deps)
            return cache[name];
        }
        return createModule
    })()

2.模块系统

职责:(1)依赖管理:加载/分析/注入/初始化 (2)决定模块的写法
下面总结三种典型的模块系统的写法

(1)commonjs

优点:

  1. 依赖管理成熟可靠

  2. 社区活跃,规范接受度高

  3. 运行时支持,模块定义非常简单

  4. 文件级别的模块作用域隔离

  5. 可以处理循环依赖

缺点:

  1. 不是标准组织的规范

  2. 同步的require,没有考虑浏览器异步加载的过程

    但是还是有办法使用的,目前有很多工具可以把多个模块的文件打包成一个文件:browserify,webpack,component

看下面用commonjs写就的代码:
math.js:

    function add(a,b){
        return a+ b
    }
    function sub(a,b){
        return a - b
    }
    exports.add = add
    exports.sub = sub

caculator.js:

       var math = require("./math");  //依赖声明
        function Caculator(container){
            this.left = container.querySelector(".j-left" );
            this.right = container.querySelector(".j-right" );
            this.add = container.querySelector(".j-add" );
            this.result = container.querySelector(".j-result");
            
            this.add.addEventListener("click",this.compute.bind(this));
        }
        Caculator.prototype.compute = function(){
            this.result.textContent = math.add(+this.left.value, +this.right.value)
        }
        exports.Caculator = Caculator;  //暴露接口

用前端打包工具进行打包math.jscaculator.js
首先安装browserify,在命令行输入命令:browserify caculator.js > caculator-bundle.js
打包成形如命名空间的文件形式

(2)AMD

天然的作用于异步环境
AMD代码写法:
math.js:第一个参数是依赖列表

    define([],function(){
        function add(a,b){
            return a+ b
        }
        function sub(a,b){
            return a - b
        }
        return{  //接口暴露
            add:add,
            sub:sub
        }
    })

caculator.js:参数一是依赖声明,参数二是依赖注入

    define(["./math"],function(math){
    
        function Caculator(container){
                this.left = container.querySelector(".j-left" );
                this.right = container.querySelector(".j-right" );
                this.add = container.querySelector(".j-add" );
                this.result = container.querySelector(".j-result");
                
                this.add.addEventListener("click",this.compute.bind(this));
            }
            Caculator.prototype.compute = function(){}
            return{
                Caculator:Caculator
            }
        })

AMD还支持一个叫Simplified CommonJS wrapping

    define(function(require,exports){
        var math = require("./math");
        function Caculator(container){
                this.left = container.querySelector(".j-left" );
                this.right = container.querySelector(".j-right" );
                this.add = container.querySelector(".j-add" );
                this.result = container.querySelector(".j-result");
                
                this.add.addEventListener("click",this.compute.bind(this));
            }
            Caculator.prototype.compute = function(){}
            exports.Caculator = Caculator;
        })

上述如何获取依赖列表呢?
函数通过toString可以打印出它的函数体,然后用正则表达式提取出来
factory.toString()
/require\(['"]([^'"]*)['"]\)/.exec(factory.toString())[1]

优点:

  1. 依赖管理成熟可靠

  2. 社区活跃,规范接受度高

  3. 专为异步IO打造,适合浏览器环境

  4. 支持类似Commonjs的书写方式

  5. 通过插件api可支持加载非js资源

  6. 成熟的打包构建工具,并可结合插件

缺点:

  1. 模块定义繁琐,需要额外嵌套

  2. 只是库级别的支持,需要引入额外的库

  3. 无法处理循环依赖

  4. 无法实现条件加载

(3)ES6/module

语言级别的支持,未来的模块化


yangdepp
446 声望21 粉丝

只有那些疯狂的人才能引起我的兴趣,那些人疯狂地生活,疯狂地表达,同时对一切事物心怀渴望,他们毫无倦意,不屑于陈词滥调,只是燃烧,燃烧,燃烧。