前兩天有朋友拿了這樣一段代碼來問我,「我想把一段代碼寫成模塊化的樣子,你幫我看看是否是這樣的。」,代碼大概是這樣的:
(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
上了嗎,他似懂非懂,我只好說,你可能須要回頭去看看AMD
和CMD
規範了,我大概可以理解這其中的原因,畢竟這段時間前端發展的速度飛快,加上webpack
也不須要本身配,包括vue
,react
,angular
在內的框架類庫都有一鍵生成項目的工具,從而只須要使用下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 } }
以上代碼彷佛解決了全局變量的問題,可是其中的a
和b
兩個變量仍是可能被修改,其中包含的進化有限。
回過頭來看文章開頭的代碼,姑且不論以上代碼的錯誤之處,稍做修改,這算是最初的一種關於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 } })()
這就是一種最簡單的模塊化的方式,利用閉包的特性,設置了兩個只有在被暴露出來的add
和reduce
方法內部才能訪問到的兩個變量,從而保證了函數的局部變量不可被修改的特性,此次的進化用到了閉包,從而實現了部分有效的目的。
其實開頭的代碼更符合另一種叫作放大模式的方法,不過通常來講不會講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.js
和b.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
的時代,咱們通常會使用jQuery
,underscore
這類的類庫,若是按照往常的樣子咱們會將代碼寫成下面這副模樣: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
規範到底解決了什麼樣的問題呢,主要有幾點:
AMD
是「異步模塊定義」的縮寫,也就是說,其中內容是異步加載的,從而讓頁面不被js
的加載阻塞,最大程度上的避免了頁面假死等狀況的產生。AMD
的一個好處在與依賴前置,全部被使用到的模塊都會被提早加載好,從而加快運行速度。
那麼,commonjs
規範和AMD
規範有什麼區別呢
- 運行環境不一樣,
commonjs
規範只能運行在node
端,而AMD
規範則被用到瀏覽器端- 因爲運行環境的不一樣,兩者的加載機制也不一樣,
commonjs
中的require
是同步執行的,而AMD
中則是異步的。
ES2015
模塊化在ES2015
中,可使用export
, export default
, import
import * as
等操做符來做模塊化的功能,可是這個規範如今還沒有被任何瀏覽器加入規範中,我目前的Chrome
版本爲63.0.3239.132
,也沒法原生支持,不過現階段咱們幾乎都用上了這個規範,這一切都只能歸功於babel
,webpack
和rollup
等新工具的出現,既然如此,那就擁抱將來吧,不過有一點,須要在瞭解原理的前提下,否則,假若有一天,真的須要咱們來封裝一個小小的模塊的時候,沒有了那些工具,咱們該從何下手呢。