模块化
模块就是在单个文件中声明的JavaScript代码。我们可以用JS代码直接从其他文件中导入函数、变量和类。
在NodeJS之前,由于没有过于复杂的开发场景,前端是不存在模块化的,后端才有模块化。
NodeJS诞生之后,它使用CommonJS的模块化规范。从此,js模块化开始快速发展
CommonJS
多用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。
// name.js
module.exports.name = 'abc'
module.exports.sayName = function() {console.log('name')}
或者
// name.js
var name = 'abc';
function sayName() {console.log('name')}
module.exports = {
name: name,
sayName: sayName
}
引用:[普通模块name.js]
var nameObj = require('./name.js')
console.log(nameObj.name)
nameObj.sayName(); // name
如果引用系统模块[会去node_modules里找]
var http = require('http');
http.createService(...).listen(3000);
特点:用同步的方式加载模块。
缺点:在服务端,模块文件都存放在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
AMD
AMD是一种异步模块化标准。
RequireJS是AMD最流行的实现。(CommonJS与requireJS无关,只是用了require语句)
场景:项目中我们会将JS组件放到不同的文件里,并通过script标签引入。当组件间存在依赖关系的时候,被依赖的组件需要放到前面。否则的话会出现XXX is undefined或者XXXX is not a function之类的错误。比如一个jquery的插件显然是依赖jquery核心库的,所以jquery核心库文件必须先引入。当组件间依赖复杂时,使用RequireJs可以从一个根开始检查依赖,根据这些依赖关系自动的帮助我们插入script标签。
原理:所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
简单使用:
引入requireJS。指定工程JS模块入口。
// index.html
<script src="lib/require.js" data-main="js/scripts/main.js"></script>
情景一: 入口文件main.js里用RequireJs映射Jquery并使用
require.config(
{
// baseUrl——用于加载模块的根路径
baseUrl:'./js',
// paths——用于映射不存在根路径下面的模块路径
paths: {
'Jquery': lib/jquery'
}
}
);
require(['Jquery'],function ($) {
// jquery操作
$(document).on('click','#contentBtn',function(){
$('#messagebox').html('You have access Jquery by using require()');
});
});
情景二:app.js需要依赖tools.js模块
// tool.js
define(function(){
return{
decs : 'abcd',
};
})
// app.js
require(['./tool'],function(data){
console.log(data.desc)
})
define函数接收[moduleName][requireModeule][callback]三个参数。
完整写法诸如:
define('./app',['./tool'],function(data){
.....
})
第一个参数是定义模块名,第二个参数是传入定义模块所需要的依赖,第三个函数则是定义模块的主函数,主函数和require的回调函数一样,同样是在依赖加载完以后再调用执行。
不建议传入第一个参数,即自定义模块名,因为如果哪一天我将这个文件转移到其他目录下,那我就得在这这里再修改一次模块名。官方其实也不推荐,用官方的说法是:让优化工具去自动生成这些模块名吧!
情景三:将require.config配置在入口文件,以供全局使用
// main.js
define(function(){
require.config({
baseUrl:'./js',
paths: {
'Jquery': lib/jquery'
}
});
});
// app.js
require(['./main.js'],function(){
require(['Jquery'],function ($) {
$(document).on('click','#btn1',function(){
$('#messagebox').html('anc');
require(['./tool'],function(data){
console.log(data.desc)
});
});
});
});
require.config额外配置:
shims——虽然目前已经有一部分流行的函数库(比如 jQuery)符合 AMD 规范,但还有很多库并不符合。shim 就是为了加载这些非 AMD 规范的 js,并解决其载入顺序的。
require.config({
baseUrl: './js',
paths: {
'Jquery': lib/jquery'
}
shim: {
'backbone': {
deps: ['underscore', 'Jquery'],
exports: 'Backbone'
},
'underscore': {
exports: '_'
}
},
});
我们想通过 RequireJS 来使用 backbone,那么你就需要在配置中把它定义为一个 shim。同时通过 deps 配置其依赖关系,可以保证 underscore、jquery 先被加载。
CMD
CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。CMD最流行的实现模式便是sea.js
AMD模式:
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
// 等于在最前面声明并初始化了要用到的所有模块
a.do()
if (false) {
// 即便没用到某个模块 b,但 b 还是提前加载了。**这就CMD要优化的地方**
b.do()
}
});
CMD模式:
define(function(require, exports, module) {
var a = require('./a'); //在需要时申明
a.doSomething();
if (false) {
var b = require('./b');
b.doSomething();
}
});
完整写法:
// index.html
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
seajs.use('./js/main.js')
</script>
// main.js
define(function(require,exports,module){
let module1 = require('./module1.js')
console.log(module1.getName()) // module1
let module4 = require('./module4.js') //module2 module4 module3
module4.getVal()
})
// modoule1
define(function(require,exports,module){
let name = 'module1';
function getName(){
return name;
}
//暴露模块
module.exports = {getName}
})
// module4
define(function(require,exports,module){
let val = 'module4'
function getVal(){
console.log(val)
}
// 引入module2 同步
let module2 = require('./module2.js');
// 执行module2
module2()
// 异步引入module3
require.async('./module3.js',function(module3){
//执行module3
module3.module3.getData()
});
// 暴露模块
module.exports = {getVal}
}
// module2.js
define(function(require,exports,module){
let msg = 'module2';
function getMsg(){
console.log(msg)
}
//暴露模块
module.exports = getMsg;
})
// module3.js
define(function(require,exports,module){
let data = 'module3'
function getData(){
console.log(data)
}
// 暴露模块
exports.module3 = {getData}
})
因为在module4.js中引入了module3.js是异步的,所以先打印出module4,再打印出module3.
UMD
UMD是AMD和CommonJS的一个糅合。AMD是浏览器优先,异步加载;CommonJS是服务器优先,同步加载。
既然要通用,怎么办呢?那就先判断是否支持node.js的模块,存在就使用node.js;再判断是否支持AMD(define是否存在),存在则使用AMD的方式加载。这就是所谓的UMD。
((root, factory) => {
if (typeof define === 'function' && define.amd) {
//AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
//CommonJS
var $ = requie('jquery');
module.exports = factory($);
} else {
//都不是,浏览器全局定义
root.testModule = factory(root.jQuery);
}
})(this, ($) => {
//do something... 这里是真正的函数体
});
ES6Module
ES2015在JavaScript标准中引入官方模块功能。
// a.js
const numberFn = r=> r * r;
const nmber = 5;
export {numberFn,number}
或者
// a.js
export const numberFn = r=> r * r;
export const nmber = 5;
使用:
import {number as count, numberFn} from './a.js'
console.log(numberFn(count))
或者将整个模块当做一个变量来导入
import * as nn from './a.js'
console.log(nn.numberFn(nn.number))
假设模块中只有一个成员被导出。可以使用export default关键字
// b.js
export default class Book{
constructor(title) {
this.title = title
}
printTitle() {
console.log(this.title)
}
}
import Book from './b.js'
const aBook = new Book('aaa')
aBook.printTitle(); // aaa
导入export default导出模块。不需要将类名包含在花括号中(import {book} from './b.js' X)。只有模块有多个成员被导出才用花括号。