javascript模块化
模块方案
模块方案
├─tree.txt
├─浏览器端
| ├─CMD
| | ├─SeaJS
| ├─AMD
| | ├─RequireJS
├─服务器端
| ├─CommonJS
| | ├─Node
├─UMD
AMD与CMD的主要区别:
- 对于依赖的模块,AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行了(写法不同)
- CMD推崇依赖就近,AMD推崇依赖前置
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
var b = require('./b')
b.doSomething()
})
// AMD
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
b.doSomething()
})
AMD
AMD是RequreJS在推广过程中对模块定义的规范化产出
define() Function 定义模块
define(id?, dependencies?, factory);
id
第一个参数 id 是字符串文字。 它指定正在定义的模块的 id。 这个参数是可选的,如果它不存在,模块 id 应该默认为加载器为给定响应脚本请求的模块的 id。 当存在时,模块 id 必须是“顶级”或绝对 id(不允许使用相对 id)。
module id format
- 模块标识符是由正斜杠分隔的“术语”字符串。
- 术语必须是驼峰式标识符、“.”或“..”。
- 模块标识符可能没有像“.js”这样的文件扩展名。
- 模块标识符可以是“相对的”或“顶级的”。 如果第一项是“.”,则模块标识符是“相对的”。 或者 ”..”。
- 顶级标识符从概念模块名称空间根目录中解析出来。
- 相对标识符相对于写入和调用“require”的模块的标识符进行解析。
dependencies
第二个参数依赖项是模块 ID 的数组文字,这些模块 ID 是正在定义的模块所需的依赖项。 这个参数是可选的,依赖项必须在模块工厂函数执行之前解析,解析的值应该作为参数传递给工厂函数,参数位置对应于依赖项数组中的索引。
依赖项 id 可能是相对 id,并且应该相对于正在定义的模块进行解析。 换句话说,相对 id 是相对于模块的 id 解析的,而不是用于查找模块 id 的路径。
factory
factory:工厂方法,返回定义模块的输出值
总结一段话:声明模块的时候指定所有的依赖dependencies,并且还要当做形参传到factory中,对于依赖的模块提前执行,依赖前置)
Example
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});
define(["alpha"], function (alpha) {
return {
verb: function(){
return alpha.verb() + 2;
}
};
});
define({
add: function(x, y){
return x + y;
}
});
define(function (require, exports, module) {
var a = require('a'),
b = require('b');
exports.action = function () {};
});
require() Function 加载模块
require([dependencies],function(){});
- 第一个参数是一个数组,表示所依赖的模块
- 第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用.加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块
require()函数在加载依赖的函数的时候是异步加载的,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题
Example
require(['alpha'],function(alpha){
alpha.verb ();
})
CMD
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出
在 CMD 规范中,一个模块就是一个文件
define() Function定义模块
define(factory)
factory可以是一个函数,也可以是一个对象或字符串。
factory为对象、字符串时,表示模块的接口就是该对象、字符串。
define({'foo': 'bar'})
define('I am a template. My name is {{name}.}')
factory为函数时,表示模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory方法在执行时,默认会传入三个参数: require、exports和module:
define(function(require, exports, module) {
// 模块代码
})
define(id?, deps?, factory)
字符串id表示模块标识,数组deps是模块依赖。
define('hello', ['jquery'], function(require, exports, module) {
// 模块代码
})
define.cmd Object
一个空对象,可以用来判定当前页面是否有CMD模块加载器
if (typeof define === 'function' && define.cmd) {
// 有Sea.js等CMD模块加载器的存在
}
require() Function
require(id)
接受模块标识作为唯一参数,用来获取其他模块提供的接口。
define(function(require, exports, module) {
// 获取模块 a 的接口
var a = require('./a')
// 调用模块 a 的方法
a.doSomething();
})
require.async(id, callback?)
require.async方法用来在模块内部异步加载模块,并在加载完成后执行指定回调。callback参数可选。
define(function(require, exports, module) {
// 异步加载一个模块,在加载完成时,执行回调
require.async('./b', function(b) {
b.doSomething()
})
// 异步加载多个模块,在加载完成时,执行回调
require.async(['./c', './d'], function(c, d) {
c.doSomething()
d.doSomething()
})
})
exports Object
exports是一个对象,用来向外提供接口。
define(function(require, exports, module) {
// 对外提供foo属性
exports.foo = 'bar'
// 对外提供doSomething方法
exports.doSomething = function () {}
})
除了给exports对象添加成员,还可以使用return直接向外提供接口
define(function(require, exports, module) {
return {
foo: 'bar',
doSomething: function() {}
}
})
module Object
module是一个对象,上面存储了与当前模块相关联的一些属性和方法。
module.id string
模块的唯一标识
module.exports Object
当前模块对外提供的接口。
传给factory函数的exports参数是module.exports对象的一个引用。只通过exports参数来提供接口
,有时无法满足开发者的所有需求。比如模块的接口是某个类的实例时,需要通过module.exports来实现。
define(function(require, exports, module) {
// exports是module.exports的一个引用
console.log(module.exports === exports) // true
// 重新给module.exports赋值
module.exports = new SomeClass()
// exports不再等于module.exports
console.log(module.exports === exports) // false
})
CommonJS
CommonJs是服务器端模块的规范。
CommonJs定义的模块分为:模块引用(require)/模块定义(exports)/模块标识(module)
所有代码都运行在模块作用域,不会污染全局作用域。
模块可以多次加载,但是只会在第一次加载时运行过一次,然后运行结果就被缓存了,以后再加载, 就直接读取缓存结果。想要让模块再次运行,必须清除缓存。
模块的加载顺序,按照其在代码中出现的顺序。
// common.js
module.exports = function(a, b) {
return a - b
}
let minus = require('./common.js')
console.log(minus(5, 4))
ESM
导出
分命名导出和默认导出
命名导出
// 写法1
export const name = 'test'
export const add = function (a, b) {
return a + b
}
// 写法2
const name = 'test'
const add = function(a, b) {
return a + b
}
export {name, add}
在使用命名导出时,可以通过as关键字对变量重命名。如:
const name = 'test'
const add = function(a, b) {
return a + b
}
export {name, add as getSum}
默认导出
// calc.js
export default {
name: 'test',
add: function(a, b) {
return a + b
}
}
导入
针对命名导出模块的导入
// calc.js
const name = 'test'
const add = function(a, b) {
return a + b
}
export {name, add}
// 一般导入方式
import {name, add} from './calc.js'
add(2, 3)
// 通过as关键字对导入的变量重命名
import {name, add as calcSum} from './calc.js'
calcSum(2, 3)
// 使用 import * as <myModule>可以把所有导入的变量作为属性值添加到<myModule>对象中,从而减少了对当前作用域的影响
import * as calcObj from './calc.js'
calcObj.add(2, 3)
针对默认导出模块的导入
import后面直接跟变量名,并且这个名字可以自由指定,它指代了calc.js中的默认导出值。
import calc from './calc.js'
命名导出模块和默认导出模块混合导入
import React, {Component} from 'react'
注意:默认导出模块的导入必须在命名导出模块的导入之后
复合写法
在工程中,有时候需要把一个模块导入后立即导出,比如专门用来集合所有页面或组件的入口文件。采用复合写法:
export {name, add} from './calc.js'
复合写法只支持通过命名导出方式暴露出来的变量,默认导出则没有复合写法,只能将导入和导出拆开。
import calc from './calc.js'
export default calc
UMD
UMD并不能说是一种模块标准,不如说它是一组模块形式的集合更准确。UMD全称Universal Module Difinition(通用模块标准)。 也是随着大前端的趋势所诞生,它可以通过运行时或者编译时让同一个代码模块在使用 CommonJs、CMD 甚至是 AMD 的项目中运行。 未来同一个 JavaScript 包运行在浏览器端、服务区端甚至是 APP 端都只需要遵守同一个写法就行了, 它的目标是使一个模块能运行在各种环境下。UMD是AMD和CommonJS的糅合。
它没有自己专有的规范,是集结了 CommonJs、CMD、AMD 的规范于一身,我们看看它的具体实现:
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS之类的
module.exports = factory(require('jquery'));
} else {
// 浏览器全局变量(root 即 window)
root.returnExports = factory(root.jQuery);
}
}(this, function($) {
// 方法
function myFunc() {};
// 暴露公共方法
return myFunc;
}));
不难发现,它在定义模块的时候回检测当前使用环境和模块的定义方式, 将各种模块化定义方式转化为同样一种写法。它的出现也是前端技术发展的产物, 前端在实现跨平台的道路上不断的前进,UMD 规范将浏览器端、服务器端甚至是 APP 端都大统一了, 当然它或许不是未来最好的模块化方式,未来在 ES6+、TypeScript、Dart 这些拥有高级语法的语言回代替这些方案。