传统模块化
<!DOCTYPE html>
<html lang="en">
<body>
</body>
<!--
// module1.js
var a = [1,2,3,4,5].reverse();
// module2.js
var b = a.concat([6,7,8,9]);
// module3.js
var c =b.join('-')
//index.js
console.log(a,b,c)
-->
<!-- 顺序加载 ,并公用一个作用域-->
<script src="./module1.js"></script>
<script src="./module2.js"></script>
<script src="./module3.js"></script>
<script src="./index.js"></script>
</html>
模块化解决的问题
- 加载顺序
- 全局污染 -> 立即执行函数创建模块的独立作用域
立即执行函数
- ES5没有解决顺序加载的问题
// module1.js
var module1 = (function(){
// 模块独立作用域
var a = [1,2,3,4,5].reverse();
return {a}
})()
// module2.js
var module2 = (function(module1){
var b = module1.a.concat([6,7,8,9]);
return {b}
})(module1)
// module3.js
var module3 = (function(module2){
var c =module2.b.join('-')
return {c}
})(module2)
// 解决了变量全局污染和模块以依赖的问题
// index.js
;(function(m1,m2,m3){
console.log(m1.a,m2.b,m3.c)
})(module1,module2,module3)
// 也可以不写参数
// 但注入参数,方法内这个参数是局部变量,它的变化不会影响全局
// 符合纯函数的概念,否则就是全局变量了
;(function(){
console.log(module1.module2,m2.module3,m3.c)
})()
- 插件化
;(function(doc){
var Calculator = function(){
this.resultDom = document.getElementsByClassName('result')[0]
// ……
this.init()
}
Calculator.prototype.init = function(){
this.bindEvent()
}
Calculator.bindEvent = function(){
// 添加事件 加 减
}
Calculator.onBtnClick = function(event){
this.resultDom.innerHTML = this.calcuate('plus',2,4)
}
Calculator.calcuate = function(field,val1,val2){
switch(field){
case 'plus':
return val1 + val2;
case 'minus':
return val1 - val2
}
}
window.Calculator = Calculator;
})(document)
// 使用
new Calculator();
CommonJS规范->nodeJs中使用
- require 引入模块
- module.exports 导出模块
Node 应用由模块组成,采用 CommonJS 模块规范,
前端的webpack也是对CommonJS原生支持的。
每个文件就是一个模块,有自己的作用域。
在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。
// 定义
// es1.js
var name = "Tom";
var age = 16;
function say(name,age){
console.log(name+"is"+age+"years old")
}
// 语法1
module.exports.name = name;
module.exports.age = age;
module.exports.say = say;
// 语法2
exports.name = name;
exports.age = age;
exports.say = say;
// 语法3
module.exports = {
name,
age,
say
}
// 语法4
module.exports = {
name : "Tom",
age : 16,
say(name,age){
console.log(name+"is"+age+"years old")
}
}
// 引用
var module = require('./es1.js')
consolo.log(module.name) //Tom
consolo.log(module.age) //16
module.say(name,age) //Tom is 16 years old
- CommonJS每当require引入一个js文件,都会创建这个js文件的实例
- 缓存机制
- 浏览器不兼容CommonJS,它依赖于node 需要webpack打包
- require把引入的js文件变成一个立即执行函数
- exports 与module.exports 的区别:exports 是对 module.exports 的引用,不能直接给exports 赋值,直接赋值无 效,结果是一个空对象, module.exports 可以直接赋值
- 一个文件不能写多个module.exports ,如果写多个,对外暴露的接口是最后一个module.exports
- 模块如果没有指定使用module.exports 或者exports 对外暴露接口时,在其他文件就引用该模块,得到的是一个空对象{}
AMD
AMD 即 Asynchronous Module Definition,中文名是“异步模块定义”的意思。
- define(moduleName,[moudule],factory) 定义模块
- require([module],callback) 引入模块 前置依赖->所有模块加载完后才执行回调,避免了异步模块加载顺序的问题
在浏览器环境,要从服务器端加载模块,就必须采用非同步模式,因此浏览器端一般采用AMD规范。
AMD是一个在浏览器端模块化开发的规范,而AMD规范的实现,就是require.js。
特点:依赖必须提前声明好。
优点:异步加载,不阻塞页面的加载,能并行加载多个模块
缺点:不能按需加载,必须提前加载所需依赖
// 定义
// 独立模块 es1.js
define({
module1: function() {},
module2: function() {},
});
// 等价写法
define(function () {
return {
module1: function() {},
module2: function() {},
};
});
// 非独立模块 只有先加载这两个模块,新模块才能正常运行 es2.js
define(['es1', 'es2'], function(m1, m2) {
return {
method: function() {
m1.methodA();
m2.methodB();
}
};
});
// 使用
require.config({
paths:[
es1:'es1路径',
es2:'es2路径',
]
})
require(['es1', 'es2'], function ( es1, es2) {
es1.module1()
es1.module2()
es2.mothod()
});
CMD
阿里巴巴开发的 通用模块定义
CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。
CMD规范整合了CommonJS和AMD规范的特点。
在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。
- 与AMD的本质不同
- 支持动态引入依赖文件。
- 依赖就近 按需加载
https://www.zhangxinxu.com/sp/seajs/
// 定义
// 使用 exports 直接向外提供接口。
define(function(require, exports) {
// 对外提供 name属性
exports.name = 'Tom';
// 对外提供 say 方法
exports.say= function(name) {
console.log("hello"+name)
};
});
// 使用 return 直接向外提供接口。
define(function(require) {
return {
name : 'Tom',
say: function(name) {
console.log("hello"+name)
}
};
});
// 使用 module.exports 直接向外提供接口。
define(function(require, exports, module) {
module.exports = {
name: 'Tom',
say: function(name) {
console.log("hello"+name)
}
};
});
// 使用
define(function (require) {
var m1 = require('./module1')
console.log(m1.name) // Tom
m1.say(m1.name) // Hello Tom
})
// require.async 方法用来在模块内部异步加载模块,并在加载完成后执行指定回调。callback 参数可选。
define(function(require, exports, module) {
// 异步加载一个模块
require.async('./module1', function(a) {
a.doSomething();
});
// 异步加载多个模块,在加载完成时,执行回调
require.async(['./module2', './module3'], function(b, c) {
b.doSomething();
c.doSomething();
});
})
ES6模块化->import的静态和动态
-
import module from ‘模块路径’;导入模块
-
export module; 导出模块
-
ES6 模块export、import命令可以出现在模块的任何位置,但是必须处于模块顶层。如果处于块级作用域内,就会报错
-
静态导入
// 静态导入
/***
* utils文件
*/
export function plus(a,b){}
export function minus(a,b){}
// =======
function plus(a,b){}
function minus(a,b){}
// 第一种导出 她不是一个对象,而是一个模块类型
export {
plus,minus
}
// use
// 1.
import {plus,minus} from './utils';
// 2. 重命名
import {plus as add ,minus} from './utils';
// 3. 命名空间就是一个对象
import * as utils from './utils'
console.log(utils) //打印出的是模块
const {plus,minus} = utils;
// 第二种导出,将一个对象作为模块导出,此时不能使用结构
export default{
plus,minus
}
// use
import utils from './utils'
console.log(utils) //打印出的是对象
const {plus,minus} = utils;
//第三种导出 也可以直接导入 副作用的导入,导入即执行,通常用来导入样式文件
import './utils'
//第四种导出 模块导出和对象导出混用
export default{
a:1,
B:2
}
export function plus(a,b){}
export function minus(a,b){}
// export {
// plus,minus
// }
//1. 默认导出的必须第一个声明
import utils,{plus,minus} from './utils';
const {a,b} = utils;
const res1 = plus(a,b);
const res2 = minus(a,b);
// 2.
import utils,* as calc from './utils';
const {a,b} = utils;
const {plus,minus} = calc;
- 动态导入
// 动态导入 import()
// import关键字,不是调用方法
// 运行时加载,无法静态分析
// 1.
import('./utils').then(module=>{
const {a,b} = module.default;
const {plus,minus} = module;
})
// 2.
(async ()=>{
const module = await import('./utils');
// const {default:utils,plus,minus} = await('./utils');
// const {default:{a,b},plus,minus} = await('./utils');
console.log(module);
})()
对比
- CommonJS中export中是值的拷贝,ES6是引用
- CommonJS运行时加载模块,ES6是编译阶段加载