Skip to content

前端模块化开发

https://zhuanlan.zhihu.com/p/28573281

ES6 以来,有了 module 的体系,替代了之前比较常用的 CommonJS 和 AMD 规范,使得在编译时就能确定模块的依赖关系,同时使得前端在复杂项目时可以更好的分工和规范。

这篇文章主要介绍前端的模块化开发,主要包括 ES6 的 module 以及三个主要的模块化开发规范 CommonJS AMD 以及 CMD。

ES6 Module

ES6 的模块功能主要通过 export 以及 import 实现。模块中如果对外暴露接口就需要使用 export ,而如果想在模块中引用外部模块就需要使用 import。

典型的栗子就是 React,在 React 中我们十分常见的写法就是:

import React from 'react';
import {func as fu, bar} from './my_module';
...
export class MyClass extends React.Component {
    ...
}

当然我们不止是像 React 中导出一个类,只要想把模块中的变量、方法等等暴露出去,都可以使用 export。

注意下面几点:

1.在 import 中我们使用了大括号,其中大括号中的变量名必须和被导入模块中对外暴露的借口名称相同,其实就是一个解构操作。

2.在 import 的时候我们使用了 as 来区别名,将输入的 func 变量重新取名为 fu,这样做的好处是如果同时引入的两个模块中都含有 func 方法,通过区别名的方式就可以区分开来。其实在 export 的时候我们就可以使用别名。

3.同时注意 import 具有提升效果,因为是在编译阶段执行的,在代码运行之前。因此也不能使用一些运行时才能知道的语法结果,比如在 if else 语句之中判断 import 哪个模块。

4.在导出的时候可以使用export default,指定默认输出。在使用 default 之后我们在 export 的时候就不需要再给自己的函数等等取名字了,可以使用匿名函数。这样在 import 的时候可以自己随意取名字。

比如:

import $, {clone} from 'my_module';

在 my_module 中的写法就是:

export default function() { // 对应 import 中的 $
    ...
}
export function clone (obj) { // 对应 import 中的 {clone}
    ...
}

注意一个模块里面只能有一个 default

CommonJS

CommonJS 主要是在 Node 服务器端的规范。

CommonJS 也认为一个文件就是一个模块,必须通过某种导出才能使得别的模块使用该模块的对外接口。只是在 ES6 中使用 import export ,但是在 CommonJS 中使用 module.exports 对象进行导出,并且一个模块中只有这一个统一的出口。在外部模块中,我们使用 require 进行加载,这个方法读取一个文件并且返回文件中的 module.exports 对象。

例如:

定义一个模块 my_modules:

function bar() {
    ...
}
fucntion func() {
    ....
}
module.exports = {
    bar: bar,
    func: func
}

加载模块:

var needModules = require(./my_modules);
needModules.bar();
needModules.func();

主要注意几点: 1.加载的文件如果是“/”开头表示记载绝对路径的模块,以“./”开头表示加载相对路径的模块文件,两者都不含的话表示从系统的核心模块或者 node_modules 已经安装的模块。

2.模块加载会有缓存,存放在 require.cache 之中,并且缓存是根据绝对路径是识别的,同样的模块名放在不同的路径之中多次 require 的时候还是不会重新加载模块,除非使用 delete require.cache.

3.CommonJS是同步加载的,因此更适合服务器端。只有加载完成之后才能进行下面的操作。因为在服务器端模块文件一般存放在本地,再加上有缓存,加载速度十分快。因此这种就不适合用在浏览器端,浏览器端的各个 script 标签中的文件来自各个服务器,如果上个模块记载的时间很长,就会导致浏览器“假死”,因此浏览器端我们采用另外一种异步的加载方式–AMD。

AMD

AMD(Asynchronous Module Definition)就是异步加载模块,模块的加载不会影响后面代码的执行。所以依赖这个模块的代码都在一个回调函数中,等到所有模块加载完成之后就会执行这个回调函数。最常见的支持 AMD 规范的就是 RequireJS。

在 AMD 中,模块的加载也是使用 require([dependencies], function(){}) 语句,但是不同于 CommonJS,它的参数除了有需要加载的模块文件之后,还需要约定一个回调函数,由这个回调函数去规定当模块都加载完成之后执行什么操作,加载的模块会以参数形式传入该回调函数函数,从而在回调函数内部就可以使用这些模块。

而模块的定义主要是使用 define(id?, dependencies, factory)语句,其中 id 可选,定义模块的标识。 dependencies 可选参数,表示当前模块依赖的模块,是一个数组。 factory 是一个工厂方法,表示模块初始化需要执行的函数或者对象。一般我们会在这个 factory 中 return 出一个对象来暴露出我们的对外接口等等。

举个栗子: 定义一个模块 my_modules

define(['math'], function() {
    function bar() {
        ...
    }
    return {
        bar: bar
    };
});

加载模块

require([my_modules], function(my) {
    my.bar();
})

CMD

CMD(Common Module Definition)就是通用模块定义。CMD 是从国内发展出去的,实现主要是 SeaJS, 和 AMD 都是解决异步加载以及模块依赖的问题,但是在模块的定义方式上面有点不同。

在 CMD 中,定义模块使用 define(id?, deps?,factory),其中 id、depes 可以省略,字符串 id 表示模块标识,数组 deps 是模块依赖。当 factory 是一个函数的时候表示模块的构造方法,默认接受三个参数 require、export、module。

再举个栗子: 定义一个模块 my_module

define(function(require, export, module) {
    var person = {name: 'abby', age: 20};
    var _ = require('lodash'); // 需要的时候才去加载模块 lodash
    var shallow = _clone(person);
    // 支持异步加载模块,当多个模块加载完成之后才执行回调
    require.async('./a', ./b', function(a, b) {
        a.doSomething();
        b.doSomething();
    });
    bar() {
        ...
    }
    // 在导出模块的时候支持了 CommonJS 和 AMD 的写法。
    return {
        bar: bar
    };
    或者
    module.exports = {
        bar: bar
    }
    或者
    exports.bar = bar //此时注意不能写成对象形式
})

AMD CMD 区别

1.AMD推崇依赖前置,在定义模块的时候就声明依赖的模块。CMD 推崇依赖就近,只有在需要用的时候才去require 执行。

2.对于依赖的模块,AMD 提前执行,但是 CMD 的思想是 lazy loading, 延迟执行。但是现 RequireJS 2.0 也改成可以根据不同的写法进行延迟执行

UMD

UMD(Universal Module Definition)就是通用模块定义规范,是一种兼容 CommonJS 以及 AMD 的写法,具体实现就是使用一个立即执行的函数,然后将当前的运行环境 this 以及 factory 传过去,它会优先判断是否支持 AMD 环境,然后判断是否支持 CommonJS 环境来加载模块。

举个栗子:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery', 'lodash'], factory);
    } else if (typeof exports === 'object') {
        // CommonJS
        module.exports = factory(require('jquery'), require('lodash'));
    } else {
        // 在浏览器里面的全局变量即 window
        root.returnExports = factory(root.jQuery, root._);
    }
}(this, function ($, _) {
    function a(){};    //    没被返回,私有方法
    function b(){};    //    被返回了,公有方法
    function c(){};    //    被返回了,公有方法
    //暴露对外的接口
    return {
        b: b,
        c: c
    }
}));

本文只是很浅显的介绍,具体的用法可以参考文档。具体项目中使用哪种分情况而定。

(最近少女心爆棚,又做了张粉图。。其实我喜欢黑白。。噗嗤

参考资料:

Module

import

export

CommonJS规范

Why AMD

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

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

前端模块化

CMD 模块定义规范 #242

AMD 和 CMD 的区别有哪些

UMD Github

What Is AMD, CommonJS, and UMD?


Last update: September 12, 2022