目錄
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
}
}));