寫在開頭
前端的發展總會讓我們眼前一亮,這又有什麼規範出來了,上個規範我還沒理解透徹呢。但不管未來怎麼發展,瞭解歷史還是非常重要的,以史為鏡,可以知得失。知道了規範的發展歷史,才能更好的瞭解目前的規範。
沒有模組化,前端程式碼會怎麼樣?
- 變數和方法不容易維護,容易汙染全域性作用域
- 載入資源的方式通過script標籤從上到下。
- 依賴的環境主觀邏輯偏重,程式碼較多就會比較複雜。
- 大型專案資源難以維護,特別是多人合作的情況下,資源的引入會讓人奔潰。
當年我們是怎麼引入資源的。
看著上面的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的變數,所以它後面不能跟變數宣告語句。
特別技巧偵查程式碼是否處於ES6模組中
利用頂層的this等於undefined這個語法點,可以偵測當前程式碼是否在 ES6 模組之中。
const isNotModuleScript = this !== undefined;
複製程式碼
如果大神您想繼續探討或者學習更多知識,歡迎加入QQ或者微信一起探討:854280588