在上一篇《前端模組化,AMD和CMD的區別總結》中,介紹了commonJS規範下衍生出來的AMD和CMD。多年來,前端的js程式碼大都是以這種方式組織起來(再早連這個都沒有。。。),但是從語言設計本身的層面上講,官方一直沒有設計出javascript的模組系統,直到。。。ES6的正式釋出!
可以說ES6的正式釋出(討論了十年)是前端界翹首以盼眾望所歸的事,它是javascript被建立以來最重要的更新之一。而其中最重要的就是JS模組化的定義,從原生語法上支援了匯入與匯出模組,我們終於不再需要使用一些庫(Requirejs、Seajs等)來模仿模組化了。
本文不想過多介紹如何使用ES6模組的語法(比如import和export的規範),因為網路上關於這方面的介紹已經很多了,現在來說點不太常被提及但是對理解有很重要的東西。
“模組”是自動執行在嚴格模式下並且沒有辦法退出執行的JavaScript程式碼。模組有三個比較顯著的特性:
1、在模組頂部建立的變數不會自動被新增到全域性作用域(比如windows下),訪問模組的變數必須通過匯出的方式。
2、在模組頂部this的值是undefined。
3、模組不支援HTML的程式碼註釋。
以上也是ES6模組區別於傳統模組系統很重要的三點,而且ES6模組系統與傳統模組系統更顯著的一個區別是:
4、ES6的模組系統是靜態解析的
舉例:
if(Math.random()){
import name from `./example.js` // 丟擲錯誤
}
-------------------------------------------------
let name = `js`
if(Math.random()){
export {name} // 丟擲錯誤
}
複製程式碼
import和export不能在條件語句或任何動態方式中使用,原因是要讓JavaScript引擎靜態地確定哪些模組可以匯出。
如果使用過RequireJS或者SeaJS的開發者應該深有體會,它們可沒有這樣的限制。
由於ES6模組的靜態性,導致了一個怪異甚至讓人困惑之處,先看一個例子:
//a.js
let name = `ajs`;
let setName = fuction(newName) {
name = newName;
}
export {name, setName}
//b.js
import {name, setName} from `./a.js`;
console.log(name) // `ajs`
name = `bjs` // 丟擲錯誤
setName(`cjs`)
console.log(name) // `cjs`
複製程式碼
b.js只是簡單的引用了a.js中的值,而不能改變a.js中的值,當呼叫setName(`cjs`)時會回到a.js中去執行,並將其中的name設定為`cjs`。這說明ES6模組輸出的是值的引用,與CommonJS(輸出的是值的拷貝)完全不同。
至於將ES6模組系統設計成靜態的原因,大家可以參考這篇文章Static module resolution。其中原理較為深奧,個人認為可以不求甚解。
PS:動態import(不是ES6的內容)
凡事有利就有弊,靜態性的模組系統在帶來一系列好處的同時,也限制了開發者對於專案靈活性的掌控。比如在某些條件語句或是使用者點選觸發的操作裡面,動態(或者說按需)匯入模組的要求就變得很迫切。不過還好,現在動態匯入的提案已經存在於TC39的第三階段了。
你可以這樣使用:
if(Math.random()){
import(`./example.js`).then((M)=>{
let Mod = M.default
// TODO
})
}
或者
if(Math.random()){
import(`./example.js`).then(({setName})=>{
setName(`Dynamic`)
// TODO
})
}
甚至是這樣
const locale = `en`;
import(`./utils_${locale}.js`).then(
(utils)=>{
console.log(`utils`, utils);
utils.default();
}
);
複製程式碼
·你可以在延遲載入、條件載入和使用者操作的情景下使用動態匯入
·動態import()可以在指令碼的任何地方使用
·import()能夠傳遞字串,你可以根據你的需求構造匹配符