目录
1.模块化
2.模块化的核心
3.ESM
3.1独立模块作用域
3.2导出模块内部数据
3.3导入外部模块数据
3.3.1静态导入
3.3.2ESM导入导出——示例:
3.3.3动态导入import()
4.模块化的向下兼容
5.CommonJS
5.1独立模块作用域
5.2导出模块内部数据
5.3导入外部模块数据
5.4CommonJS规范使用示例
6.AMD
7.AMD——requireJS
7.1独立模块作用域
7.2导出模块内部数据
7.3导入外部模块数据
7.4AMD——requireJS使用示例
8.requireJS 的CommonJS风格
8.1导出模块内部数据
8.2导入外部模块数据
8.3requireJS的CommonJS风格示例
9.UMD
模块化已经是现代前端开发中不可或缺的一部分了。也是后端必备。
把复杂的问题分解成相对独立的模块,这样的设计可以降低程序复杂性,提高代码的重用,也有利于团队协作开发与后期的维护和扩展。
从 ECMAScript2015
开始引入了模块的概念,我们称为:ECMAScript Module
,简称:ESM。
从 ECMAScript2015/ECMAScript6
开始,JavaScript
原生引入了模块概念,而且现在主流浏览器也都有了很好的支持。
一个文件就是模块,拥有独立的作用域,且导出的模块都自动处于 严格模式
下,即:'use strict'
。
如果该文件是通过模块化进行加载的,那么:
'use strict'
的。严格模式('use strict'
):
使用 export
语句导出模块内部数据。
// 导出单个特性
export let name1, name2, …, nameN;
export let name1 = …, name2 = …, …, nameN;
export function FunctionName(){...}
export class ClassName {...}
// 导出列表
export { name1, name2, …, nameN };
// 重命名导出
export { variable1 as name1, variable2 as name2, …, nameN };
// 默认导出
export default expression;
export default function (…) { … }
export default function name1(…) { … }
export { name1 as default, … };
// 模块重定向导出
export * from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;
导入分为两种模式
在浏览器中,import
语句只能在声明了 type="module"
的 script 的标签中使用。
且import语句必须写在JS文件最上面;
import defaultExport from "module-name";
import * as name from "module-name";
import { export } from "module-name";
import { export as alias } from "module-name";
import { export1 , export2 } from "module-name";
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
静态导入方式不支持延迟加载,
import
必须这模块的最开始
document.onclick = function () {
// import 必须放置在当前模块最开始加载
// import m1 from './m1.js'
// console.log(m1);
}
项目路径:
index.html:注意ESM中使用模块化时,script标签中必须要有type="module"属性。
Document
main.js:
注意点:
//导入m1模块
//1.导出单个特性:因为不是默认导出,所以需要声明变量接收,且文件必须有.js结尾
import {cssFunc1,cssFunc2,cssFunc3,M1Class} from './m1.js';
// 2.导入列表::此处a,b,c必须和导出中的变量名一一对应
import {a,b,c} from './m1.js';
//3.重命名导出:导入导出的变量名需一一对象,想在导入时使用不同名字可使用别名, 变量名 as 别名
// import {name,pw} from './m1.js'
import {name as name1,pw as pw1} from './m1.js';
//4.默认导入:注意:default导入导出都不需要加{}
// import aa from './m1.js';
// import defaultFunc from "./m1.js"
// import defaultFunc2 from './m1.js';
import bb from './m1.js';
// 5.模块重定向导出
import * as obj from './m1.js'
import {v1,v2} from './m1.js';
import {value1,value2} from './m1.js';
import {default as defaultV} from './m1.js';
// 1.导出单个特性
let a1 = 10;
let m1Class = new M1Class();
m1Class.m1ClassFunc();
console.log("main.js",a1,cssFunc1,cssFunc2,cssFunc3);
//2.导入列表
console.log(a,b,c);//1 2 3
//3.重命名导出
// console.log(name,pw);//张三 1234
console.log(name1,pw1);//张三 1234
//4.默认导入
// defaultFunc();
// defaultFunc2();
// console.log(aa);
// console.log(bb);
//5.模块重定向导出
console.log(obj);//Module {…}
console.log(v1,v2);//2 3
console.log(value1,value2);//2 3
console.log(defaultV);//ƒ m2Func(){ console.log(v1+v2); }
m1.js:
console.log("m1模块...");
function css1(){
console.log("m1模块下的css1方法");
}
//1.导出单个特性
export let cssFunc1 = css1;
export let cssFunc2 = function css2(){
console.log("m1模块下的css2方法");
}
export function cssFunc3(){
console.log("m1模块下的cssFunc3方法");
}
export class M1Class{
constructor(){
}
m1ClassFunc(){
console.log("m1模块下的m1ClassFunc");
}
}
//2.导出列表
let a = 1,b=2,c=3;
export {a,b,c};
//3.重命名导出
let username = "张三";
let password = "1234";
export {username as name,password as pw};
//4.默认导出
let aa = 1;
// export default aa;
// export default function() {
// let defaultVal = 33;
// console.log("defaultVal:"+defaultVal);
// }
// export default function defaultFunc2() {
// console.log("defaultFunc2方法");
// }
let bb = 2,cc = 3;
//不能同时导出多个。如export { bb as default,cc as default};
// export { bb as default};
// 5.模块重定向导出: from-从已经存在的模块、脚本文件…导出
export * from './m2.js';
export {v1,v2} from './m2.js';
export { v1 as value1, v2 as value2 } from './m2.js';
export { default } from './m2.js';
m2.js:
let v1=2,v2=3;
export {v1,v2};
export default function m2Func(){
console.log(v1+v2);
}
结果:
import()
,它不需要依赖 type="module"
的 script 标签。import
可以像调用函数一样来动态的导入模块。以这种方式调用,将返回一个 promise
。import('./m.js')
.then(m => {
//...
});
// 也支持 await
let m = await import('./m.js');
通过
import()
方法导入返回的数据会被包装在一个对象中,即使是default
也是如此
示例:通过import()导出的是一个Promise对象。
不在页面初始化加载时就加载m3.js文件,而是当点击时加载。
m3.js:
let obj = {
a:1,
b:2
}
export default obj;
main.js:如果通过import语句导入
//6.动态导入:通过import()方法导入,返回一个promise对象进行异步延迟加载
document.onclick = function(){
//直接通过import导入会报错
// import obj from 'm3.js';
// console.log(obj);
import('./m3.js').then(obj=>{
console.log(obj);
});
}
结果:
使用async await 异步延迟加载是,要使用default()方法,必须导出时导出的是函数
m3.js:
function css(){
console.log("css");
}
export default css;
main.js:
//使用async await进行异步延迟加载
document.onclick = async function(){
let m1 = await import('./m3.js');
console.log(m1);
m1.default();
}
结果:
无论是那种模块化规范,重点关注:
在早起前端对于模块化并没有什么规范,反而是偏向服务端的应用有更强烈的需求,CommonJS 规范就是一套偏向服务端的模块化规范,NodeJS 就采用了这个规范。
NodeJS和前端JS是同宗同源,NodeJS使用V8解析器,ECMAscript最为底层语言,NodeJS基于此延伸出了操作浏览器之外的如操作文件系统,网络,硬盘方法。
CommonJS 是后端模块化规范,通过操作文件系统进行实现,但是前端不能操作文件系统,所以前端使用不了CommonJS 规范。
一个文件就是模块,拥有独立的作用域。
CommonJS是使用同步加载方法夹杂模块化文件。只有模块化加载成功后才会继续往下执行。
通过 module.exports
或 exports
对象导出模块内部数据。
注意: module.exports
或 exports
两种方式不能同时使用。
// a.js
let a = 1;
let b = 2;
module.exports = {
x: a,
y: b
}
// or
exports.x = a;
exports.y = b;
通过 require
函数导入外部模块数据
// b.js
let a = require('./a');
a.x;
a.y;
通过nodeJS环境,使用nodemon main.js启动main.js。
m1.js:
let a = 1, b =2;
// module.exports = {
// x:a,
// y:b
// }
exports.l = a;
exports.m = b;
main.js:
let obj = require('./m1');
// console.log(obj);//{ x: 1, y: 2 }
console.log(obj);//{ l: 1, m: 2 }
因为 CommonJS 规范一些特性(基于文件系统,同步加载),它并不适用于浏览器端,所以另外定义了适用于浏览器端的规范
AMD(Asynchronous Module Definition)。AMD没有办法获取本地文件,因此使用异步文件加载方式实现模块化加载。
https://github.com/amdjs/amdjs-api/wiki/AMD
浏览器并没有具体实现该规范的代码,我们可以通过一些第三方库来解决。如requireJS。
官网:https://requirejs.org/
必须在页面通过data-main指定入口文件;
// 1.html
通过一个 define
方法来定义一个模块,并通过该方法的第二个回调函数参数来产生独立作用域
// scripts/Cart.js
define(function() {
// 模块内部代码
})
两种方式导出模块:
通过 return
导出模块内部数据:可以导出方法,对象等东西;
// scripts/Cart.js
define(function() {
return class Cart {
add(item) {
console.log(`添加商品:${item}`)
}
}
})
通过前置依赖列表导入外部模块数据
// scripts/main.js
// 定义一个模块,并导入 ./Cart 模块
define(['./Cart'], function(Cart) {
let cart = new Cart()
cart.add({name: 'iphoneXX', price: 1000000})
})
define
方法来定义一个模块,并将模块代码进行隔离;return
导出模块内部数据;方式二:CommonJS风格导出;index.html:
必须使用data-main指定入口文件