模塊化這個問題並非一開始就存在,WWW剛剛問世的時候,html,JavaScript,CSS(JS和CSS都是後來在網景被引進瀏覽器的)都是極其簡單的存在,不需要模塊化。
模塊化的需求是規模的產物,當web page進化到web application,瀏覽器端處理的邏輯越來越複雜,展現的樣式和動畫越來多,對於工程的要求也就越來越高。於是模塊化的需求也就產生了。模塊化的意義:
- 組件的複用,降低開發成本和維護成本
- 組件單獨開發,方便分工合作
- 模塊化遵循標準,方便自動化依賴管理,代碼優化,部署
JavaScript長久以來被認爲是簡單的腳本語言,實際上情況早就發生來變化,在最新版的JavaScript是通用編程語言而不是腳本語言。腳本語言,比如shell並不是用來完成複雜功能的,只是用來做一些自動化控制,是不需要模塊化的。而用於構建複雜系統通用編程語言(比如Java)一般都有模塊的實現。
1.模塊化標準
ES6之前,JavaScript並沒有原生的模塊機制,好在JavaScript非常靈活,有很多種寫法可以將代碼天然隔離,起到模塊化的功能:
//define
var modules = {}
modules.mod1 = {
foo : function(){...},
bar : function(){...}
...
} //歡迎加入前端全棧開發交流圈一起學習交流:1007317281
//call
modules.mod1.foo()
在客戶端這種方式基本是夠用的,然而問題依然存在:你無法管理依賴,所有的代碼都必須load到內存中,需要哪些模塊必須由人工處理。分模塊是工程化的產物,也是自然發展的結果,自然有很多嘗試。很顯然,模塊之間互相依賴需要編寫模塊的時候遵循一定的規範。現存的規範還真不少,不知道ES6 能否終結這場混戰:
- AMD
- CMD
- closure
- CommonJS
- ES6
AMD和CMD分別是requireJS和seaJS定義的標準。使用純原生的ES5語法意味者其只能使用閉包,書寫和閱讀都很怪異。值得一提的是AngularJS也使用類似的方式,以至於Angular的作者們都受不了,決定在AngularJS 2 使用新的語言AtScript,前端輪子太多,又造了一個,好在這個輪子造的比較好,兼容ES6 TypeScript規範,扯的遠了,看看AMD長得啥樣:
AMD:
define(['./a', './b'], function(a, b) {
...
})
Closure是google出品的前端工具,Closure提供了一系列工具和庫,谷歌自己的多個項目都是使用Closure開發的。closure compiler通過模塊間依賴的聲明把所有被依賴的文件打包到一起,而且Closure的一大優勢是如果採用破壞性壓縮(ADVANCED)壓縮率極高。
//文件A
goog.provide('module1')
com.foo.bar = {
...
} //歡迎加入前端全棧開發交流圈一起學習交流:1007317281
//文件B
goog.require('module1')
var a = com.foo.bar;
然而Closure並不完美,不同的文件共享同一個全局對象,所以你不得不這樣寫 a.b.c=...。
CommonJS是Node.js使用的模塊化標準。Node.js對於前端開發者來說不僅僅可以提供一個Server,還是一個完美的開發平臺,在Node上使用Grunt/gulp構建web項目是件很爽的事情。Node的模塊化聲明的方式與Closure類似,只是更進一步,天然隔離了命名空間。上面的代碼如果使用CommonJS的模塊化規範可以這麼寫
//文件A
module.exports = {...}
//文件B
var a = require('./foo/bar')
browserify讓使用CommonJS模塊化規範的代碼可以運行在客戶端上。
2.靜態加載與動態加載
在看ES6之前我們先看模塊加載的兩種方式:
- 靜態加載:在編譯階段進行,把所有需要的依賴打包到一個文件中
- 動態加載:在運行時加載依賴
AMD標準是動態加載的代表,而CommonJS是靜態加載的代表。AMD的目的是用在瀏覽器上,所以是異步加載的。而NodeJS是運行在服務器上的,同步加載的方式顯然更容易被人接收,所以使用了CommonJS。同樣的道理,如果靜態加載,那就使用同步的加載方式,如果動態加載就必須用異步的加載方式。
那麼ES6採用何種加載機制?
ES6既希望用簡單的聲明方式來完成靜態加載,又不願放棄動態加載的特性,而這兩種方式幾乎不可能簡單的同時實現,所以ES6提供了兩種獨立的模塊加載方法。
2.1 聲明的方式
import {foo} from module1
2.2 通過System.import API的方式
System.import('some_module')
.then(some_module => {
// Use some_module
})
.catch(error => {
...
}); //歡迎加入前端全棧開發交流圈一起學習交流:1007317281
再看下export的語法,與CommonJS很像,只不過沒有了module這個對象,而直接調用export。 可以export任何一個 函數,變量,對象
//expt.js
export function abc(){}//export 一個命名的function
export default function(){} //export default function
export num=123 //export 一個數值
export obj={}
export { obj as default };
//import
import expt from 'expt'//default export
import {default as myModule} from 'expt' //rename
import {abc,num,obj} from 'expt'
目前來看,使用預編譯的方式顯然要好於使用動態加載,瀏覽器對ES6語法支持還很差,如果使用動態加載ES6,在瀏覽器端要做ES6到ES5的翻譯工作,這個顯然是重複低效的。但是隨着瀏覽器對ES6支持增強,尤其是瀏覽器實現了動態加載API後,動態加載的優勢就會展現:
- 更流暢的用戶體驗,動態加載可以實現類似lazyload的加載方式,將download的時間分散
- 更簡潔的項目,無需預編譯,項目可以少配置很多工具
- HTTP/2的普及更傾向於使用多個小的請求,適合動態加載
3.實踐
如果現在使用ES6,可以選擇動態加載模塊system.js 或者browserify的預編譯方法。
使用system.js+babel動態加載依賴。system.js 是ES6動態模塊加載的一個實現。寫了一個小DEMO:
項目初始化
bower install babel system.js --save
index.html
<script src="/bower_components/system.js/dist/system.js"></script>
<script>
System.config({
baseURL : "/scripts",
transpiler : 'babel',
map : {
babel:'/bower_components/babel/browser.js'
}
} //歡迎加入前端全棧開發交流圈一起學習交流:1007317281
)
System.import('main.js').then(function(m){
m.default.sayHello()
})
</script>
main.js
export default {
sayHello : function(){
console.log('hello')
}
}
使用gulp+browserify+babel預編譯。gulp是一個Node.js平臺上的任務管理平臺。預編譯要做很多配置,非常繁瑣,推薦使用yeoman來生成項目骨架。比如使用generator-es6-webapp。
生成非常簡單,在項目目錄中執行
yo es6-webapp
缺少依賴的化安裝依賴就好。
4.其他,關於前端化趨勢
ES6模塊化意味着什麼?
更強大的前端,Web技術整體前移。HTML5的發展和某些優秀瀏覽器的支持讓web技術整體前移,以前像渲染這種工作在後端進行是由於瀏覽器薄弱,且有老IE這種拖後腿搗亂的選手。
簡化編程模型,人工管理JS依賴和將多個JS打包這種工作可以不需要了,而配合WebComponents標準,開發Web將不再借助模板引擎和預編譯引擎。
前端化還有更深遠的影響--在過去瀏覽器是個工具,現在瀏覽器是個重要的工具,在未來瀏覽器就是用戶唯一的操作系統。
感謝您的觀看,如有不足之處,歡迎批評指正。
獲取資料👈👈👈
本次給大家推薦一個免費的學習羣,裏面概括移動應用網站開發,css,html,webpack,vue node angular以及面試資源等。
對web開發技術感興趣的同學,歡迎加入Q羣:👉👉👉1007317281👈👈👈,不管你是小白還是大牛我都歡迎,還有大牛整理的一套高效率學習路線和教程與您免費分享,同時每天更新視頻資料。
最後,祝大家早日學有所成,拿到滿意offer,快速升職加薪,走上人生巔峯。