javascript模块化(一)--总览

前言:很久没写文章总结了,这次主要粗略的总结一下 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

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


前端学习
在路上,beat generation

只有那些疯狂的人才能引起我的兴趣,那些人疯狂地生活,疯狂地表达,同时对一切事物心怀渴望,他们毫无...

446 声望
21 粉丝
0 条评论
推荐阅读
补全一段jQuery代码
主要思路是,window.$ = jQuery所以window.jQuery要等于一个函数,这个函数接收一个参数,返回一个对象,返回的对象包含两个方法,这两个方法可以添加text文本和添加class类

yangdepp3阅读 1.1k

从零搭建 Node.js 企业级 Web 服务器(零):静态服务
过去 5 年,我前后在菜鸟网络和蚂蚁金服做开发工作,一方面支撑业务团队开发各类业务系统,另一方面在自己的技术团队做基础技术建设。期间借着 Node.js 的锋芒做了不少 Web 系统,有的至今生气蓬勃、有的早已夭折...

乌柏木148阅读 12.2k评论 10

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy46阅读 5.9k评论 12

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木66阅读 6.1k评论 16

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs39阅读 6.3k评论 12

封面图
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木43阅读 7.3k评论 6

从零搭建 Node.js 企业级 Web 服务器(二):校验
校验就是对输入条件的约束,避免无效的输入引起异常。Web 系统的用户输入主要为编辑与提交各类表单,一方面校验要做在编辑表单字段与提交的时候,另一方面接收表单的接口也要做足校验行为,通过前后端共同控制输...

乌柏木33阅读 6.2k评论 9

只有那些疯狂的人才能引起我的兴趣,那些人疯狂地生活,疯狂地表达,同时对一切事物心怀渴望,他们毫无...

446 声望
21 粉丝
宣传栏