-
CommonJs
-
AMD
-
CMD
-
AMD(RequireJS):提前执行(异步加载:依赖先执行)+ 延迟执行;即依赖关系前置,在定义模块的时候就要声明它所依赖的模块。
-
CMD(SeaJS):延迟执行(运行到需加载,根据顺序执行);即按需加载,依赖就近,在需要某个模块的时候,再去require。
define(function(require, exports, module) {
// The module code goes here
});
-
require()用来引入外部模块;
-
exports对象用于导出当前模块的方法或变量,唯一的导出口;
-
module对象就代表模块本身。
define(function(require, exports, module) {
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
});
increment.js
define(function(require, exports, module) {
var add = require('math').add;
exports.increment = function(val) {
return add(val, 1);
};
});
index.js
define(function(require, exports, module) {
var inc = require('increment').increment;
inc(1); // 2
});
Browserify 是目前最常用的 CommonJS 格式转换的工具。
-
多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
-
js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长
require([module], callback);
代码示例:
require([‘moduleA’, ’moduleB’, ’moduleC'], function(moduleA, moduleB, moduleC) {
//do something
});
require([‘jquery’, ‘underscore’, ‘backbone’], function($, _, backbone){
//do something
});
require()异步加载moduleA,moduleB和moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。
define()函数
define(id?, dependencies?, factory);
参数说明:
-
id:指定义中模块的名字,可选;如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
-
依赖dependencies:是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。依赖参数是可选的,如果忽略此参数,它应该默认为["require", "exports", "module"]。然而,如果工厂方法的长度属性小于3,加载器会选择 以函数的长度属性指定的参数个数 调用工厂方法。
-
工厂方法factory,模块初始化要执行的函数或对象。 如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值 。
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});
require.config是用来定义别名的,在paths属性下配置别名。然后通过requirejs(参数一,参数二);参数一是数组,传入我们需要引用的模块名,第二个参数是个回调函数,回调函数传入一个变量,代替刚才所引入的模块。
//别名配置
requirejs.config({
paths: {
jquery: 'jquery.min' //可以省略.js
},
shim: {//适用于非AMD规范编写的库
‘underscore’:{
exports:’_'
},
‘backbone’:{
deps:[‘underscore’,’jquery'],
exports:’backbone'
},
‘jquery.scroll’:{
deps:[‘jquery’],
exports:’jQuery.fn.scroll'
}
}
});
//引入模块,用变量$表示jquery模块
requirejs(['jquery'], function ($) {
$('body').css('background-color','red');
});
引入模块也可以只写require()。requirejs通过define()定义模块,定义的参数上同。在此模块内的方法和变量外部是无法访问的,只有通过return返回才行。
define('math',['jquery'], function ($) {//引入jQuery模块
return {
add: function(x,y){
return x + y;
}
};
});
将该模块命名为math.js保存。
require(['jquery','math'], function ($,math) {
console.log(math.add(10,100));//110
});
main.js引入模块方法。
define(function(require, exports, module) {
// 模块代码
});
require是可以把其他模块导入进来的一个参数;
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
})
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})
seaJS使用示例:
// 定义模块 myModule.js
define(function(require, exports, module) {
var $ = require('jquery.js')
$('div').addClass('active');
exports.data = 1;
});
// 加载模块
seajs.use(['myModule.js'], function(my){
var star= my.data;
console.log(star); //1
});
(function(root, factory) {
if(typeof define === 'function' && define.amd) {
//AMD
define(['jquery'], factory);
} else if(typeof module === 'object' && typeof module.exports === ‘object’) {
//commonJS, node etc.
module.exports = factory(require('jquery'));
} else {
//Browser globals. Root is window
root.UmdModule = factory(root.jQuery);
}
}(this, function(jquery) {
// 现在你可以利用jquery做你想做的事了
// methods
function myFunc(){};
// exposed public method
return myFunc;
}));
加载jquery和underscore依赖:
(Function(this, factory){
if(typeof define === ‘function’ && define.amd){
//AMD
define([‘jquery’,’underscore'], factory);
}else if(typeof module === ‘object’ && typeof module.exports === ‘object'){
//commonJS node
module.exports = factory(require(‘jquery’), require(‘underscore’));
}else{
//browser globals(root is window)
root.returnExports = factory(root.jQuery, root._);
}
}(this, function($, _){
function a(){};//private
function b(){};//public
function c(){};//public
return {
b:b,
c:c
}
}));
地图数据初始化之前加载echarts依赖:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS, node
factory(exports, require('echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
}(this, function (exports, echarts) {
// do something
}));
这种模块定义方法,可以看做是IIFE的变体。不同的是它倒置了代码的运行顺序,需要你将所需运行的函数作为第二个参数传入。由于这种通用模块的适用性强,很多JS框架和类库都会打包成这种形式的代码。
// demo-export.js 模块定义
var name = "scq000"
var sayHello = (name) => { console.log("Hi," + name); }
export {name, sayHello};
// demo-import.js 使用模块
import {sayHello} from "./demo-export"; sayHello("scq000");
-
ES6使用的是基于文件的模块。所以必须一个文件一个模块,不能将多个模块合并到单个文件中去。
-
ES6模块API是静态的,一旦导入模块后,无法再在程序运行过程中增添方法。
-
ES6模块采用引用绑定(可以理解为指针)。这点和CommonJS中的值绑定不同,如果你的模块在运行过程中修改了导出的变量值,就会反映到使用模块的代码中去。所以,不推荐在模块中修改导出值,导出的变量应该是静态的。
-
ES6模块采用的是单例模式,每次对同一个模块的导入其实都指向同一个实例。
module.exports = {
//... output: { library: 'librayName', libraryTarget: 'umd', // 配置输出格式 filename: 'bundle.js' }
};
另外,ES6模块好处很多,但是并不支持按需加载的功能, 而按需加载又是Web性能优化中重要的一个环节。好在我们可以借助Webpack来弥补这一缺陷。Webpack v1版本提供了require.ensureAPI, 而2.x之后使用了import()函数来实现异步加载。具体的代码示例可以查看 前端性能优化之加载技术 这篇文章。
模块化方案
|
加载
|
同步/异步
|
浏览器
|
服务端
|
模块定义
|
模块引入
|
IFEE
|
取决于代码
|
取决于代码
|
支持
|
支持
|
IFEE
|
命名空间
|
AMD
|
提前预加载
|
异步
|
支持
|
构建工具r.js
|
define
|
require
|
CMD
|
按需加载
|
延迟执行
|
支持
|
构建工具spm
|
define
|
define
|
Common
|
值拷贝,
运行时加载
|
同步
|
原生不支持,需要使用browserify提前打包编译
|
原生支持
|
module.exports
|
require
|
UMD
|
取决于代码
|
取决于代码
|
支持
|
支持
|
IFEE
|
命名空间
|
ES Modules
(ES6)
|
实时绑定,
动态绑定,
编译时输出
|
同步
|
需用babel转译
|
需用babel转译
|
export
|
import
|