前端模块化发展史

模块化的优势不言而喻:

  1. 避免命名冲突
  2. 依赖管理
  3. 提供可维护性和代码复用

相对于其他静态语言,JS最大缺陷就是天生不具有模块化,没有语言层面的命名空间的概念。问题如下:

  1. 编写通用模块很容易将其暴露给全局作用域,造成命名冲突。
  2. 浏览器按照从上到下解析HTML文档,多个<script>标签按照次序解析加载。这种机制导致存在依赖关系的JS模块,必须被依赖者先执行,依赖者后执行。比如使用jquery时,必须先导入jquery
<script src='jquery.js'></script>
<script src='jquery.plugin.js'></script>

一直以来,前端工程师们都在探索如何做到JavaScript模块化,其中以CommonJS,AMD,CMD为代表。

直到ES6 Module的出现,才从语言层面上解决了JS模块化的问题。

1. CommonJS(面对浏览器之外的模块化规范)

CommonJS是针对服务器或桌面应用环境下的JavaScript开发(非浏览器),其被Node.js采用为默认的模块化规范,并随着Node.js的流行被广大Web开发者熟知。

CommonJS有两个弊端,使其不适合浏览器环境开发:

  1. 无法处理非JavaScript资源(比如CSS,图片等)。
  2. 模块是同步阻塞式加载,无法实现按需加载(像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式。)。

使用方法如下:

  • 模块导出 exports
  • 模块导入require
// foobar.js
//私有变量
var test = 123;
 
//公有方法
function foobar () {
 
    this.foo = function () {
        // do someing ...
    }
    this.bar = function () {
        //do someing ...
    }
}
 
//exports对象上的方法和变量是公有的var foobar = new foobar();
exports.foobar = foobar;

// main.js
//require方法默认读取js文件,所以可以省略js后缀
var test = require('./foobar').foobar;
 
test.bar();

2. AMD & CMD(针对浏览器的模块化规范)

在CommonJS基础上,还扩展了以下功能:

  1. 可以处理JavaScript以外的资源。
  2. 可按需加载,可并行加载。

AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义";CMD是SeaJS 在推广过程中对模块定义的规范化产出。使用方式如下:

//AMD
define(['./a','./b'], function (a, b) {
 
    //依赖一开始就写好
    a.test();
    b.test();
});
 
//CMD
define(function (requie, exports, module) {
    //依赖可以就近书写
    var a = require('./a');
    a.test();
    ...
    //软依赖
    if (status) {
        var b = requie('./b');
        b.test();
    }
});

上面的例子可以看出两者的区别:

  • 对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。
  • CMD推崇依赖就近,AMD推崇依赖前置
  • AMD会先下载且执行依赖模块(哪怕后续没有用到该依赖模块),再执行主模块;CMD只会下载依赖模块,不预执行,直到调用子模块时,才执行子模块(比如,加载子模块依赖的模块)

3. ES6 Module - 规范的静态模块体系

ES6 Module是语言层面的规范,与应用场景无关,完全可以取代现有的 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
目前受限与浏览器的实现程度,针对浏览器的模块化仍然需要构建工具进行编译。

ES6 Module与CommonJS类似,是一种静态模块规范,不支持异步加载。不过,结合其他框架或者构建工具(比如React的lazy load,webpack的require.ensure)可以实现按需加载和异步加载。

// react lazy loader
const Home = React.lazy(() => import('./pages/Home'));
const Page1 = React.lazy(() => import('./pages/Page1'));
const Page2 = React.lazy(() => import('./pages/Page2'));

// webpack
window.onload =require.ensure([], require => {
  const b = require('./module.a.js');
  b()
}, 'async')

注意:CommonJS模块输出的是值拷贝,但是,ES6模块输出的是值引用

ES6 Module具体用法可参考:ECMAScript6基础学习教程(七)模块

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 前端模块化实现现状 1. <script> 标签 这个是通过把接口暴露到window全局变量上,其他部分通过...
    wpzero阅读 987评论 0 2
  • 前言在 JavaScript 发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可;如今 CPU、浏览器性能得到了...
    前端一菜鸟阅读 862评论 0 9
  • 媳妇跟丈夫说:萝卜要切成"丁",煮的汤才会好吃。 丈夫在厨房自言自语 :这也太难了吧!都切了1 小...
    无欲则刚66阅读 740评论 0 0
  • 下面,通过微调的变式方法,我们将学生平时的一篇习作进行了改编,让同学们直观感受平时习作的“变形记”,体会“看破千道...
    实实在在教语文阅读 2,666评论 2 6
  • 恋爱麻烦,我们直接结婚吧。到了这个年纪已经谁也不想去取悦了,丧失了很多的冲劲和动力,可还是到了这个可怕的年纪。我一...
    19f4528712dd阅读 128评论 0 0