传送门javascript
同步加载,适合服务器开发,node实现了commonJS。module.exports和requirecss
判断commonJS环境的方式是(参考jquery源码):html
if ( typeof module === "object" && typeof module.exports === "object" )
一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。java
// 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模块的运行理解:node
模块中的代码运行在一个函数中,全部根上的变量都存在于当前做用域中,而后返回一个导出对象,导出对象中的函数引用的都是当前做用域内的变量。 jquery
UMD是AMD和CommonJS的糅合git
AMD模块以浏览器第一的原则发展,异步加载模块。
CommonJS模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。
这迫令人们又想出另外一个更通用的模式UMD (Universal Module Definition)。但愿解决跨平台的解决方案。es6
UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。
在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。github
模块内默认开启严格模式,可用于代替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(); //1 // 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的对比:
可实现异步的模块加载。适合浏览器,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被执行的时候,模块才会开始下载,下载完再执行模块,而后触发回调函数。
实际作的事情:
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的时候才执行
实际作的事情是:
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逻辑是写在回调函数中的,给人异步的感受