轉原文javascript
javaScript — 目錄最火熱的語言,處處發着光芒, html5, hybrid apps, node.js, full-stack 等等。javaScript 從一個僅僅在瀏覽器上面的一個玩具語言,一轉眼演變成無所不能神通常的存在。可是,因爲天生存在着一點戲劇性(javaScript 據傳說是在飛機上幾天時間設計出來的),模塊系統做爲一門語言最基本的屬性倒是javaScript所缺的。css
讓咱們回到過去,經過 <script>
標籤來編寫管理 js 腳本的年代也歷歷在目,翻看如今的許多項目,仍是能找到這樣子的痕跡,可是隨着項目規模的不斷增加,js文件愈來愈多,需求的不斷變動,讓維護的程序員們愈來愈力不從心,怎麼破?html
2009 ~ 2010 年間,CommonJS 社區大牛雲集,稍微瞭解點歷史的同窗都清楚,在同時間出現了 nodejs,一會兒讓 javaScript 搖身一變,有了新的用武之地,同時在nodejs推進下的 CommonJS 模塊系統也是逐漸深刻人心。前端
package.json
來進行 module 的依賴關係配置,require一個module只須要require package.json裏面定義的name便可 同時,nodejs也定義了一些系統內置的module方便進行開發,好比簡單的http serverhtml5
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/');
CommonJS 在nodejs帶領下,風聲水起,聲明大噪,CommonJS 社區大牛們也就逐漸思考可否把在nodejs的這一套推向瀏覽器?java
理想很豐滿,可是現實倒是不盡如人意的node
一個最大的問題就是在瀏覽器加載腳本天生不支持同步的加載,沒法經過文件I/O同步的require加載一個js腳本
So what ? CommonJS 中逐漸分裂出了 AMD,這個在瀏覽器環境有很好支持的module規範,其中最有表明性的實現則是 requirejswebpack
正如 AMD 介紹的那樣:
The Asynchronous Module Definition (AMD) API specifies a mechanism for defining modules such that the module and its dependencies can be asynchfanronously loaded. This is particularly well suited for the browser environment where synchronous loading of modules incurs performance, usability, debugging, and cross-domain access problems.git
翻譯過來就是說:異步模塊規範 API 定義了一種模塊機制,這種機制下,模塊和它的依賴能夠異步的加載。這個很是適合於瀏覽器環境,由於同步的加載模塊會對性能,可用性,debug調試,跨域訪問產生問題。程序員
確實,在瀏覽器環境下,AMD有着本身獨特的優點:
因爲源碼和瀏覽器加載的一致,所見即所得,代碼編寫和debug很是方便。尤爲是在多頁面的web項目下,不一樣頁面的腳本js都是根據依賴關係異步按需加載的,不用手動處理每一個頁面加載js腳本的狀況。
可是,AMD 有一個不得不認可的做爲一個module system的不足之處
請問?在 AMD(requireJS)裏面怎麼使用一個第三方庫的?
通常都會經歷這麼幾個步驟:
一個庫就須要問這麼些個問題,並且都是人工手動的操做
最最關鍵的問題是你辛辛苦苦搞定的配置項都是相對於你當前項目的
當你想用在其餘項目或者是單元測試,那麼OK,你還得修改一下
由於,你相對的是當前項目的根路徑,一旦根路徑發生改變,一切都發生了變化
requireJS 使用以前必須配置,同時該配置很難重用
相比較於 CommonJS 裏面若是要使用一個第三方庫的話,僅僅只須要在 package.json 裏面配置一下 庫名和版本號,而後npm install一下以後就能夠直接 require 使用的方式,AMD 的處理簡直弱爆了 !!!
對於 AMD 的這個不足之處,又有社區大神提出了能夠在 browser 運行的 CommonJS 的方式,而且經過模塊定義配置文件,能夠很好的進行模塊複用
比較知名的就有 substack 的 browserify, tj 曾主導的 component,還有後來的 duo,webpack,時代就轉眼進入了 browser 上的 CommonJS
因爲 CommonJS 的 require 是同步的,在 require 處須要阻塞,這個在瀏覽器上並無很好的支持(瀏覽器只能異步加載腳本,並無同步的文件I/O),CommonJS 要在 browser 上直接使用則必須有一個 build 的過程,在這個 build 的過程裏進行依賴關係的解析與作好映射。這裏有一個典型的實現就是 substack 的 browserify。
browserify 在 github 上的 README.md 解釋是:
require('modules')
in the browser
Use a node-style require()
to organize your browser code
and load modules installed by npm.
browserify will recursively analyze all the require()
calls in your app in
order to build a bundle you can serve up to the browser in a single <script>
tag.
在 browserify 裏能夠編寫 nodejs 同樣的代碼(即CommonJS以及使用package.json進行module管理),browserify 會遞歸的解析依賴關係,並把這些依賴的文件所有build成一個bundle文件,在browser端使用則直接用 <script>
tag 引入這個 bundle 文件便可
browserify 有幾個特性:
爲了能讓browser直接使用nodejs上的module,browserify 內置了一些 nodejs module 的 browser shim 版本
好比:assert,buffer,crypto,http,os,path等等,具體見browserify builtins
這樣子,browserify就解決了:
component 經過 component.json 來進行依賴描述,它的庫管理是基於 github repo的形式,因爲進行了顯示的配置依賴,它並不須要對源碼進行 require 關係解析,可是時刻須要編寫 component.json 也使得開發者很是的痛苦,開發者更但願 code over configuration 的形式
因此有了 duo,duo 官網上介紹的是:
Duo is a next-generation package manager that blends the best ideas from Component, Browserify and Go to make organizing and writing front-end code quick and painless.
Duo 有幾個特色:
直接使用 require 使用 github 上某個 repo 的庫
var uid = require('matthewmueller/uid');
var fmt = require('yields/fmt');
var msg = fmt('Your unique ID is %s!', uid());
window.alert(msg);
不需用配置文件進行描述,直接內嵌在代碼裏面
webpack takes modules with dependencies and generates static assets representing those modules.
webpack 是一個 module bundler 即模塊打包工具,它支持 CommonJS,AMD的module形式,同時還支持 code splittling,css 等
最近 browserify 和 webpack 也有必定的比較,能夠看看 substack 的文章 browserify for webpack users
這些 browser 上的 CommonJS 解決方案都有一個共同的問題,就是沒法避免的須要一個 build 過程,這個過程雖然能夠經過 watch task 來進行自動化,可是仍是edit和debug仍是很是不方便的
試想着,你在進行debug,你設置了一個debugger,而後單步調試,調試調試着跳到了另一個文件中,而後因爲是一個bundle大文件,你在瀏覽器開發者工具看到的永遠都是同一個文件,而後你發現了問題所在,回頭去改源碼,還得先找到當前所在行與源碼的對應關係!固然這個能夠經過 source map 技術來進行解決,可是相比較 AMD 那種所見即所得的開發模式仍是有必定差距
同時,須要build的過程也給多頁面應用開發帶來了不少麻煩,每一個頁面都要配置 watch task,都要配置 source map 之類的,並且build過程若是一旦出現了build error,開發者還要去看看命令行裏面的日誌,除非使用 beefy 這種能夠把命令行裏面的日誌輸出到瀏覽器console,不然不知道狀況的開發者就會一臉迷茫
這永遠是一個話題,由於誰也沒法很好的取代誰,尤爲在瀏覽器環境裏面,二者都有本身的優勢和缺點
CommonJS
AMD
前面提到的 javaScript 依賴管理的方式,其實都是實現了同一種設計模式,service locator 或者說是 dependency lookup:
經過顯示的
調用 require(id) 來向 service locator 提供方請求依賴的 module
id 能夠是路徑,url,特殊含義的字符串(duo 中的github repo)等等
相反,dependency injection 則並無顯示的
調用,而僅僅經過一種與 container 的約定描述來表達須要某個依賴,而後由 container 自動完成依賴的注入,這樣,實際上是完成了 IoC(Inversion of control 控制反轉)
service locator 和 dependency injection 並無誰必定優於誰一說,要看具體使用場景,尤爲是 javaScript 這種天生動態且是first-class的語言裏, 能夠簡單的對比下:
service locator 很是直接,須要某個依賴,則直接經過 locator 提供的 api (好比 require)調用向 locator 獲取便可,不過這也帶來了必須與 locator 進行耦合的問題,好比CommonJS的require,AMD的define
相反,dependency injection 因爲並無顯示的調用container某個api,而是經過與container之間的某個約定來進行描述依賴,container再自動完成注入,相比較 service locator 則會隱晦一點
service locator 因爲能夠本身控制,使用起來更加的靈活,所依賴的也能夠多樣,不只僅限於javaScript(還能夠是json等,具體要看service locator實現)
dependency injection 則沒有那麼的靈活,通常的container實現都是基於某個特定的module,好比最簡單的class,注入的通常都是該module所約定好的,好比class的instance
service locator 中的id實現通常基於文件系統或者其它標識,能夠是相對路徑或者絕對路徑或者url,這個其實就帶來了必定的限制性,依賴方必需要在該id描述下一直有效,若是依賴方好比改了個名字或者移動了目錄結構,那麼全部被依賴方則必須作出改動
dependency injection 中雖然也有id,可是該id是module的全局自定義惟一id,這個id與文件系統則並無直接的關係,不管外部環境如何變,因爲module的id是硬編碼的,container都能很好的處理
service locator 因爲靈活性,寫出來的代碼多樣化,module之間會存在必定耦合,固然也能夠實現鬆耦合的,可是須要必定的技巧或者規範
dependency injection 因爲天生是基於id描述的形式,控制交由container來完成,鬆散耦合,當應用規模不斷增加的時候還能持續帶來不錯的維護性
service locator 目前在javaScript界有大量實現,並且有大量的庫能夠直接使用,好比基於CommonJS的npm,所以在使用庫方面 service locator 有着自然的優點
dependency injection 則實現很少,並且因爲是與container之間的約定,不一樣container之間的實現不一樣,也沒法共通
其實,比較來比較去,不如二者結合起來使用,都有各自的優缺點:
dependency injection 來編寫鬆散耦合的應用層邏輯,service locator來使用第三方庫
一個優秀的dependency injection container須要有下面這些特性:
這其實就是 bearcat 所作的事兒
bearcat 並非實現了 service locator 模式的module system,它實現了 dependency injection container,所以bearcat能夠很好的與上面提到的各類CommonJS或者AMD結合使用,結合本身的優點來編寫彈性、持續可維護的系統(應用)
bearcat 的一個理念能夠用下面一句話來描述:Magic, self-described javaScript objects build up elastic, maintainable front-backend javaScript applications
bearcat 所倡導的就是使用簡單、自描述的javaScript對象來構建彈性、可維護的先後端javaScript應用
固然可能有人會說,javaScript裏面不只僅是對象,還能夠函數式、元編程什麼的,其實也是要看應用場景的,bearcat更適合的場景是一個多人協做的、須要持續維護的系統(應用),若是是快速開發的腳本、工具、庫,那麼則該怎麼簡單、怎麼方便,就怎麼來
假若有一個應用,須要有一輛car,同時car必需要有engine才能發動,那麼car就依賴了engine,在bearcat的 dependency injection container 下,僅僅以下編寫代碼便可:
car.js
var Car = function() { this.$id = "car"; this.$engine = null; } Car.prototype.run = function() { this.$engine.run(); console.log('run car...'); } bearcat.module(Car, typeof module !== 'undefined' ? module : {});
engine.js
var Engine = function() { this.$id = "engine"; } Engine.prototype.run = function() { console.log('run engine...'); } bearcat.module(Engine, typeof module !== 'undefined' ? module : {});
this.$id
來定義該module在bearcat container裏的全局惟一id$Id
屬性來描述依賴,在car裏就描述了須要id爲 engine的一個依賴typeof module !== 'undefined' ? module : {}
這一段是爲了與 CommonJS(nodejs) 下進行兼容,在nodejs裏因爲有同步require,則無需向在瀏覽器環境下進行異步加載
啓動bearcat容器,總體跑起來
瀏覽器環境
<script src="./lib/bearcat.js"></script> <script src="./bearcat-bootstrap.js"></script> <script type="text/javascript"> bearcat.createApp(); // create app to init bearcat.use(['car']); // javaScript objects needed to be used bearcat.start(function() { // when this callback invoked, everything is ready var car = bearcat.getBean('car'); car.run(); });
bearcat.use(['car'])
表面當前頁面須要使用 car,bearcat而後就會加載car.js,而後解析car裏面的依賴,知道須要engine,而後加載engine.js腳本,加載完以後,再把engine實例化注入到car中,最後調用bearcat.start
的回調完成整個容器的啓動
nodejs 環境
var bearcat = require('bearcat'); var contextPath = require.resolve('./context.json'); global.bearcat = bearcat; // make bearcat global, for `bearcat.module()` bearcat.createApp([contextPath]); bearcat.start(function() { var car = bearcat.getBean('car'); // get car car.run(); // call the method });
nodejs 環境下啓動,則不需用bearcat.use
了,直接把 context.json
的路徑傳遞給bearcat便可,bearcat會掃描context.json
裏面配置着的掃描路徑,該路徑下的全部js文件都會被掃描,合理的module都會註冊到bearcat中,而後實例化,注入
完整源碼 10-secondes-example
bearcat 的簡潔,異步加載的module,無需打包,所見即所得,在編寫應用層代碼上有很是大的便利
browserify 能夠直接複用 npm 上的 module,使用第三方庫很是的方便
bearcat + browserify 會是一個不錯的組合
一個例子,基於 bearcat + browserify 的 markdwon-editor
bearcat 與 browserify 之間經過一個requireUtil
(好比)的module來進行鏈接
在這個 requireUtil
可使用 browserify 的 require,用這個 require 來引入第三方庫,好比marked庫
requireUtil.js
var RequireUtil = function() { this.$id = "requireUtil"; this.$init = "init"; this.brace = null; this.marked = null; } RequireUtil.prototype.init = function() { this.brace = require('brace'); this.marked = require('marked'); } bearcat.module(RequireUtil, typeof module !== 'undefined' ? module : {});
而後在你的業務層代碼上,注入這個 requireUtil
來使用 browserify 引入的第三方庫
markDownController.js
var MarkDownController = function() { this.$id = "markDownController"; this.$requireUtil = null; // requireUtil is ready for you to use } MarkDownController.prototype.initBrace = function(md) { var ace = this.$requireUtil.brace; var editor = ace.edit('editor'); editor.getSession().setMode('ace/mode/markdown'); editor.setTheme('ace/theme/monokai'); editor.setValue(md); editor.clearSelection(); return editor; } bearcat.module(MarkDownController, typeof module !== 'undefined' ? module : {});
這樣子一來,編寫業務層代碼因爲是bearcat管理的,javaScript依賴異步加載,代碼編寫和debug就和AMD同樣,所見即所得,設置斷點什麼的,不再用擔憂找不到源文件(或者須要source map)
使用 browserify 僅僅是爲了用它來引入第三方庫,且也僅僅當引入一個新的第三方庫的時候纔會執行一下 browserify 的 build
bearcat 和 browserify 的優點就都發揮了出來,提升了開發的效率以及可維護性
bearcat-markdown-editor 官網例子地址 markdown-editor
不管是CommonJS、AMD或者是dependency injection,單獨使用某一個,javaScript依賴管理都不是完美的
應人而異,各取所需