目录
1.模块化
模块化已经是现代前端开发中不可或缺的一部分了。也是后端必备。
把复杂的问题分解成相对独立的模块,这样的设计可以降低程序复杂性,提高代码的重用,也有利于团队协作开发与后期的维护和扩展。
从 ECMAScript2015
开始引入了模块的概念,我们称为:ECMAScript Module
,简称:ESM。
2.模块化的核心
- 独立的作用域——将代码进行有效的隔离,各模块之间代码不会相互影响
- 如何导出模块内部数据——访问模块化的数据
- 如果导入外部模块数据
3.ESM
从 ECMAScript2015/ECMAScript6
开始,JavaScript
原生引入了模块概念,而且现在主流浏览器也都有了很好的支持。
3.1独立模块作用域
一个文件就是模块,拥有独立的作用域,且导出的模块都自动处于 严格模式
下,即:'use strict'
。
如果该文件是通过模块化进行加载的,那么:
- 该文件会产生一个独立的作用域;
- 该文件内的代码默认是运行在严格模式下即:
'use strict'
的。
严格模式('use strict'
):
- 变量必须先声明才能使用;
- 没有变量提升(预解析机制)。
3.2导出模块内部数据
使用 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 …;
3.3导入外部模块数据
导入分为两种模式
- 静态导入
- 动态导入
3.3.1静态导入
在浏览器中,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);
}
3.3.2ESM导入导出——示例:
项目路径:
index.html:注意ESM中使用模块化时,script标签中必须要有type="module"属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<!-- 在浏览器中,import 语句只能在声明了 type="module" 的 script 的标签中使用。 -->
<script type="module" src="./js/main.js"></script>
</body>
</html>
main.js:
注意点:
- 文件路径必须有'.js'结尾;
- 导入列表时,所有的变量名必须和导出时的变量名一一对应,不想一一对象可使用别名;
- default导入导出时都不能加{};
- 模块化重定向导出(即从另一个模块或脚本文件导出)时,from后表示从已经存在的模块、脚本文件…导出
//导入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);
}
结果:
3.3.3动态导入import()
- 此外,还有一个类似函数的动态
import()
,它不需要依赖type="module"
的 script 标签。 - 关键字
import
可以像调用函数一样来动态的导入模块。以这种方式调用,将返回一个promise
。 - 使用async await 异步延迟加载是,要使用default()方法,必须导出时导出的是函数
- 方式一:使用promise对象的then方法
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进行延迟加载
使用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();
}
结果:
4.模块化的向下兼容
- CommonJS
- AMD
- UMD
- ESM
无论是那种模块化规范,重点关注:
- 独立模块作用域
- 导出模块内部数据
- 导入外部模块数据
5.CommonJS
在早起前端对于模块化并没有什么规范,反而是偏向服务端的应用有更强烈的需求,CommonJS 规范就是一套偏向服务端的模块化规范,NodeJS 就采用了这个规范。
NodeJS和前端JS是同宗同源,NodeJS使用V8解析器,ECMAscript最为底层语言,NodeJS基于此延伸出了操作浏览器之外的如操作文件系统,网络,硬盘方法。
CommonJS 是后端模块化规范,通过操作文件系统进行实现,但是前端不能操作文件系统,所以前端使用不了CommonJS 规范。
5.1独立模块作用域
一个文件就是模块,拥有独立的作用域。
CommonJS是使用同步加载方法夹杂模块化文件。只有模块化加载成功后才会继续往下执行。
5.2导出模块内部数据
通过 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;
5.3导入外部模块数据
通过 require
函数导入外部模块数据
// b.js
let a = require('./a');
a.x;
a.y;
5.4CommonJS规范使用示例
通过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 }
6.AMD
因为 CommonJS 规范一些特性(基于文件系统,同步加载),它并不适用于浏览器端,所以另外定义了适用于浏览器端的规范
AMD(Asynchronous Module Definition)。AMD没有办法获取本地文件,因此使用异步文件加载方式实现模块化加载。
https://github.com/amdjs/amdjs-api/wiki/AMD
浏览器并没有具体实现该规范的代码,我们可以通过一些第三方库来解决。如requireJS。
7.AMD——requireJS
必须在页面通过data-main指定入口文件;
// 1.html
<script data-main="scripts/main" src="https://cdn.bootcss.com/require.js/2.3.6/require.min.js"></script>
7.1独立模块作用域
通过一个 define
方法来定义一个模块,并通过该方法的第二个回调函数参数来产生独立作用域
// scripts/Cart.js
define(function() {
// 模块内部代码
})
7.2导出模块内部数据
两种方式导出模块:
- return方式;
- 使用CommonJS风格进行导出
通过 return
导出模块内部数据:可以导出方法,对象等东西;
// scripts/Cart.js
define(function() {
return class Cart {
add(item) {
console.log(`添加商品:${item}`)
}
}
})
7.3导入外部模块数据
通过前置依赖列表导入外部模块数据
// scripts/main.js
// 定义一个模块,并导入 ./Cart 模块
define(['./Cart'], function(Cart) {
let cart = new Cart()
cart.add({name: 'iphoneXX', price: 1000000})
})
7.4AMD——requireJS使用示例
- 必须在页面通过data-main指定入口文件;
- 当使用data-main指定入口文件后,会动态创建一个script标签,然后将文件通过ajax的方式(网络请求方法)去加载需要加载的JS文件(main.js),因为页面必须运行在服务器环境下;
- 通过
define
方法来定义一个模块,并将模块代码进行隔离; - 导出模块:方式一:再通过
return
导出模块内部数据;方式二:CommonJS风格导出;
index.html:
必须使用data-main指定入口文件
<body>
<script data-main="js/main.js" src="js/require.js"></script>
</body>
</html>
require.js:
由于官网加载很慢,将以下地址再网页上执行后,将网页上JS复制下来,生成require.js文件,放入指定目录
https://cdn.bootcss.com/require.js/2.3.6/require.min.js
main.js:
导入文件时,文件必须使用中括号[ ] 进行应用
//导入模块化文件时,通过方法的参数获取到模块中导出的数据
//注意导入时,文件名需要使用中括号
define(['./m1'],function(Cart){
let cart = new Cart()
return cart.add({name: 'iphoneXX', price: 1000000})
}
);
m1.js:
通过return方式导出模块化数据
define(function () {
return class Cart {
add(item) {
console.log("m1模块");
console.log(`添加商品:${item}`, item)
}
};
});
结果:
8.requireJS 的CommonJS风格
require.js
也支持 CommonJS
风格的语法
8.1导出模块内部数据
// scripts/Cart.js
define(['require', 'exports', 'module'], function(require, exports, module) {
class Cart {
add(item) {
console.log(`添加商品:${item}`)
}
}
exports.Cart = Cart;
})
// 忽略不需要的依赖导入
define(['exports'], function(exports) {
class Cart {
add(item) {
console.log(`添加商品:${item}`)
}
}
exports.Cart = Cart;
})
// 如果是依赖的导入为:require, exports, module,也可以省略依赖导入声明
define(function(require, exports, module) {
class Cart {
add(item) {
console.log(`添加商品:${item}`)
}
}
exports.Cart = Cart;
})
8.2导入外部模块数据
//CommonJS风格导入导出模块化数据
define(function(require) {
//注意如果导出的是类,则需要将对象中的类解构出来才能用
let {Cart} = require('./m2');
let cart = new Cart();
cart.add({name: 'iphoneXX', price: 1000000})
})
8.3requireJS的CommonJS风格示例
m2.js:
// define(['require', 'exports', 'module'], function(require, exports, module) {
// class Cart {
// add(item) {
// console.log("m2模块");
// console.log(`添加商品:${item}`, item)
// }
// }
// exports.Cart = Cart;
// })
// // 忽略不需要的依赖导入
// define(['exports'], function(exports) {
// class Cart {
// add(item) {
// console.log(`添加商品:${item}`,item)
// }
// }
// exports.Cart = Cart;
// })
// 如果是依赖的导入为:require, exports, module,也可以省略依赖导入声明
define(function(require, exports, module) {
class Cart {
add(item) {
console.log(`添加商品:${item}`,item)
}
}
exports.Cart = Cart;
})
main.js:
//CommonJS风格导入导出模块化数据
define(function(require) {
//注意如果导出的是类,则需要将对象中的类解构出来才能用
let {Cart} = require('./m2');
let cart = new Cart();
cart.add({name: 'iphoneXX', price: 1000000})
})
9.UMD
严格来说,UMD
并不属于一套模块规范,它主要用来处理 CommonJS
、AMD
、CMD
的差异兼容,是模块代码能在前面不同的模块环境下都能正常运行
- 判断如果module是object对象,且导出类型时obejct,就是在nodeJS 环境下导出;
- define为function且define.amd为真,则再AMD下使用;
- 判断完后,会执行函数自执行里的导出;
- 通过判断就能在浏览器下执行AMD模块化导出,而再后端nodeJS下就能使用CommonJS规范进行模块化导出
(function (root, factory) {
if (typeof module === "object" && typeof module.exports === "object") {
// Node, CommonJS-like
module.exports = factory(require('jquery'));
}
else if (typeof define === "function" && define.amd) {
// AMD 模块环境下
define(['jquery'], factory);
}
}(this, function ($) { // $ 要导入的外部依赖模块
$('div')
// ...
function b(){}
function c(){}
// 模块导出数据
return {
b: b,
c: c
}
}));