CommonJS
CommonJS的流行得益于Node.js采用了这种规范 CommonJS规范允许我们:
- 使用
module.exports = {}或者exports.name = xxx导出模块 - 使用
const m1 = require('./m1')引入模块
1.暴露模块的方法:
- module.exports = {}
- exports.xxx = 'xxx'
(1)
module.exports = {
name: 'lindaidai',
sex: 'boy'
}
(2)
exports.name = 'lindaidai';
exports.sex = 'boy'
2.引入模块:全局方法require()
注意:⚠️这个全局方法是 node 中的方法哈,它不是 window 下面的,所以如果你没做任何处理想直接在 html 里用肯定就是不行的了。require() 它是 Node.js 中的一个全局方法,并不是CommonJS独有的,CommonJS只是众多规范中的其中一种
3.引入模块分类
- 核心模块(
Node.js自带的模块) - 路径模块(相对或绝对定位开始的模块)
- 自定义模块(
node_modules里的模块)
// 直接导入 `Node.js`自带
const path = require('path');
// 相对路径
const m1 = require('./m1.js');
// 直接导入 `node_modules`里的模块
const lodash = require('lodash');
4.CommonJS规范的特点
- 所有代码都运行在模块作用域,不会污染全局作用域;
- 模块是同步加载的,即只有加载完成,才能执行后面的操作;
- 模块在首次执行后就会缓存,再次加载只返回缓存结果,如果想要再次执行,可清除缓存;
- CommonJS输出是值的拷贝(即,
require返回的值是被输出的值的拷贝,模块内部的变化也不会影响这个值)。 - 代码可复用于Node.js环境下运行
- 通过NPM发布的很多第三方模块都采用了CommonJS规范 缺点: 代码无法直接运行在浏览器环境下,需要通过工具转换为标准的ES5
CommonJS 还可以细分为 CommonJS1 和 CommonJS2,区别在于 CommonJS1 只能通过 `exports.XX = XX` 的方式导出,CommonJS2 在 CommonJS1 的基础上加入了 `module.exports = XX` 的导出方式。
CommonJS 通常指 CommonJS2。
AMD
与CommonJS最大的不同是采用异步的方式加载依赖的模块。
优点:
- 可在不转换代码的情况下直接在浏览器中运行;
- 可异步加载依赖;
- 可并行加载多个依赖;
- 代码可运行在浏览器环境和 Node.js 环境下。 缺点: JavaScript 运行环境没有原生支持 AMD,需要先导入实现了 AMD 的库后才能正常使用(requirejs)
整体使用:
- define() 定义模块
- require() 导入模块
// **math.js**
define(function () {
var add = function (a, b) {
return a + b;
}
return {
add: add
}
})
// **test.js**
var requirejs = require("requirejs"); //引入requirejs模块
requirejs(['math'],function(math) {
console.log(math)
console.log(math.add(1, 2));
})
1.产生原因
因为CommonJS是同步加载的,上一个加载完才执行后面的内容,这样可能会造成卡顿,而AMD就是优化这种现象的异步加载(采用异步的方式加载模块)。
2.定义并暴露模块
前提:必须依赖其他库,目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js define(id?, dependencies?, factory)
define(function () {
var add = function (a, b) {
return a + b;
}
return {
add: add
}
})
3.引用模块
var requirejs = require("requirejs"); //引入requirejs模块
requirejs(['math'],function(math) {
console.log(math)
console.log(math.add(1, 2));
})
4.依赖其他模块的define
// m1.js
define(function () {
console.log('我是m1, 我被加载了...')
return {
name: 'lindaidai',
sex: 'boy'
}
})
// math.js
define(['m1'], function (m1) {
console.log('我是math, 我被加载了...')
var add = function (a, b) {
return a + b;
}
var print = function () {
console.log(m1.name)
}
return {
add: add,
print: print
}
})
// test.js
var requirejs = require("requirejs"); //引入requirejs模块
requirejs(['math'],function(math) {
console.log('我是test, 我被加载了...')
console.log(math.add(1, 2));
math.print();
})
function other () {
console.log('我是test模块内的, 但是我不依赖math')
};
other();
node test.js 结果:
LinDaiDaideMBP:commonJS lindaidai$ node test.js
我是test模块内的, 但是我不依赖math
我是m1, 我被加载了...
我是math, 我被加载了...
我是test, 我被加载了...
3
lindaidai
CMD
1.定义模块 和AMD定义模块一样:define(id?, dependencies?, factory) 区别在于:
- AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块
- CMD推崇就近依赖,只有在用到某个模块的时候再去require 例子:
AMD写法:
// math.js
define(['m1'], function (m1) {
console.log('我是math, 我被加载了...')
var add = function (a, b) {
return a + b;
}
var print = function () {
console.log(m1.name)
}
return {
add: add,
print: print
}
})
CMD写法(seajs推荐写法):
define(function (require, exports, module) {
console.log('我是math, 我被加载了...')
var m1 = require('m1');
var add = function (a, b) {
return a + b;
}
var print = function () {
console.log(m1.name)
}
module.exports = {
add: add,
print: print
}
})
执行结果区别:
AMD,会先加载m1,"我是m1"会先执行CMD,我是"我是math"会先执行,因为本题中console.log('我是math, 我被加载了...')是放在require('m1')前面的。
ES6 Modules
es6 modules 已成为了前端模块化的主流规范
1.export导出模块
两种导出方式:
- 命名式导出(名称导出)
- 默认导出(自定义导出)
命名式导出 示例:
// 以下两种为错误
// 1.
export 1;
// 2.
const a = 1;
export a;
// 以下为正确
// 1.
const a = 1;
export { a };
// 2. 接口名与模块内部变量之间,建立了一一对应的关系
export const a = 1, b = 2;
// 3. 接口名与模块内部变量之间,建立了一一对应的关系
export const a = 1;
export const b = 2;
// 或者用 as 来命名
const a = 1;
export { a as outA };
const a = 1;
const b = 2;
export { a as outA, b as outB };
默认导出:在export后面加一个export
// 1.
const a = 1;
export default a;
// 2.
const a = 1;
export default { a };
// 3.
export default function() {}; // 可以导出一个函数
export default class(){}; // 也可以出一个类
默认导出可以理解为另一种形式的命名导出,也就是把属性名默认重写成了default
const a = 1;
export defalut a;
// 等价于
export { a as default }
2.import导入模块
也存在两种模块导入方式:命名式导入(名称导入)和默认导入(定义式导入)。
// 某个模块的导出 moudule.js
export const a = 1;
// 模块导入
// 1. 这里的a得和被加载的模块输出的接口名对应
import { a } from './module'
// 2. 使用 as 换名
import { a as myA } from './module'
// 3. 若是只想要运行被加载的模块可以这样写,但是即使加载2次也只是运行一次
import './module'
// 4. 整体加载
import * as module from './module'
// 5. default接口和具名接口
import module, { a } from './module'
3.export...from...
export { someVariable } from './a'; 例子:a,b,c三个模块,c想通过b引用a
// b.js
方式一:
import { someVariable } from './a';
export { someVariable };
方式二:
export { someVariable } from './a';
4.ES6 Modules规范的特点
- 导出用export
- 导入用import
- 可以用export...from...来中转
- export命令和import命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,这是因为处于条件代码块中,就没法做静态优化了,违背了ES6模块的设计初衷。
- import命令具有提升效果,会提升到整个模块的头部,首先执行。
- 导入的模块变量是不可重新赋值的,只是个可读引用,不过却可以改写属性
缺点: 目前无法直接运行在大部分 JavaScript 运行环境下,必须通过工具转换成标准的 ES5 后才能正常运行