JavaScript模塊發展變遷史

前兩天有朋友拿了這樣一段代碼來問我,「我想把一段代碼寫成模塊化的樣子,你幫我看看是否是這樣的。」,代碼大概是這樣的:
(function(global) {
    var myModules = {
        name: 'xxx',
        location: 'chengdu',
        intro: function() {
                return `his name is ${myModules.name} and come from ${myModules.location}`
            },
        }
        // some other code...
        if(typeof module === 'undefined')
            global.myModules = myModules
        else
            module.exports = myModules
})(this)
「但是我這段代碼在全局仍是能 myModules這個屬性啊?」
我一臉懵,還有這種操做,爲何你的當即執行函數要把 this傳進去呢,這樣不久將裏面的內容掛在到了 window上了嗎,他似懂非懂,我只好說,你可能須要回頭去看看 AMDCMD規範了,我大概可以理解這其中的原因,畢竟這段時間前端發展的速度飛快,加上 webpack也不須要本身配,包括 vuereactangular在內的框架類庫都有一鍵生成項目的工具,從而只須要使用下 import * from '*'export default {},而這種便利會讓新人再也不須要去學習基本原理就能快速上手,畢竟如今都 ES2018了呀。
  • 對象形式

最開始的時候,爲了減小全局變量,通常會使用一個對象的方式來對全部的變量方法進行一個包裝:javascript

var obj = {
        a: 1,
        b: 2,
        sum: function(val) {
           return obj.a + obj.b + val
        },
        rest: function(val) {
           return val - obj.a - obj.b
        }
   }
以上代碼彷佛解決了全局變量的問題,可是其中的 ab兩個變量仍是可能被修改,其中包含的進化有限。
  • 當即執行函數

回過頭來看文章開頭的代碼,姑且不論以上代碼的錯誤之處,稍做修改,這算是最初的一種關於JavaScript模塊化的開端,當即執行函數:html

var add = (function(){
    var a = 1
    var b = 2
    function sum(count){
        return a + b + count
    }
    function rest(count){
        return count - a - b
    }
    return {
        rest: rest,
        sum: sum
    }
})()
這就是一種最簡單的模塊化的方式,利用閉包的特性,設置了兩個只有在被暴露出來的 addreduce方法內部才能訪問到的兩個變量,從而保證了函數的局部變量不可被修改的特性,此次的進化用到了閉包,從而實現了部分有效的目的。
  • 放大模式

其實開頭的代碼更符合另一種叫作放大模式的方法,不過通常來講不會講window做爲放大模式中被傳入的對象前端

var globalObject = {
      fn1: function() {
        // todo...
      },
      // ...
    }
    globalObject = (function(obj) {
      var name = 'xxx'
      var location = 'xxx'
      function sum(val) { /* todo... */ }
      function rest(val) { /* todo... */ }
      obj.sum = sum
      obj.rest = rest
      return obj
    })(globalObject)
以這種形式來寫,可讓全局變量儘可能的減小,同時讓一個當即執行函數中的代碼儘可能作到精簡。
  • 可是這種放大模式也存在着問題,好比當globalObject內容足夠多的時候,極可能會形成命名重複的狀況,而且以上全部的方式都不能夠減小script標籤的數量,因此,咱們仍是會被模塊的加載順序,命名空間衝突等問題所困擾,這時候,咱們應該跨入新時代了。

  • CMD規範

CMD規範來自阿里的框架seajs,當初確實有挺多人使用,不過現階段已經再也不維護了,我也不會,就暫時不說了,只列出來。vue

  • commonjs

同時,從2009年開始,JavaScript就再也不只是一種瀏覽器端的腳本語言了,nodejs的出現讓使用js開發服務端變成了可能,隨着node出現的東西還有一個叫作commonjs的規範,在這個規範中,每一個文件都是一個模塊,有着本身的做用域。java

譬如,以下代碼node

// 文件a.js
    var a = 1
    // 文件b.js
    console.log(a) // a is not defined.

在這樣的特性下,a.jsb.js都有着本身獨有的做用域,要在b中對a進行訪問,就須要一種加載機制,通常來講,有兩種方法可以作到:react

方法1
// 文件a.js
    global.a = 1
    // 文件b.js
    console.log(a) // 1

這種方法掛載在global上,固然是不可取的。jquery

方法2
// 文件a.js
    exports.a = 1
    // 文件b.js
    var moduleA = require('./a')
    console.log(moduleA.a)
  • AMD規範

requirejs的出現讓script標籤的減小變成了可能,在requirejs的時代,咱們通常會使用jQueryunderscore這類的類庫,若是按照往常的樣子咱們會將代碼寫成下面這副模樣:webpack

<script src="/js/lib/jquery.min.js"></script>
<script src="/js/lib/underscore.min.js"></script>
<script src="/js/app/index.js"></script>
<script src="/js/app/app.js"></script>
<!-- and so on... -->

這樣的代碼乍一看彷佛沒什麼問題,可是當一個項目的代碼量上了一個量級,一切就變得不是這麼回事兒了,你會被困在加載順序,加載時間的問題上,這也就是requirejs可以出現的緣由了。web

requirejs中,你能夠如此改寫以上代碼:

// `index.js`
   require(['js/lib/jquery.min', 'js/lib/underscore.min', 'js/app/app'], function($, _, app) {  /*  todo...  */  })
    // `app.js`
   define(['js/lib/jquery.min', 'js/lib/underscore.min'], function($, _) {  /*  todo...  */  })
<script data-main="/index.js" src="/js/require.js"></script>

這裏固然顯得更加優雅了,在requirejs的推廣過程當中,AMD規範也就應運而生了,那麼,requirejs或者說AMD規範到底解決了什麼樣的問題呢,主要有幾點:

  1. AMD是「異步模塊定義」的縮寫,也就是說,其中內容是異步加載的,從而讓頁面不被js的加載阻塞,最大程度上的避免了頁面假死等狀況的產生。
  2. AMD的一個好處在與依賴前置,全部被使用到的模塊都會被提早加載好,從而加快運行速度。

那麼,commonjs規範和AMD規範有什麼區別呢

  1. 運行環境不一樣,commonjs規範只能運行在node端,而AMD規範則被用到瀏覽器端
  2. 因爲運行環境的不一樣,兩者的加載機制也不一樣,commonjs中的require是同步執行的,而AMD中則是異步的。
  • ES2015模塊化

ES2015中,可使用export, export default, import import * as 等操做符來做模塊化的功能,可是這個規範如今還沒有被任何瀏覽器加入規範中,我目前的Chrome版本爲63.0.3239.132,也沒法原生支持,不過現階段咱們幾乎都用上了這個規範,這一切都只能歸功於babel,webpackrollup等新工具的出現,既然如此,那就擁抱將來吧,不過有一點,須要在瞭解原理的前提下,否則,假若有一天,真的須要咱們來封裝一個小小的模塊的時候,沒有了那些工具,咱們該從何下手呢。

相關文章
相關標籤/搜索