在最先的時候JavaScript
這門語言實際上是並無模塊這一律念,可是隨着時間的推移與技術的發展將一些複用性較強的代碼封裝成模塊變成了必要的趨勢。html
在這篇文章中主要介紹原生的 JavaScript
封裝的幾種手段以及新增的 ES6 Module
的語法,來實現模塊封裝。node
而且會簡單的使用Webpack
讓Es6
代碼向後兼容。webpack
如下有兩個Js
文件,若是不採起任何封裝手段直接導入會致使window
環境污染。es6
而且,若是文件中有相同名字的變量或函數會發生命名衝突,由於它們都是放在全局做用域window
對象中的。web
<script src="./js_m1.js"></script> <script src="./js_m2.js"></script> <script> "use strict"; // 這是因爲js_m2後引入,因此js_m1的同名變量以及函數都被覆蓋掉了。 console.log(module_name); // js_m2 show(); // js_m2.show </script>
var module_name = "js_m1"; function show(){ console.log("js_m1.show"); }
var module_name = "js_m2"; function show(){ console.log("js_m2.show"); }
針對上述問題,採起函數的閉包及做用域特性咱們爲每一個模塊封裝一個做用域。npm
第一步:進行自執行函數包裹代碼封裝出局部做用域json
第二步:向外部暴露接口,爲
window
對象添加新的對象promise
<script src="./js_m1.js"></script> <script src="./js_m2.js"></script> <script> "use strict"; console.log(js_m1.module_name); // js_m1 js_m1.show(); // js_m1.show console.log(js_m2.module_name); // js_m2 js_m2.show(); // js_m2.show </script>
(function () { var module_name = "js_m1"; function show() { console.log("js_m1.show"); } window.js_m1 = { module_name: module_name, show: show }; // 在es6中,可簡寫爲 { module_name , show } }())
(function () { var module_name = "js_m2"; function show() { console.log("js_m2.show"); } window.js_m2 = { module_name: module_name, show: show }; // 在es6中,可簡寫爲 { module_name , show } }())
在Es6
以前,因爲沒有出現塊級做用域的概念,那時候你們都使用上面的方式進行封裝。瀏覽器
在當Es6
的塊級做用域出現以後,又誕生出了新的封裝方式即塊級做用域封裝。閉包
和
IIFE
封裝相同,都是利用做用域的特性進行封裝。注意一點,塊級做用域只對
let
或const
聲明有效。
<script src="./js_m1.js"></script> <script src="./js_m2.js"></script> <script> "use strict"; console.log(js_m1.module_name); // js_m1 js_m1.show(); // js_m1.show console.log(js_m2.module_name); // js_m2 js_m2.show(); // js_m2.show </script>
{ let module_name = "js_m1"; let show = function () { console.log("js_m1.show"); } window.js_m1 = { module_name, show }; }
{ let module_name = "js_m2"; let show = function () { console.log("js_m2.show"); } window.js_m2 = { module_name, show }; }
上面的兩種方式雖然都能達到模塊封裝的效果,可是咱們依然有更好的選擇。
下面將介紹極力推薦的Es6 module
語法進行導入。
學習Es6 module
從如下三個方面來入手:
1.模塊標籤及其特性
2.導出
3.導入
要想使用Es6 module
語法導入模塊,必須使用模塊標籤來引入Js
文件。
模塊標籤與普通的<script>
標籤具備一些不太同樣的地方,下面會從各個方面逐一進行介紹。
將<script>
標籤添加上type="module"
的屬性。
<script type="module"></script>
在瀏覽器中引用模塊必須添加路徑如./
,但在打包工具如webpack
中則不須要,由於他們有本身的存放方式。
總而言之,即便是在當前目錄也要添加上./
,不能夠進行省略。
這也是推薦的一種引入文件方式,不論是何種語言中都推薦引入文件時不進行路徑省略。
正確的導入路徑
<script type="module" src="./js_m1.js"></script> <script type="module" src="./js_m2.js"></script>
錯誤的導入路徑
<script type="module" src="js_m1.js"></script> // 不可省略!省略就會拋出異常
<script type="module" src="js_m2.js"></script>
所謂延遲解析是指在模塊標籤中的代碼會提到HTML
代碼以及嵌入式的<script>
標籤後才進行執行。
注意看下面的示例,編碼時模塊標籤在普通的<script>
之上,可是結果卻相反。
<script type="module"> console.log("<script type='module'> code run..."); </script> <script> "use strict"; console.log("<script> code run..."); </script>
模塊標籤中的全部代碼都是按嚴格模式運行的,請注意變量名的聲明以及this
指向問題,同時還有解構賦值等等。
<script type="module"> username = "雲崖"; // 拋出異常,未聲明 </script>
<script type="module"> let obj = { show() { console.log(this); // {show: ƒ} (function () { console.log(this); }()) // undefined 嚴格模式下爲undefined ,普通模式下爲window對象 } }; obj.show(); </script>
每一個模塊標籤中的代碼都會爲其建立一個專屬的做用域,禁止相互之間進行訪問。
而普通的<script>
標籤中的代碼所有在全局做用域下執行。
<script> let m1 = "m1..."; </script> <script> console.log(m1); // m1... </script>
<script type="module"> let m1 = "m1..."; </script> <script type="module"> console.log(m1); // Uncaught ReferenceError: m1 is not defined </script>
模塊在導入時只執行一次解析,以後的導入不會再執行模塊代碼,而使用第一次解析結果,並共享數據。
能夠在首次導入時完成一些初始化工做
若是模塊內有後臺請求,也只執行一次便可
<script type="module" src="./js_m3.js"></script> <script type="module" src="./js_m3.js"></script> <script type="module" src="./js_m3.js"></script> <!-- 導入屢次,也只執行一次代碼 --> <!-- 打印結果以下:import m3... --> <!-- js_m3內容以下: console.log("import m3..."); -->
ES6
使用基於文件的模塊,即一個文件一個模塊。
可使用export
來將模塊中的接口進行導出,導出方式分爲如下幾種:
1.單個導出
2.默認導出
3.多個導出
4.混合導出
5.別名導出
另外,ES6
的導出是是以引用方式導出,不管是標量仍是對象,即模塊內部變量發生變化將影響已經導入的變量。
下面將使用export
來將模塊中的接口進行單個單個的導出。
export let module_name = "js_m3.js"; export function test(){ console.log("測試功能"); } export class User{ constructor(username){ this.username = username; } show(){ console.log(this.username); } }
一個模塊中,只能默認導出一個接口。
若是默認導出的是一個類,那麼該類就能夠不用起類名,此外函數同理。
export let module_name = "js_m3.js"; export function test(){ console.log("測試功能"); } export default class{ // 默認導出 constructor(username){ this.username = username; } show(){ console.log(this.username); } }
可使用exprot
與{}
的形式進行接口的批量多個導出。
let module_name = "js_m3.js"; function test() { console.log("測試功能"); } class User { constructor(username) { this.username = username; } show() { console.log(this.username); } } export { module_name, test, User };
使用export default
導出默認接口,使用 export {}
批量導入普通接口
let module_name = "js_m3.js"; function test() { console.log("測試功能"); } export default class { constructor(username) { this.username = username; } show() { console.log(this.username); } } export { module_name, test };
同時也可使用as
來爲一個導出的接口取別名,若是該接口別名爲default
則將該接口當作默認導出。
let module_name = "js_m3.js"; function test() { console.log("測試功能"); } class User { constructor(username) { this.username = username; } show() { console.log(this.username); } } export { module_name, test, User as default };
使用as
來爲導出的export {}
中的導出接口起一個別名,當導入時也應該使用導出接口的別名進行接收。
當一個接口的別名爲default
時,該接口將當作默認導出。
let module_name = "js_m3.js"; function test() { console.log("測試功能"); } class User { constructor(username) { this.username = username; } show() { console.log(this.username); } } export { module_name as m_name, test as m_tst, User as default };
使用import
與from
進行靜態的模塊的導入,注意導入時必須將導入語句放在頂層。
模塊的導入分爲如下幾部分:
1.具名導入
2.批量導入
3.默認導入
4.混合導入
5.別名導入
6.動態導入
具名導入應該注意與導出的接口名一致。
下面是模塊導出的代碼:
let module_name = "js_m3.js"; function test() { console.log("測試功能"); } class User { constructor(username) { this.username = username; } show() { console.log(this.username); } } export { module_name, test, User };
使用具名導入:
<script type="module"> import { module_name, test, User} from "./js_m3.js"; console.log(module_name); // js_m3.js test(); // 測試功能 let u1 = new User("雲崖"); u1.show(); // 雲崖 </script>
若是導入的內容過多,可以使用*
進行批量導入,注意批量導入後應該使用as
來取一個別名方便調用。
下面是模塊導出的代碼:
let module_name = "js_m3.js"; function test() { console.log("測試功能"); } class User { constructor(username) { this.username = username; } show() { console.log(this.username); } } export { module_name, test, User };
使用批量導入:
<script type="module"> import * as m3 from "./js_m3.js"; // 別名爲m3,下面使用都要以m3開頭 console.log(m3.module_name); // js_m3.js m3.test(); // 測試功能 let u1 = new m3.User("雲崖"); u1.show(); // 雲崖 </script>
使用默認導入時不須要用{}
進行接收,而且可使用任意名字來接收默認導出的接口。
下面是模塊導出的代碼:
let module_name = "js_m3.js"; function test() { console.log("測試功能"); } class User { constructor(username) { this.username = username; } show() { console.log(this.username); } } export { module_name, test, User as default };
使用默認導入,咱們只導入默認導出的接口,能夠隨便取一個名字。
<script type="module"> import m3U from "./js_m3.js"; let u1 = new m3U("雲崖"); u1.show(); // 雲崖 </script>
當一個模塊中導出的又有默認導出的接口,又有其餘的導出接口時,咱們可使用混合導入。
使用{}
來接收其餘的導出接口,對於默認導出的接口而言只須要取一個名字便可。
下面是模塊導出的代碼:
let module_name = "js_m3.js"; function test() { console.log("測試功能"); } class User { constructor(username) { this.username = username; } show() { console.log(this.username); } } export { module_name, test, User as default };
使用混合導入:
<script type="module"> import m3U, { module_name, test } from "./js_m3.js"; console.log(module_name); // js_m3.js test(); // 測試功能 let u1 = new m3U("雲崖"); u1.show(); // 雲崖 </script>
爲了防止多個模塊下接口名相同,咱們可使用as
別名導入,再使用時也應該按照別名進行使用。
下面是m1
模塊導出的代碼:
let module_name = "js_m1"; let show = function () { console.log("js_m1.show"); } export { module_name, show };
下面是m2
模塊導出的代碼:
let module_name = "js_m2"; let show = function () { console.log("js_m2.show"); } export { module_name, show };
下面是使用別名導入這兩個模塊的接口並進行使用:
<script type="module"> import { module_name as m1_name, show as m1_show } from "./js_m1.js"; import { module_name as m2_name, show as m2_show } from "./js_m2.js"; console.log(m1_name); // js_m1 console.log(m2_name); // js_m2 m1_show(); // js_m1.show m2_show(); // js_m2.show </script>
使用import
與from
的導入方式屬於靜態導入,必須將導入語句放在最頂層,若是不是則拋出異常。
這是模塊中的導出接口:
export function test() { console.log("測試功能"); }
若是咱們想在某種特定條件下才導入並調用改接口,使用import
與from
的方式會拋出異常。
<script type="module"> if (true) { import { test } from "./js_m3.js"; // Error test(); // 想在特定條件下執行模塊中的測試功能 } </script>
這個時候就須要用到動態導入,使用 import()
函數能夠動態導入,實現按需加載,它返回一個 promise
對象。
<script type="module"> if (true) { let m3 = import("./js_m3.js"); m3.then((module)=> module.test()); // 測試功能 } </script>
咱們可使用解構語法來將模塊中的接口一個一個所有拿出來。
<script type="module"> if (true) { let m3 = import("./js_m3.js"); m3.then(({ test, }) => test()); // 拿出test接口 } </script>
若是有多個模塊都須要被使用,咱們能夠先定義一個Js
文件將這些須要用到的模塊中的接口作一個合併,而後再將該文件導出便可。
合併導出請將export
與from
結合使用。
// js_m1 export default class{ // 默認導出 static register(){ console.log("註冊功能"); } }
// js_m2 export class Login{ static login(){ console.log("登陸功能"); } } export function test(){ console.log("js_m2測試功能"); }
// index.js // 合併導出 import js_m1 from "./js_m1.js"; // js_m1中有接口是默認導出,所以咱們須要不一樣的導出方式 , 注意這裏就導出了一個接口,即js_m1的註冊類 export {default as js_m1_register} from "./js_m1.js"; // 導出js_m2中的接口,共導出兩個接口。登陸類和測試函數。 export * as js_m2 from "./js_m2.js";
導入與使用:
<script type="module"> import * as index from "./index.js"; index.js_m1_register.register(); // 註冊功能 index.js_m2.Login.login(); // 登陸功能 index.js_m2.test(); // js_m2測試功能 </script>
表達式 | 說明 |
---|---|
export function show(){} | 導出函數 |
export const name="Yunya" | 導出變量 |
export class User{} | 導出類 |
export default show | 默認導出 |
const name = "Yunya" export {name} |
導出已經存在變量 |
export {name as m1_name} | 別名導出 |
import m1_default from './m1_js.js' | 導入默認導出 |
import {name,show} from '/m1_js.js' | 導入命名導出 |
Import {name as m1_name,show} from 'm1_js.js' | 別名導入 |
Import * as m1 from '/m1_js.js' | 導入所有接口 |
因爲module
語法是Es6
推出的,因此對老舊的瀏覽器兼容不太友好,這個時候就須要用到打包工具進行打包處理使其能讓老舊的瀏覽器上進行兼容。
首先登陸 https://nodejs.org/en/
官網下載安裝Node.js
,咱們將使用其餘的npm
命令,npm
用來安裝第三方類庫。
在命令行輸入 node -v
顯示版本信息表示安裝成功。
cd
到你的項目路徑,並使用如下命令生成配置文件 package.json
npm init -y
修改package.json
添加打包命令
... "main": "index.js", "scripts": { "dev": "webpack --mode development --watch" // 添加這一句 }, ...
安裝webpack
工具包,若是安裝慢可使用淘寶
npm i webpack webpack-cli --save-dev
index.html
--dist #壓縮打包後的文件
--src
----index.js #合併入口
----style.js //模塊
index.html內容以下
<!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> <script src="dist/main.js"></script> </body> </html>
index.js內容以下
import style from "./style"; new style().init();
style.js
export default class User { constructor() {} init() { document.body.style.backgroundColor = "green"; } }
運行如下命令將生成打包文件到 dist
目錄,由於在命令中添加了 --watch
參數,因此源文件編輯後自動生成打包文件。
npm run dev