JS 模块化方案对比

1. CommonJS 规范(同步加载 NodeJS)

// 导入
require("module");
require("../app.js");

// 导出
exports.getStoreInfo = function() {};
module.exports = someValue;

// 优点:使用上更简单,因为同步加载
// 缺点:在浏览器上,同步意味着阻塞加载,不能非阻塞的并行加载多个模块,客户端网络不可控
// 实现:NodeJS

2. AMD(异步加载模块 requireJS)

采用异步方式加载模块,模块的加载不影响后面语句的运行。所有依赖模块的语句,都定义在一个回调函数中,等到加载完成之后,回调函数才执行。

// 定义
define("module", ["dep1", "dep2"], function(d1, d2) {...});
                                                     
// 加载模块
require(["module", "../app"], function(module, app) {...});
  • 加载模块 require([module], callback);
    • 第一个参数[module],是一个数组,里面的成员就是要加载的模块;
    • 第二个参数callback是加载成功之后的回调函。
  • 优点:
    • 适合在浏览器环境中异步加载模块
    • 可以并行加载多个模块
  • 缺点:
    1. 提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不顺畅
    2. 不符合通用的模块化思维方式,是一种妥协的实现
  • RequireJS执行流程:
    1. require 函数检查依赖的模块,根据配置文件,获取js文件的实际路径
    2. 根据js文件实际路径,在dom中插入script节点,并绑定onload事件来获取该模块加载完成的通知。
    3. 依赖script全部加载完成后,调用回调函数

RequireJS 对模块的态度是预执行。由于 RequireJS 是执行的 AMD 规范, 因此所有的依赖模块都是先执行;即 RequireJS 是预先把依赖的模块执行,相当于把 require 提前了。

3. CMD规范(异步加载模块 SeaJS)

  • CMD规范和AMD很相似,一个模块就是一个文件。
  • 定义模块使用全局函数 define,其接收 factory 参数,factory 可以是一个函数,也可以是一个对象或字符串;
  • factory 是一个函数,有三个参数,function(require, exports, module):
    1. require 是一个方法,接受模块标识作为唯一参数,用来获取其他模块提供的接口:require(id)
    2. exports 是一个对象,用来向外提供模块接口
    3. module 是一个对象,上面存储了与当前模块相关联的一些属性和方法
define(function(require, exports, module) {
  var a = require('./a');
  a.doSomething();

  // 依赖就近书写,什么时候用到什么时候引入
  var b = require('./b');
  b.doSomething();
});
  • 优点:依赖就近,需要使用到模块时,显示的使用 require 来执行
  • 缺点:模块的加载逻辑偏重
  • 实现:SeaJS

4. AMD 与 CMD 的区别

  1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从2.0开始,也改成了可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
  2. AMD推崇依赖前置;CMD推崇依赖就近,只有在用到某个模块的时候再去require。
// AMD
define(['./a', './b'], function(a, b) {  // 依赖必须一开始就写好  
   a.doSomething()    
   // 此处略去 100 行    
   b.doSomething()    
   ...
});

// CMD
define(function(require, exports, module) {
   var a = require('./a')   
   a.doSomething()   
   // 此处略去 100 行   
   var b = require('./b') 
   // 依赖可以就近书写   
   b.doSomething()
   // ... 
});

5. UMD (统一模块规范)

  • UMD 是 AMD 和 CommonJS 的糅合
  • UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式;在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
(function (window, factory) {
    if (typeof exports === 'object') {
    
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
    
        define(factory);
    } else {
    
        window.eventUtil = factory();
    }
})(this, function () {
    //module ...
});

6. ESModule 规范

  • ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案;
  • ES6 模块设计原则:尽量的静态化、使得编译时就能确定模块的依赖关系;
// 导入
import "/app";
import React from “react”;
import { Component } from “react”;

// 导出
export function multiply() {...};
export var year = 2018;
export default ...
...
  • 优点:
    1. 容易进行静态分析
    2. 面向未来的 EcmaScript 标准
  • 缺点:
    1. 原生浏览器端还没有实现该标准

7. require(commonJS) 与 import(ESModule) 的区别

require使用与CommonJs规范,import使用于Es6模块规范;所以两者的区别实质是两种规范的区别;

7.1 CommonJS

  1. 对于基本数据类型,属于复制。即会被模块缓存;同时,在另一个模块可以对该模块输出的变量重新赋值。
  2. 对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
  3. 当使用 require 命令加载某个模块时,就会运行整个模块的代码。
  4. 当使用 require 命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS 模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
  5. 循环加载时,属于加载时执行。即脚本代码在 require 的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。

7.2 ES6模块

  1. ES6模块中的值属于【动态只读引用】。
  2. 对于只读来说,即不允许修改引入变量的值,import 的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到 import 命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
  3. 对于动态来说,原始值发生变化,import 加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
  4. 循环加载时,ES6 模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。

8. ESModule 模块加载器的实现

使用 webpack 打包项目,会把 webpack 实现的模块加载器也打包进去,可以去看一下它的代码。

9. 在浏览器端尽量使用 ESModule

使用 ESModule 可以避免 tree-shaking 技术失效,因为它是静态分析的,如果使用 commonJS 会导致 tree-shaking 失效。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容

  • CommonJS中require的功能是加载并执行一个js文件,并返回该文件的exports对象,是同步的。由于在...
    欧石楠阅读 149评论 0 0
  • 因为内容太多,没有大纲不方便阅读,所以也可以跳转 前端各种模块化方案总结 附带大纲[https://tenloy....
    Tenloy阅读 4,300评论 0 6
  • 模块化主要是用来抽离公共代码,隔离作用域,避免变量冲突等。将一个复杂的系统分解为多个模块以方便编码。 会讲述以下内...
    zhongmeizhi阅读 4,481评论 1 5
  • 前言 第3季度,公司团队需要提升整体技术能力,于是我研究m站fis项目模块化方案,写下这篇文章来浅读modJS源码...
    Rubin666阅读 651评论 0 1
  • 我也来说说js的模块化秉承哲学中认识问题的逻辑思维过程,我从“是什么”,“为什么”,“怎么样(怎么用)”总结了一下...
    小蜗牛22阅读 275评论 0 0