前端模块化那点事-个人总结

模块化

其实我理解的模块化就是封装代码,将一个功能的代码封装到一个模块,然后需要使用时就加载这个模块

模块化开发的好处:

  • 避免变量污染,命名冲突

  • 提高代码复用率

  • 提高维护性

  • 依赖关系的管理

原生模块化

所谓的“原生模块化”,是指模块化规范没有诞生之前的所使用的一种代码封装方式

  1. 函数封装

    函数都是挂载在window对象上的,也叫全局函数封装(全局function),污染了全局空间

    function m1(){
      //...
    }
    function m2(){
      //...
    }
    
  2. namespace模式

    解决了全局空间污染问题,但是内部模块的数据全部暴露,可以直接修改数据

    let myModule = {
      data: 'www.baidu.com',
      foo() {
        console.log(`foo() ${this.data}`)
      },
      bar() {
        console.log(`bar() ${this.data}`)
      }
    }
    myModule.data = 'other data' //能直接修改模块内部的数据
    myModule.foo() // foo() other data
    
  3. IIFE函数匿名自调用(闭包)

    作用:数据是私有的,外部只能操作暴露的方法

    将暴露的模块挂载到window对象,引用js模块文件后可直接访问模块方法

    但是,如果存在模块依赖怎么办?

    // module.js文件
    (function(window) {
      let data = 'www.baidu.com'
      //操作数据的函数
      function foo() {
        //用于暴露有函数
        console.log(`foo() ${data}`)
      }
      function bar() {
        //用于暴露有函数
        console.log(`bar() ${data}`)
        otherFun() //内部调用
      }
      function otherFun() {
        //内部私有的函数
        console.log('otherFun()')
      }
      //暴露行为
      window.myModule = { foo, bar } //ES6写法
    })(window)
    
    // index.html文件
    <script type="text/javascript" src="module.js"></script>
    <script type="text/javascript">
        myModule.foo()
        myModule.bar()
        console.log(myModule.data) //undefined 不能访问模块内部数据
        myModule.data = 'xxxx' //不是修改的模块内部的data
        myModule.foo() //没有改变
    </script>
    
  4. IIFE模块增强:引入依赖(这就是现代模块实现的基石)

    如果模块有依赖,先调用依赖的模块在调用使用依赖的模块文件,通过(function(对应依赖名的形参){})(依赖名)

    // module.js文件
    (function(window, $) {
      let data = 'www.baidu.com'
      //操作数据的函数
      function foo() {
        //用于暴露有函数
        console.log(`foo() ${data}`)
        $('body').css('background', 'red')
      }
      function bar() {
        //用于暴露有函数
        console.log(`bar() ${data}`)
        otherFun() //内部调用
      }
      function otherFun() {
        //内部私有的函数
        console.log('otherFun()')
      }
      //暴露行为
      window.myModule = { foo, bar }
    })(window, jQuery)
    
     // index.html文件
      <!-- 引入的js必须有一定顺序 -->
      <script type="text/javascript" src="jquery-1.10.1.js"></script>
      <script type="text/javascript" src="module.js"></script>
      <script type="text/javascript">
        myModule.foo()
      </script>
    

CommonJs

commonJs标志着“JavaScript模块化编程”诞生了。

node.js的模块系统,就是参照CommonJS规范实现的。

因为CommonJs是同步加载模块,对于浏览器而言是不适用的。因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。

  1. 通过module.exports暴露模块

    //module1.js
    
    // module.exports = value 暴露一个对象
    module.exports = {
        msg:'module1',
        foo(){
            console.log("foo()",this.msg);
        }
    }
    
  2. 通过require()导入模块

    let uniq=require('uniq')
    
    let module1 = require('./modules/module');
    module1.foo();
    

AMD(Asynchronous Module Definition)

异步模块,是为了解决同步加载问题。
AMD对应的就是很有名的RequireJS。

  • requireJS主要解决两个问题

    多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
    js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长

  1. 定义暴露模块

    requireJS定义了一个函数 define,它是全局变量,用来定义模块

    define(id?, dependencies?, factory);

    id:可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名(去掉拓展名)
    dependencies:是一个当前模块依赖的模块名称数组
    factory:工厂方法,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值

    • 定义没有依赖的模块
    // md1.js
    define(function() {
        var name = 'Ink-模块2';
        function getMinNumber(arr) {
            return Math.min.apply(null, arr)
        }
        return { 'name': name, 'getMinNumber': getMinNumber }
    });
    
    • 定义有依赖的模块
    // md2.js
    define(['md1'], function(md1) {
        function getMinNumber(arr) {
            console.log('此时调用md1的name:' + md1.name);
            return Math.min.apply(null, arr)
        }
        return { 'getMinNumber': getMinNumber }
    });
    

    2.require模块requireJs文档

    require([dependencies], function(){});

    第一个参数是一个数组,表示所依赖的模块
    第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块
    require()函数在加载依赖的函数的时候是异步加载的,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。

    // 1.requireJs模块配置
    requirejs.config({
        // baseUrl: 'js/lib',
        paths: {
            md1: './module/md1',
            md2: './module/md2',
            // 引用第三方模块
            jquery: './libs/jquery'
        }
    });
    // 2.引入模块
    requirejs(['md1', 'md2', 'jquery'], function(ModuleA, ModuleB, $) {
        "use strict";
        console.log('初始化模块A', ModuleA);
        let arr = [1, 432, 4, 56, 99];
        console.log('数组:' + arr + '\n取最大值:\t' + ModuleA.getMaxNumber(arr));
        console.log('初始化模块B', ModuleB);
        console.log('数组:' + arr + '\n取最小值:\t' + ModuleB.getMinNumber(arr));
        // 3.引用第三方模块(先在config中配置)
        $('body').css({ 'background': 'darkblue' })
    });
    
    1. HTML页面
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>AMD规范</title>
    </head>
    <body>
    <!-- data-main是主js模块入口,就是说requireJs加载处理后会加载data-main中的main.js -->
        <script src="./require.js" data-main='main.js'></script>
    </body>
    </html>
    

CMD(Common Module Definition)

即通用模块定义,对应SeaJS,是阿里玉伯团队首先提出的概念和设计。跟requireJS解决同样问题,只是运行机制不同:是通过按需加载的方式,而不是必须在模块开始就加载所有的依赖

  1. 模块定义

    define(id?, deps?, factory)
    因为CMD推崇一个文件一个模块,所以经常就用文件名作为模块id
    CMD推崇依赖就近,所以一般不在define的参数中写依赖,在factory中写
    factory有三个参数

    function(require, exports, module)

    require代表依赖其他的模块
    exports代表导出给别人的模块
    module代表一个模块

    define(function(require, exports, module) {
        'use strict';
        function getMaxNumber(arr) {
            return Math.max.apply(null, arr)
        }
        let name = 'm1';
        let version = 'v1.0';
        exports.name = name;
        exports.version = version;
        exports.getMaxNumber = getMaxN
      umber;
    });
    
  2. 通过seajs函数库引用模块seajs文档

    seajs.use('./module/m1', function(m1) {
        console.log(m1);
    })
    
  3. HTML页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>CMD规范</title>
    </head>
    <body>
        <script src="./libs/sea.js"></script>
        <script src="./main.js"></script>
    </body>
    </html>
    

ES6模块

使用export指令导出了模块对外提供的接口,下面我们就可以通过 import 命令来加载对应的这个模块了

  1. export模块

    export指令用于导出变量

    // info.js
    
    let author = 'Ink-kai';
    let version = 'v1.1';
    
    // 某些情况下一个模块中包含某个功能,我们并不希望给这个功能命名,而是让导入者可以自己来命名
    // default是export对象自带的一个默认对象
    export default {
        author,
        version
    }
    // export function可以直接通过模块名.getMinNum(arr)调用
    export function getMinNum(arr) {
        return Math.min.apply(null, arr)
    }
    
  2. import模块

    用于导入模块中的内容

    // main.js 主模块
    
    // *表示导出模块所有对象(export模块的内容) as 别名
    import * as md1 from './module/info.js'
    
    let arr = [11, 323, 4, 5];
    console.log('info模块:', md1);
    console.log(arr + '\n最小值:' + md1.getMinNum(arr));
    console.log('author:' + md1.author);
    console.log('info模块版本:' + md1.version);
    
  3. HTML页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>ES6模块</title>
    </head>
    <body>
        <!-- 类型需要设置为module -->
        <script type="module" src="./main.js"></script>
    </body>
    </html>
    

AMD与CMD区别

最明显的区别就是在模块定义时对依赖的处理不同

AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块
CMD推崇就近依赖,只有在用到某个模块的时候再去require
看到很多网站说AMD是异步加载,CMD是同步加载,肯定是不准确的,他们的都是异步加载模块。

对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.

CMD 推崇依赖就近,AMD 推崇依赖前置

通俗来说:
AMD在加载完成定义(define)好的模块就会立即执行,所有执行完成后,遇到require才会执行主逻辑。(提前加载)
CMD在加载完成定义(define)好的模块,仅仅是下载不执行,在遇到require才会执行对应的模块。(按需加载)
AMD用户体验好,因为没有延迟,CMD性能好,因为只有用户需要的时候才执行。

CMD为什么会出现,因为对node.js的书写者友好,因为符合写法习惯,就像为何vue会受人欢迎的一个道理。

官方阐述SeaJS与RequireJS异同

两者的主要区别如下:

  1. 定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。Sea.js 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 环境中。
  2. 遵循的规范不同。RequireJS 遵循 AMD(异步模块定义)规范,Sea.js 遵循 CMD (通用模块定义)规范。规范的不同,导致了两者 API 不同。Sea.js 更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
  3. 推广理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。Sea.js 不强推,采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。
  4. 对开发调试的支持有差异。Sea.js 非常关注代码的开发调试,有 nocache、debug 等用于调试的插件。RequireJS 无这方面的明显支持。
  5. 插件机制不同。RequireJS 采取的是在源码中预留接口的形式,插件类型比较单一。Sea.js 采取的是通用事件机制,插件类型更丰富。
posted @ 2021-02-17 01:26  InkCat  阅读(56)  评论(0编辑  收藏  举报