JavaScript中的模块化开发

一、为什么会有模块化


1. 当一个项目开发的越来越复杂的时候,会遇到一些问题,比如:

  • 命名冲突:当项目由团队进行协作开发的时候,不同开发人员的变量和函数命名可能相同;即使是一个开发,当开发周期比较长的时候,也有可能会忘记之前使用了什么变量,从而导致重复命名,导致命名冲突。

  • 文件依赖:代码重用时,引入js文件的数目可能少了,或者引入的顺序不对,比如使用boostrap的时候,需要引入jQuery,并且jQuery的文件必须要比boostrap的js文件先引入。

2. 当使用模块化开发的时候可以避免以上的问题,并且让开发的效率变高,以及方便后期的维护:

  • 提升开发效率:代码方便重用,别人开发的模块直接拿过来就可以使用,不需要重复开发法类似的功能。

  • 方便后期维护:代码方便重用,别人开发的模块直接拿过来就可以使用,不需要重复开发法类似的功能。

所以总结来说,在生产角度,模块化开发是一种生产方式,这种方式生产效率高,维护成本低。从软件开发角度来说,模块化开发是一种开发模式,写代码的一种方式,开发效率高,方便后期维护。

二、模块化开发的演变过程


1. 全局函数

function add(a , b) {
    return parseFloat(a) + parseFloat(b);
}
function substract(a ,b) {}
function multiply(a ,b) {}
function divide(a ,b) {}

在早期的开发过程中就是将重复的代码封装到函数中,再将一系列的函数放到一个文件中,这种情况下全局函数的方式只能认为的认为它们属于一个模块,但是程序并不能区分哪些函数是同一个模块,如果仅仅从代码的角度来说,这没有任何模块的概念。

存在的问题:

  • 污染了全局变量,无法保证不与其他模块发生变量名冲突。
  • 模块成员之间看不出直接关系。

2. 对象封装-命名空间

var calculator = {
  add: function(a, b) {
    return parseFloat(a) + parseFloat(b);
  },
  subtract: function(a, b) {},
  multiply: function(a, b) {},
  divide: function(a, b) {}
};

通过添加命名空间的形式从某种程度上解决了变量命名冲突的问题,但是并不能从根本上解决命名冲突。 不过此时从代码级别可以明显区分出哪些函数属于同一个模块。

存在的问题:

  • 暴露了所有的模块成员,内部状态可以被外部改写,不安全。
  • 命名空间越来越长。

3. 私有公有成员分离

var calculator = (function () {
    // 这里形成一个单独的私有的空间
    // 私有成员的作用:
    //   1、将一个成员私有化
    //   2、抽象公共方法(其他成员中会用到的)
    
    // 私有的转换逻辑
    function convert(input){
        return parseInt(input);
    }

    function add(a, b) {
        return convert(a) + convert(b);
    }
    function subtract(a, b) {}
    function multiply(a, b) {}
    function divide(a, b) {}
    return {
        add : add,
        subtract : subtract,
        multiply : multiply,
        divide : divide
    }
})();
  1. 利用此种方式将函数包装成一个独立的作用域,私有空间的变量和函数不会影响到全局作用域。
  2. 以返回值的方式得到模块的公共成员,公开公有方法,隐藏私有空间内部的属性、元素,比如注册方法中可能会记录日志。
  3. 可以有选择的对外暴露自身成员。
  4. 从某种意义上来说,解决了变量命名冲突的问题。

4. 模块的扩展与维护

// 计算模块
(function (calculator) {
    function convert(input) {
        return parseInt(input);
    }
    calculator.add = function(a, b) {
        return convert(a) + convert(b);
    }
    window.calculator = calculator;
})(window.calculator || {});

// 新增需求
(function (calculator) {
    calculator.remain = function (a , b) {
        return a % b;
    }
    window.calculator = calculator;
})(window.calculator || {});
        
alert(calculator.remain(4,3));
  1. 利用此种方式,有利于对庞大的模块的子模块划分。
  2. 实现了开闭原则:对新增开放,对修改关闭。对于已有文件尽量不要修改,通过添加新文件的方式添加新功能。

5. 第三方依赖的管理

(function (calculator , $) {
    // 依赖函数的参数,是属于模块内部
    // console.log($);
    calculator.remain = function (a , b) {
        return a % b;
    }
    window.calculator = calculator;
})(window.calculator || {} , jQuery);

模块最好要保证模块的职责单一性,最好不要与程序的其他部分直接交互,通过向匿名函数注入依赖项的形式,除了保证模块的独立性,还使模块之间的以来关系变得明显。
  对于模块的依赖通过自执行函数的参数传入,这样做可以做到依赖抽象,本例中使用的jQuery,而当要使用zepto的时候,只要更换传入的参数即可。
  原则:高内聚低耦合,模块内相关性高,模块间关联低。

总结:在什么场景下使用模块化开发

  • 业务复杂
  • 重用逻辑非常多
  • 扩展性要求较高

三、模块化规范


服务器端规范主要是CommonJSnode.js用的就是CommonJS规范。
  客户端规范主要有:AMD(异步模块定义,推崇依赖前置)、CMD(通用模块定义,推崇依赖就近)。AMD规范的实现主要有RequireJSCMD规范的主要实现有SeaJSRequireJS在国外用的比较多,SeaJS在国内用的比较多,并且SeaJS的创始人为阿里的玉伯,所以SeaJS在阿里系用的非常广泛,包括京东等大厂也在用SeaJS,我们详细介绍的也是SeaJS。但是SeaJS已经停止维护了,因为在ES6中已经有了模块化的实现,随着ES6的普及,第三方的模块化实现将会慢慢的淘汰(但是这个在国内可能还要很多年)。

猜你喜欢

转载自blog.csdn.net/risingfine/article/details/83863489