前端爲何須要模塊化開發

寫在開頭

前端的發展總會讓咱們眼前一亮,這又有什麼規範出來了,上個規範我還沒理解透徹呢。但無論將來怎麼發展,瞭解歷史仍是很是重要的,以史爲鏡,能夠知得失。知道了規範的發展歷史,才能更好的瞭解目前的規範。前端

沒有模塊化,前端代碼會怎麼樣?
  • 變量和方法不容易維護,容易污染全局做用域
  • 加載資源的方式經過script標籤從上到下。
  • 依賴的環境主觀邏輯偏重,代碼較多就會比較複雜。
  • 大型項目資源難以維護,特別是多人合做的狀況下,資源的引入會讓人奔潰。
當年咱們是怎麼引入資源的。

script.png

看着上面的script標籤,是否是有一種很熟悉的感受。經過script引入你想要的資源,從上到下的順序,這其中順序是很是重要的,資源的加載前後決定你的代碼是否可以跑的下去。固然咱們尚未加入defer和async屬性,否則加載的邏輯會更加複雜。這裏引入的資源仍是算少的,過多的script標籤會形成過多的請求。同時項目越大,到最後依賴會愈來愈複雜,而且難以維護,依賴模糊,請求過多。全局污染的可能性就會更大。那麼問題來了,如何造成獨立的做用域?java

defer和async的區別

defer要等到整個頁面在內存中正常渲染結束(DOM 結構徹底生成,以及其餘腳本執行完成),纔會執行;async一旦下載完,渲染引擎就會中斷渲染,執行這個腳本之後,再繼續渲染。一句話,defer是「渲染完再執行」,async是「下載完就執行」。另外,若是有多個defer腳本,會按照它們在頁面出現的順序加載,而多個async腳本是不能保證加載順序的。node

模塊化的基石

當即執行函數(immediately-invoked function expression),簡稱IIFE,實際上是一個javaScript函數。能夠在函數內部定義方法以及私有屬性,至關於一個封閉的做用域。例以下面的代碼:webpack

let module = (function(){
    let _private = 'myself';
    let fun = () =>{
        console.log(_private)
    }
    return {
        fun:fun
    }
})()
module.fun()//myself
module._private//undefined
複製代碼

以上代碼即可以造成一個獨立的做用域,必定程度上能夠減小全局污染的可能性。這種寫法但是現代模塊化的基石。雖然可以定義方法,可是不能定義屬性,這時候各類前端規範就陸續登場了。git

首先登場的是common.js

最早遵照CommonJS規範是node.js。此次變革讓服務端也能用js爽歪歪的寫了,咱們的javaScript並不止於瀏覽器,服務端也能分一杯羹,被人稱爲模塊化的第一座里程碑。想一想長征二萬五,第一座里程碑在哪裏?es6

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的聯繫。例如:github

exports = function(x) {console.log(x)};
複製代碼

上面的代碼運行以後,exports再也不指向module.exports。若是你難以區分清楚,通常最好就別用exports,只使用module.exports就行。web

怎麼區分模塊是直接執行,仍是被調用執行。

require.mainAPI就有這樣的做用,若是模塊是直接執行,那麼這時require.main屬性就指向模塊自己。例以下面:ajax

require.main === module
複製代碼
爲何客戶端不使用commonjs規範?

咱們知道客戶端(瀏覽器)加載資源主要是經過網絡獲取,通常本地讀取的比較少,而node.js主要是用於服務器編程,模塊文件通常都存在於本地硬盤上,而後I/O讀取是很是快速的,因此即便是同步加載也是可以適用的,而瀏覽器加載資源必須經過異步獲取,好比常見的ajax請求,這時候AMD規範就很是合適了,能夠異步加載模塊,容許回調函數。express

客戶端的規範不只僅只有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

QQ.png
微信
相關文章
相關標籤/搜索