前端為什麼需要模組化開發

看著上面的script標籤,是不是有一種很熟悉的感覺。通過script引入你想要的資源,從上到下的順序,這其中順序是非常重要的,資源的載入先後決定你的程式碼是否能夠跑的下去。當然我們還沒有加入defer和async屬性,不然載入的邏輯會更加複雜。這裡引入的資源還是算少的,過多的script標籤會造成過多的請求。同時專案越大,到最後依賴會越來越複雜,並且難以維護,依賴模糊,請求過多。全域性汙染的可能性就會更大。那麼問題來了,如何形成獨立的作用域?
defer和async的區別
defer要等到整個頁面在記憶體中正常渲染結束(DOM 結構完全生成,以及其他指令碼執行完成),才會執行;async一旦下載完,渲染引擎就會中斷渲染,執行這個指令碼以後,再繼續渲染。一句話,defer是“渲染完再執行”,async是“下載完就執行”。另外,如果有多個defer指令碼,會按照它們在頁面出現的順序載入,而多個async指令碼是不能保證載入順序的。
模組化的基石
立即執行函式(immediately-invoked function expression),簡稱IIFE,其實是一個javaScript函式。可以在函式內部定義方法以及 私有屬性 ,相當於一個封閉的作用域。例如下面的程式碼:
let module = (function(){ let _private = 'myself'; let fun = () =>{ console.log(_private) } return { fun:fun } })() module.fun()//myself module._private//undefined 複製程式碼
以上程式碼便可以形成一個獨立的作用域,一定程度上可以減少全域性汙染的可能性。這種寫法可是現代模組化的 基石 。雖然能夠定義方法,但是不能定義屬性,這時候各種前端規範就陸續登場了。
首先登場的是common.js
最先遵守CommonJS規範是node.js。這次變革讓服務端也能用js爽歪歪的寫了,我們的javaScript並不止於瀏覽器,服務端也能分一杯羹,被人稱為模組化的第一座里程碑。想想長征二萬五,第一座里程碑在哪裡?
CommomJS模組的特點
- 模組內的程式碼只會執行在模組作用域內,不會汙染到全域性作用域
- 模組的可以多次引入,但只會在第一次載入的時候執行一次,後面的執行都是取快取的值。想要讓模組再次執行,必須清楚快取。
// 刪除指定模組的快取 delete require.cache[moduleName]; // 刪除所有模組的快取 Object.keys(require.cache).forEach(function(key) { delete require.cache[key]; }) 複製程式碼
- 模組的載入順序,遵循在程式碼中出現的順序。
為什麼要少用exports
exports只是一個變數,指向module.exports,也就是exports只是一個 引用 而已。所以對外輸出模組的時候,我們就可以通過exports新增方法和和屬性。通過module.exports對外輸出其實也是讀取module.exports的變數。但是使用exports時要非常的小心,因為稍不注意就會切斷和module.exports的聯絡。例如:
exports = function(x) {console.log(x)}; 複製程式碼
上面的程式碼執行之後,exports不再指向module.exports。如果你難以區分清楚,一般最好就別用exports,只使用module.exports就行。
怎麼區分模組是直接執行,還是被呼叫執行。
require.mainAPI就有這樣的作用,如果模組是直接執行,那麼這時require.main屬性就指向模組本身。例如下面:
require.main === module 複製程式碼
為什麼客戶端不使用commonjs規範?
我們知道客戶端(瀏覽器)載入資源主要是通過網路獲取,一般本地讀取的比較少,而node.js主要是用於伺服器程式設計,模組檔案一般都存在於本地硬碟上,然後I/O讀取是非常快速的,所以即使是 同步載入 也是能夠適用的,而瀏覽器載入資源必須通過非同步獲取,比如常見的ajax請求,這時候AMD規範就非常合適了,可以非同步載入模組,允許回撥函式。
客戶端的規範不僅僅只有AMD,還有CMD.
每個規範的興起背後總有一些原因,requirejs的流行是因為commonjs未能滿足我們需要的效果,sea.js被創造的 原因 也是因為requirejs不能滿足一些場景。
AMD和CMD的區別
- | AMD | CMD |
---|---|---|
原理 | define(id ?,dependencies ?,factory)定義了一個單獨的函式“define”。id為要定義的模組。依賴通過dependencies傳入factory是一個工廠引數的物件,指定模組的匯出值。 | CMD規範與AMD類似,並儘量保持簡單,但是更多的與common.js保持相容性。 |
優點 | 特別適用於瀏覽器環境的非同步載入 ,且可以並行載入。依賴前置,提前執行。 定義模組時就能清楚的宣告所要依賴的模組 | 依賴就近,延遲執行。 按需載入,需要用到時再require |
缺點 | 開發成本較高,模組定義方式的語義交為難理解,不是很符合通過的模組化思維方式。 | 依賴SPM打包,模組的載入主觀邏輯交重。 |
體現 | require.js | sea.js |
ES6讓前端模組化觸手可及
概念
ES6的模組不是物件,import語法會被JavaScript引擎 靜態分析 ,請注意,這是一個很重要的功能,我們通常使用commonjs時,程式碼都是在執行時載入的,而es6是在編譯時就引入模組程式碼,當然我們現在的瀏覽器還沒有這麼強大的功能,需要藉助各類的編譯工具(webpack)才能正確的姿勢來使用es6的模組化的功能。也正因為能夠編譯時就引入模組程式碼,所以使得靜態分析就能夠實現了。
ES6模組化有哪些優點
-
靜態化編譯 如果能夠靜態化,編譯的時候就能確定模組的依賴關係,以及輸出和輸入的變數,然後CommonJS和AMD以及CMD都只能在執行程式碼時才能確定這些關係。
-
不需要特殊的UMD模組化格式 不再需要UMD模組的格式,將來伺服器和瀏覽器都會支援ES6模組格式。目前各種工具庫(webpack)其實已經做到這一點了。
-
目前的各類全域性變數都可以模組化 比如navigator現在是全域性變數,以後就可以模組化載入。這樣就不再需要物件作為名稱空間。
需要注意的地方
- export語句輸出的介面,通過import引入之後,與其對應的值是動態的繫結關係,也就是模組的內的值即使改變了,也是可以取到實時的值的。而commonJS模組輸出的是值的快取,不存在動態更新。
- 由於es6設計初衷就是要靜態優化,所以export命令不能處於 塊級作用域內 ,如果出現就會報錯,所以一般export統一寫在底部或則頂層。
function fun(){ export default 'hello' //SyntaxError } 複製程式碼
- import命令具有提升效果,會提升到整個模組的頭部首先執行。例如:
fun() import {fun} from 'myModule'; 複製程式碼
上面的程式碼import的執行早於fun呼叫,原因是import命令是編譯階段執行的,也就是在程式碼執行之前。
export default使用
export default就是輸出一個叫default的變數或方法,然後系統允許你為它取任意名字。所以,你可以看到下面的寫法。
//modules.js function add(x,y){ return x*y } export {add as default}; //等同於 export default add; //app.js import {default add foo} from 'modules'; //等同於 import foo from 'modules' 複製程式碼
這是因為export default命令其實只是輸出一個叫做default的變數,所以它後面不能跟變數宣告語句。