JavaScript的不断发展,功能不断增强,代码也就越来越复杂,伪类便于管理和维护,模块化必不可少

规范的作用就是统一一规则,让所有人都可以分享让自己制作的模块;比如USB接口,鼠标线、U盘、数据线,只要实现了接口就以插入电脑,连接上外设。

CommonJS

  • 主要用于nodejs,服务端语言的,同步加载的模块规范

  • CommonJS也是一种规范,而nodejs的模块化内置API实现了这种规范

  • 使用

    main.js

    1
    2
    let moduleA = require('./moduleA')
    // do...

    moduleA.js

    1
    2
    3
    4
    function f1(){}
    module.exports = {
    f1:f1,
    }
  • CommonJS的原理大概是,先把模块的代码加上一层函数包裹,调用函数(利用eval函数),最后得到模块导出,多次导出会有缓存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function wrapper(script){
    let wrapper = ['function(exports,require,module,__filename,__dirname){','}']
    return wrapper[0] + script + wrapper[1]
    }

    function require(id){
    // 已存在在缓存,直接返回
    let cacheModule = Module._cache[id]
    if(cacheModule){
    return cacheModule
    }

    const module = {exports:{}}
    // 缓存
    Module._cache[id] = module

    // 执行代码
    eval(wrapper('module.exports = 1236'))(module.exports,require,module,'filename','dirname')

    return module.exports
    }

AMD和RequireJS

  • 主要用于浏览器JavaScript,客户端端语言的,异步加载的模块规范

    • 这是由于,同步加载会阻塞应用渲染,影响页面性能,所以客户端的模块化应该是异步加载的
    • 服务端nodejs因为模块本身就存在于服务器,同步加载和异步加载无影响(只看硬盘读取时间)
  • AMD是一种规范,而RequireJS是一个实现AMD规范的;当然,是RequireJS出现带动了AMD规范的制定,先有的库再有的规范…

  • RequireJS使用

    index.html

    1
    2
    3
    ...
    <script defer asyn="true" data-main="scripts/main" src="scripts/require.js"></script>
    ...
    • data-main: 指定入口,默认是js文件,所以scripts/main不用写成scripts/main.js

    • src: 首先要引入require.js

    main.js

    1
    2
    3
    reuire(['moduleA'],function($){
    // do...
    })

    require函数即指定了本文件所需要的依赖模块,第一个参数是一个数组指定所需要的依赖,第二个参数是依赖加载完的回调函数。加载的模块会被作为参数传入回调函数中;RequireJS会加载完所有的依赖模块后执行回调函数

    RequireJS默认模块moduleA就和main.js在同一目录下,所以也会在scripts/moduleA.js找到moduleA.js

    moduleA

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 不用依赖其他模块的
    define({
    f1:function(){},
    f2:function(){}
    })
    // 与上面等价
    define(function(){
    // 必须返回一个对象
    return {
    f1:function(){},
    f2:function(){},
    }
    })

    // 有依赖其他模块的
    define(['moduleB'],function (mB){
    // do...
    })

    参考:RequireJS和AMD规范

  • RequireJS推崇的是依赖前置,提前执行,也即,当声明需要某个模块时,模块在加载完就会马上执行

CMD和seaJS

  • 和AMD很类似,也是异步加载的规范,主要用于客户端js

  • CMD是一种规范,seaJS是实现了CMD规范的一个库;当然地,CMD也是seaJS在推广时指定的一种规范

  • 使用

    main.js

    1
    2
    3
    4
    5
    6
    define(function factory(require,exports,module){
    var a = require('a')
    console.log(a)
    var b = require('b')
    console.log(b)
    })

a.js

1
2
3
4
5
6
7
define(function factory(require,exports,module){
console.log('run a')
console.log(a)
exports = {
f1:function(){}
}
})
  • CMD和AMD不同在于

    • AMD推崇依赖前置,提前执行,把所有依赖都在最显眼地方声明好,同时下载完也是立即执行模块代码,推崇事先把一切都准备好;
    • 而CMD推崇的则是依赖就近,延迟执行,依赖的导入可以写在需要依赖的地方,下载完也会执行模块代码,但不执行define里面的代码,等到代码执行到某个依赖再执行依赖模块里面define的代码,推崇的是需要的时候再来弄。
  • 值得一提的是,AMD规范,把所有依赖都事先声明,下载资源。这种方式对于某些可能不执行的代码,性能上就不太好(多解析了一段代码)

    1
    2
    3
    if(status){
    // do...
    }

    而CMD规范,可以在条件里面声明然后使用,这样可以避免事先准备了无用的代码

UMD

  • Universal Module Definition; 统一模块定义(规范);

  • UMD是一种规范,旨在可以在服务端nodejs或者客户端js上使用统一的模块管理语法

  • UMD原理其实是基于客户端js使用的AMD规范(requireJS库)和服务端nodejs用的CommonJS,它在此之上加一个判断,有AMD语法则用AMD,没有AMD语法则看是否有CommonJS语法,有则用CommonJS,若也没有CommonJS语法则把暴露到全局上【因为UMD只是规范,不是库,只能靠人为加判断】

    以使用jquery作为一个模块为例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    (function (root, factory) {
    if (typeof define === 'function' && define.amd) {
    // AMD
    define(['jquery'], factory);
    } else if (typeof exports === 'object') {
    // Node, CommonJS之类的
    module.exports = factory(require('jquery'));
    } else {
    // 浏览器全局变量(root 即 window)
    root.returnExports = factory(root.jQuery);
    }
    }(this, function ($) {
    // 方法
    function myFunc(){};

    // 暴露公共方法
    return myFunc;
    }));
  • 若使用了打包工具,打包称UMD规范,则打包工具就会自己生成这样的代码(如webpack的umd模式)

ES6 Module

  • ES6把js的模块化纳入了标准,使用import,export

  • ES6 Module

    index.html

    1
    <script type="module" src="./main.js"></script>

    需要注意的是,file://等直接浏览html文件会报错;使用服务器时,返回的模块文件时,http头的content-type的MIME类型应该为text/javascript;

    ES6 Module 可以使用后缀为.mjs 来代表js模块文件,与普通js文件,后缀为.js区分开

    在前面已经提到了,在这里再重申一次: .mjs 后缀的文件需要以 MIME-type 为 javascript/esm 来加载(或者其他的JavaScript 兼容的 MIME-type ,比如 application/javascript), 否则,你会一个严格的 MIME 类型检查错误,像是这样的 “The server responded with a non-JavaScript MIME type”.

    main.js

    1
    2
    3
    4
    import { count,countAdd } from './moduleA.js'
    console.log(count) // 3
    countAdd()
    console.log(count) // 4,这里和commonjs不同,commonjs的不会加以,因为是值复制;而ES6 Module是值的引用所以count会加一

    moduleA.js

    1
    2
    3
    4
    5
    6
    7
    8
    var count = 3
    function countAdd(){
    count++
    }
    export {
    count:count,
    countAdd:countAdd,
    }

值得注意的是,模块文件中,顶级的this是undefined的(正常js文件是window)

  • ES Module和CommonJs的区别
    • 首先,ES Module是异步加载的,而CommonJS是同步加载的。客户端页面在用户机器上,加载资源要靠网络,考虑用户体验,客户端一般采用异步加载,服务端因为是在服务器上的,资源在本地磁盘读取很快,所以可以直接使用同步加载
    • 另外,ES Module是值的引用,CommonJS是值的复制;CommonJS在运行时加载,然后执行代码,暴露的是一个对象,对象就是模块里面module.exports对象,const a = require()其实就是这个对象赋值给a,是一个值的拷贝(也即,模块的string等非引用类型是不会被 调用模块 修改的);而ES Module要求import ,export在最顶部且不可拼接路径,因为ES Module是静态识别的,在编译时就加载好,最终得到模块里面的变量一个引用(非赋值),所以当调用模块改变被调用模块时,被调用模块本身的值就会被改变(string,number等也是)
    • 语法上不同

参考

Javascript模块化编程(一):模块的写法

Javascript模块化编程(二):AMD规范

Javascript模块化编程(三):require.js的用法

JavaScript modules 模块

深入 CommonJs 与 ES6 Module

最后更新: 2021年08月18日 16:03

原始链接: https://idkhts.github.io/2021/08/18/js%E6%A8%A1%E5%9D%97%E5%8C%96/