CommonJS

传送门

同步加载,适合服务器开发,node实现了commonJS。module.exports和require

判断commonJS环境的方式是(参考jquery源码):

if ( typeof module === "object" && typeof module.exports === "object" ) 

一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。

// a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕'); // b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕'); // main.js
var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done); // 运行结果
在 b.js 之中,a.done = false
b.js 执行完毕
在 a.js 之中,b.done = true
a.js 执行完毕
在 main.js 之中, a.done=true, b.done=true

对于commonJs模块的运行理解:

  模块中的代码运行在一个函数中,所有根上的变量都存在于当前作用域中,然后返回一个导出对象,导出对象中的函数引用的都是当前作用域内的变量。

UMD

UMD是AMD和CommonJS的糅合

AMD模块以浏览器第一的原则发展,异步加载模块。
CommonJS模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。
这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。

UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。
在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

ES6模块

  模块内默认开启严格模式,可用于代替AMD和commonJs,也就是说不再须要UMD了。ES6中对于AMD异步加载使用的是import函数,而commonJs同步加载对应ES6中import关键字

  export和import要求必须在顶层作用域中,import在静态编译阶段执行,不能使用运行时的值。但import()函数可以,他异步加载返回一个promise,能运行在任意地方,可以实现按需下载

和commonJs的差异

  commonJs输出的是一个对象;而ES6模块输出的不是对象,而是一个只读地址引用的集合。前者在生成对象返回的时候,会对对象中要用到的值进行拷贝进对象中,这很常规容易理解,而后者输出的是地址,所以在运行时会动态地到模块的作用域中读取值,没有“拷贝”这种说法,如果对es6导出的值进行赋值,报错:

Uncaught TypeError: Assignment to constant variable.

但可以调用导出函数,函数内对模块内的值进行修改。

  因为es6模块默认是严格模式,所以顶层this为undefined,而commonJs中顶层this指向当前模块

不同地方多次导入同一个模块,模块只会执行一次:

// x.js
import {c} from './mod';
c.add(); // 0 -> 1 // y.js
import {c} from './mod';
c.show(); // // main.js
import './x';
import './y'; //运行main 输出 1

以上不同模块中都导入了一次mod模块,而获取到的导出对象是相同的,说明mod模块只运行了一次。

循环引用

   当模块被执行的时候,会首先执行被提升了的语句(functnion、export),在执行import,最后再执行内部的赋值语句、逻辑等。

  出现循环引用时,只要被引入模块已经运行完还没运行完,就不会再次运行。a->b->a,b中的a就不会再次运行。

// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar);
export let foo = 'foo'; // b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo);
export let bar = 'bar'; // 运行a
b.mjs
ReferenceError: foo is not defined

把a中foo的赋值改为函数定义,则不会报错,因为函数定义有提升。

babel的转换原理

export { NormalExport }
export { RenameExport as HasRenamed }
export default DefaultExport // 转换为
exports.NormalExport = NormalExport;
exports.HasRenamed = RenameExport;
exports.default = DefaultExport; //------------------------------------------- import { NormalExport } from 'normal' // 转换为
var _normal = require('normal');

可见,import关键字是同步的。

Chrome 61 加入了对 JavaScript Module <script type="module"> 的原生支持,后续介绍以下这个特性的用法:

参考:http://es6.ruanyifeng.com/#docs/module-loader#加载规则

在script中加入type=module,意味着这个脚本支持ES6模块的语法。

<script type="module">
import {x} from './es6module.js';
alert(x);
</script> //./es6module.js
export var x = "123";

以上能正常运行。如果去掉type,则报错:Uncaught SyntaxError: Unexpected token import

加了这个属性,两段脚本就运行在不同的环境中了,以下运行报错 ReferenceError: x is not defined

    <script type="module">
var x = 123
</script>
<script>
alert(x);
</script>

总结ES6模块和commonJs的对比:

  1. 导出的机制不同,前者是只读的地址,而后者是一个对象
  2. 顶层this的指向不同
  3. 循环引用时,都是遇到被加载的模块正在运行或者已经运行完,就不再进去运行,模块没运行完输出的都是已运行部分。
  4. es6静态编译,import关键字不允许使用动态值,而commonJs的require是动态执行,可以读取动态值

AMD

可实现异步的模块加载。适合浏览器,RequireJS库实现了AMD规范  传送门

关键词:define(factory)、require(['xxx'],fn)

补充:单文件多模块

2.js

define('a',function(){
return {
v:"a"
}
}); define('b',function(){
return {
v:"b"
}
});

main.js

require.config({
paths: {
'a': "2",
'b': "2",
}
}); require(['a','b'], function(a,b) {
alert(a.v);
alert(b.v);
});

可见,paths中的名字不是随便写的,必须和模块文件内定义的名字要一致

测试2:多次引入同一个模块

2.js

define('a',function(){
return {
v:"a"
}
});
console.log('module a exec')

main.js

require.config({
paths: {
'a': "2",
}
}); require(['a'], function(a) {
a.x = 789;
require(['a' + ""], function(a) {
console.log(a.x);
});
console.log(123)
});

输出

多次引入只会执行一次模块,而且使用的都是同一个模块对象,虽然第二次引入的模块在第一次就下载好了,但回调的代码依然会在下一轮事件循环中执行

模块的名字可以是表达式,如以上的['a'] 可以写成['a'+""],而且仅当require被执行的时候,模块才会开始下载,下载完再执行模块,然后触发回调函数。

实际做的事情:

  1. require函数检查依赖的模块,根据配置文件,获取js文件的实际路径
  2. 根据js文件实际路径,在dom中插入script节点,并绑定onload事件来获取该模块加载完成的通知。
  3. 依赖script全部加载完成后,调用回调函数

CMD

传送门

seaJS实现了这个规范。分析阶段(使用正则表达式进行依赖的抓取),会把所有require的文件都下载好了(这一步可能会导致程序刚开始响应比较慢),再执行入口模块

所以我们使用的时候写 var a = require('a') 虽然是引入一个模块,但可以认为这句代码没有任何阻塞,因为模块已经提前下载好了

测试代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event Propagation</title>
<script src="https://cdn.bootcss.com/seajs/3.0.2/sea.js"></script>
<script>
seajs.use("./1.js")
</script>
</head>
<body>
</body>
</html> // 1.js
define(function(require){
console.log("main module exec")
setTimeout(function(){
var exec = Math.random()*100%2;
if(exec > 1){
let two = require("./2.js");
let two1 = require("./2.js");
two.show();
}
},1000)
}); // 2.js
define(function(require,exports,module){
console.log("module two exec")
module.exports = {
show :function(){
console.log("show in module two exec")
}
}
})

运行结果:

结论:

  seajs在分析阶段会下载所有的依赖而不管这些依赖是否会被真正执行,依赖的名字可以使用变量,依赖都下载好了才执行入口模块,依赖在第一次被require的时候才执行

实际做的事情是:

  1. 通过回调函数的Function.toString函数,使用正则表达式来捕捉内部的require字段,找到require('jquery')内部依赖的模块jquery
  2. 根据配置文件,找到jquery的js文件的实际路径
  3. 在dom中插入script标签,载入模块指定的js,绑定加载完成的事件,使得加载完成后将js文件绑定到require模块指定的id(这里就是jquery这个字符串)上
  4. 回调函数内部依赖的js全部加载(暂不调用)完后,调用回调函数
  5. 当回调函数调用require('jquery'),即执行绑定在'jquery'这个id上的js文件,即刻执行,并将返回值传给var b

可见以上依赖搜索这一块用正则表达式搜索,导致模块名只能硬编码,而不能运算和使用变量

rj和sj的区别

  https://github.com/seajs/seajs/issues/277

  https://www.zhihu.com/question/20351507

  https://www.zhihu.com/question/20342350

  都是异步下载模块。

  rj会按需下载,下载完马上执行,不能保证多个依赖是按顺序执行的,次序不可控,玉伯认为这是一个坑,但是amd规定中从来没规定过模块的执行次序,异步模块之间可以没有先后,但被依赖的异步模块必须先于当前模块执行

  sj会先进行分析,然后下载好所有依赖,再开始执行入口模块(这可能会导致程序响应比较慢);在模块第一次被require的时候,模块才会执行,所以可以保证模块的执行次序和require的次序是一致的,但这可能导致一个问题,模块被require的时候执行,万一内部出错了,当前模块该怎么办?被依赖的模块有问题,当前模块执行有何意义?

  对于cmd,一require就可以马上使用,给人的感觉就像是同步代码,而amd逻辑是写在回调函数中的,给人异步的感觉

JS模块之AMD, CMD, CommonJS、UMD和ES6模块的更多相关文章

  1. js模块系统 - amd|cmd|commonjs|esm|umd

    写过前端代码大概率听说过amd cmd umd commonjs esm这些名词, 想当初我第一次看到这些的时候, 人都麻了, 都是些啥啊. 后来我知道了, 这些都是js的模块规范. amd - 浏览 ...

  2. js模块化开发 AMD CMD Commonjs

    在es6全面实行开来之前  js实现模块开发方案有: 1.AMD 异步模块开发定义  依赖前置,requireJs应用了这一规范 require([module], callback); 加载完后回调 ...

  3. FW: AMD, CMD, CommonJS和UMD

    javascript 我是豆腐不是渣 4月5日发布 推荐 2 推荐 收藏 32 收藏,486 浏览 今天由于项目中引入的echarts的文件太大,requirejs经常加载超时,不得不分开来加载ech ...

  4. AMD, CMD, CommonJS和UMD

    我的Github(https://github.com/tonyzheng1990/tonyzheng1990.github.io/issues),欢迎star 今天由于项目中引入的echarts的文 ...

  5. javascript模块化之CommonJS、AMD、CMD、UMD、ES6

    javascript模块化之CommonJS.AMD.CMD.UMD.ES6 一.总结 一句话总结: CommonJS是同步加载模块,用在服务端:AMD是异步加载模块,用于浏览器端 1.为什么服务器端 ...

  6. AMD/CMD/CommonJs到底是什么?它们有什么区别?

    知识点1:AMD/CMD/CommonJs是JS模块化开发的标准,目前对应的实现是RequireJs/SeaJs/nodeJs.   知识点2:CommonJs主要针对服务端,AMD/CMD主要针对浏 ...

  7. 兼容多种模块规范(AMD,CMD,Node)的代码

    在JavaScript模块化开发中,为了让同一个模块可以运行在前后端,以及兼容多种模块规范(AMD,CMD,Node),类库开发者需要将类库代码包装在一个闭包内. AMD规范 AMD,即“异步模块定义 ...

  8. AMD,CMD.CommonJs和UMD还有es6的模块化对比

    CommonJS CommonJS是服务器端模块的规范,Node.js采用了这个规范. 根据CommonJS规范,一个单独的文件就是一个模块.加载模块使用require方法,该方法读取一个文件并执行, ...

  9. AMD,CMD,CommonJs,UMD讲解

    一.CommonJS CommonJS规范加载模块是同步的,只有加载完成,才能执行后面的操作 CommonJS规范中的module.exports和require 每个文件就是一个模块,有自己的作用域 ...

随机推荐

  1. Integer Cache(带你脱坑)

    Integer Cache 废话不多说----->直接上代码: public class IntegerDemo { public static void main(String[] args) ...

  2. hdu1068 Girls and Boys 基础匈牙利

    #include <cstdio> #include <cstring> #include <algorithm> #include <cstdlib> ...

  3. Centos 7.x 配置Gitlab

    GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的web服务. 1. 安装并配置必要的依赖关系 如果你想使用 Postfix 发送邮件,请在安装过程中根 ...

  4. the little schemer 笔记(2)

    第二章 Do it, Do it Again, and Again, and Again... 假设l是 (Jack Sprat could eat no chicken fat) 那么 (lat? ...

  5. Markdown 简单使用教程

    标题: # 一级标题 ## 二级标题 增加星号,字号相应变小,共有6级 列表: - 无序列表 编号.  有序列表 插入链接: [显示文本](链接地址) 插入图片: ![](图片地址) 引用: > ...

  6. EOJ Monthly

    ###2018.10 A.oxx 的小姐姐们 oxx 和他的小姐姐(们)躺在图书馆前的大草坪上看星星. 有强迫症的 oxx 想要使得他的小姐姐们正好躺成一块 n×m 的长方形. 已知小姐姐的形状是 1 ...

  7. pyinstaller 打包.exe文件记录遇到的问题

    用pyinstaller打包py2.7的程序有时会出现不匹配的错误,在python的idle下运行没有问题,打包之后却会报一些错误,所以打包的话还是尽量用py3.5版本,而且用 -F 将程序打包成一个 ...

  8. [已读]响应式web设计实践

    薄的一本,彩印,书质量和内容都不错. 响应设计三要素:媒体查询.流动布局.自适应图片.

  9. json数组某个数值对应渲染

    当你统计某一年的某个值它对应的月份总数时,后台没有直接处理好,某个月对应某个值,这样会增加统计的负担,但当数据时这样的时候,在angularjs中时不能直接引用的. "data": ...

  10. 关于IE 对 $.get 缓存的记录

    最近在IE9中碰到一个问题是, 当我对某个角色进行修改的时候,再点击查询还是修改之前的内容,但是实际数据库已经修改成功,纠结了好一会儿之后,才发现是 $.get请求的问题. 因为  IE对get请求, ...