在ES6之前,由于ECMAScript不具備模塊化管理的能力,因此往往需要借助第三方類庫(例如遵守AMD規范的RequireJS或遵循CMD規范的SeaJS等)才能實現模塊加載。而自從ES6引入了模塊化標準后,就不需要再特地加載一次外部腳本了。模塊化的語法不僅讓JavaScript代碼的組織變得更有條理,還包含封裝、按需導出或導入等實用功能,可輕松應對日益復雜和龐大的前端工程。但有一點要注意,模塊中的代碼默認運行在嚴格模式中。
## 一、導出
  一個模塊就是一個獨立的JavaScript文件,如果要讀取文件內的變量、函數或類(ES6新增的概念),那么必須先將它們用export關鍵字導出,因為它們默認都是私有的。導出的方式有多種,下面會依依列舉。
**1)第一種**
  將export關鍵字放在變量、函數等聲明之前,常被稱為命名導出(named export),如下所示。注意,命名導出的變量或函數都需要有名稱,否則會拋出語法錯誤。
~~~
export let name = "strick";
export function getName() {
return "strick";
}
export class people {
getName() {
return "strick";
}
}
~~~
  以上述代碼中的name變量為例,本質上,export導出的是變量本身的引用,也就是為該變量在兩個模塊之間建立一種關聯(即綁定)。如果變量在模塊內部實時更新了,那么導出的變量的值也會隨之改變。
**2)第二種**
  第二種也叫命名導出,只是形式不同,聲明和導出會分成兩步,要導出的標識符會用花括號包裹起來,如下代碼所示。此時,還能通過as關鍵字為導出的變量、函數等設置別名。
~~~
let age = 28;
function getAge() {
return 28;
}
export { age, getAge };
export { age as myAge, getAge as getMyAge }; //設置別名
~~~
**3)第三種**
  第三種用于導出模塊的全部或部分成員(例如變量、函數或類等),此時需要包含四部分,分別是導出標識符、模塊路徑(也叫模塊說明符,module specifier)以及兩個關鍵字:export和from。如果要導出全部,那么導出標識符得用星號(\*)表示;而如果只要導出部分,那么導出標識符可以像第二種命名導出那么寫,具體如下代碼所示。注意,模塊路徑不能簡寫,需要以“/”、“./”或“../”開頭,千萬不要因為文件在同級就省略相應的字符。
~~~
export * from "./1.js"; //導出全部
export { name, age } from "./1.js"; //導出部分
export { getAge as getMyAge } from "./1.js"; //導出部分并設置別名
~~~
  上面代碼的第二條導出語句,其實可以分解成下面兩條語句,第三條也有類似的分解。
~~~
import { name, age } from "./1.js";
export { name, age };
~~~
## 二、導入
  如果想導入某個模塊的成員,可以使用import關鍵字。它的語法與前面第三種導出方式類似,也包含四個部分,分別是導入標識符、模塊路徑以及兩個關鍵字:import和from,其中模塊路徑也不能簡寫,如下代碼所示。
~~~
import * as people from "./1.js"; //導入全部
import { name, age } from "./1.js"; //導入部分
import { getAge as getMyAge } from "./1.js"; //導入部分并設置別名
~~~
  注意上面的第一行代碼,使用了命名空間導入(Namespace Import)。與導出模塊的全部成員不同,在導入時,除了要與星號組合之外,還必須為其設置別名。這是由于加載的整個模塊會被當成一個對象,而此對象需要一個名稱,它的屬性就是該模塊所有的導出。另外兩行使用了命名導入(Named Import),花括號內的導入標識符要與模塊的導出標識符一一對應。
  import語句在內部實現了單例模式,盡管上面代碼對同一個模塊執行了三次導入,但該模塊只會被實例化一次。
**1)只讀變量**
  用import導入的變量都是只讀的,相當于為它添加了const限制,如果在模塊中為其重新賦值,那么必會引起類型錯誤。想要更新導入的變量的值,有一種間接的實現辦法,如下代碼所示,先在要導出的1.js模塊內定義name變量和setName()函數。
~~~
export let name = "strick";
export function setName(str) {
name = str;
}
~~~
  然后在另一個模塊中導入剛剛的兩個成員,接著將新的name值通過setName()函數傳入到1.js模塊內部進行更新,如下代碼所示,name變量最終輸出的結果正是那個新值。
~~~
import { name, setName } from "./1.js";
console.log(name); //"strick"
setName("freedom");
console.log(name); //"freedom"
~~~
**2)成員提升**
  從模塊中導入的成員默認都會被提升至當前模塊作用域的頂部,類似于聲明提升。因此,像下面這樣的寫法都是正確的。
~~~
console.log(age);
getAge();
import { age, getAge } from "./1.js";
~~~
**3)簡潔導入**
  import語句中的導入標識符和from關鍵字都是可選的,但要注意,只有當兩者一起省略時,語句才能被正確執行,如下所示。
~~~
import "./jquery.js";
~~~
  由于import加載的模塊都會被執行一次,因此可以用上面這種簡潔導入來實現腳本的預加載。而這些腳本既可以是自己封裝的代碼段,也可以是jQuery、Zepto等第三方類庫。
## 三、模塊的默認值
  ES6中的default關鍵字可指定模塊的默認值(例如變量、函數或類等),即為模塊指定默認的導出和導入。
**1)默認導出**
  一個模塊只能存在一個默認導出,下面會列出默認導出的四種寫法,為了便于比較,將它們放在了一起。
~~~
let name = "strick";
export default name; //寫法一
export default function getName() { //寫法二
return "strick";
}
export default function() { //寫法三
return "strick";
}
export { name as default }; //寫法四
~~~
  export語句中的default其實就是要導出的模塊成員,它的名稱就叫default,而default后面能夠跟一個表達式、命名函數或匿名函數,注意觀察上面代碼的前三種寫法。第四種寫法比較特殊,是在命名導出時,將標識符重命名成default。
  默認導出可以簡單的理解為給default賦值,因此下面的前兩條語句都能被正確執行,而第三條語句由于包含了聲明變量的關鍵字(let),所以會引起語法錯誤。
~~~
export default name = "strick";
export default "strick";
export default let name = "strick"; //語法錯誤
~~~
**2)默認導入**
  如果要導入模塊的默認值,那么可以像下面這樣寫,同樣,為了便于比較,將它們放在了一起。
~~~
import name from "./1.js"; //寫法一
import name, { age } from "./1.js"; //寫法二
import name, * as people from "./1.js"; //寫法三
import { default as myName } from "./1.js"; //寫法四
~~~
  因為模塊只能有一個默認導出,所以對應的導入標識符可以不用花括號包裹(注意觀察前三種寫法),不僅如此,還能像第四種寫法那樣通過default關鍵字為其重命名。但有一點要注意,當同時使用默認和非默認的導入標識符時,必須把默認的寫在前面。
## 四、限制
**1)模塊路徑**
  由于ES6中的模塊被設計成了靜態的,因此需要在編譯階段就明確模塊之間的依賴關系,而不是在運行過程中動態計算,像下面這樣將模塊路徑設為變量或表達式都是錯誤的寫法。
~~~
let path = "./1.js";
export * from path; //變量
export * from "./" + "1.js"; //表達式
import * as people from path; //變量
import * as people from "./" + "1.js"; //表達式
~~~
**2)作用域**
  export和import語句都是靜態的,無法動態導出和導入。因此只能出現在模塊的頂層作用域中,而不能出現在塊級或函數作用域中,下面的寫法都會引起語法錯誤。
~~~
//函數作用域
function getName() {
export * from "./1.js";
import * as people from "./1.js";
}
//塊級作用域
if(true) {
export * from "./1.js";
import * as people from "./1.js";
}
~~~
**3)標識符**
  導出和導入語句中的標識符如果重復,那么也會引起語法錯誤,如下所示。
~~~
export { name, name } from "./1.js";
import { name, name } from "./1.js";
~~~
## 五、用標簽加載模塊
  在瀏覽器中,無論是以外部還是內聯的方式嵌入模塊文件,都需要將它的type屬性設為“module”,如下代碼所示。并且在加載模塊時為了避免腳本阻塞,會自動應用布爾屬性defer,即HTML文檔的解析和模塊文件的下載是同時進行的,待到解析完后才會執行模塊。
~~~
<script src="1.js" type="module"></script>
<script type="module">
import { name } from "./1.js";
console.log(name);
</script>
<script src="2.js" type="module"></script>
~~~
  上面代碼會被依次執行,先執行第一個外部模塊,再執行內聯模塊,最后執行第二個外部模塊。注意,在每個模塊中用import導入的其它模塊也會被解析和下載,并且同一個模塊每次只能被加載一次。
*****
> 原文出處:
[博客園-ES6躬行記](https://www.cnblogs.com/strick/category/1372951.html)
[知乎專欄-ES6躬行記](https://zhuanlan.zhihu.com/pwes6)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020