转原文
概述
javaScript — 目次最炽热的言语,随处发著光泽, html5, hybrid apps, node.js, full-stack 等等。javaScript 从一个仅仅在浏览器上面的一个玩具言语,一转眼演变成无所不能神平常的存在。然则,由于生成存在着一点戏剧性(javaScript 据传说是在飞机上几天时刻设想出来的),模块体系作为一门言语最基本的属性倒是javaScript所缺的。
让我们回到过去,经由历程 <script>
标签来编写治理 js 剧本的年代也念念不忘,翻看如今的许多项目,照样能找到这模样的陈迹,然则跟着项目范围的不停增进,js文件愈来愈多,需求的不停变动,让保护的程序员们愈来愈力不从心,怎样破?
CommonJS
2009 ~ 2010 年间,CommonJS 社区大牛云集,轻微相识点汗青的同砚都清晰,在同时刻涌现了 nodejs,一会儿让 javaScript 摇身一变,有了新的用武之地,同时在nodejs推进下的 CommonJS 模块体系也是逐步深入人心。
- 经由历程 require 就能够引入一个 module,一个module经由历程 exports 来导出对外暴露的属性接口,在一个module内里没有经由历程 exports 暴露出来的变量都是相关于module私有的
- module 的查找也有肯定的战略,经由历程一致的
package.json
来举行 module 的依靠关联设置,require一个module只须要require package.json内里定义的name即可
同时,nodejs也定义了一些体系内置的module轻易举行开辟,比方简朴的http server
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的这一套推向浏览器?
抱负很饱满,然则实际倒是不尽善尽美的
一个最大的题目就是在浏览器加载剧本生成不支撑同步的加载,没法经由历程文件I/O同步的require加载一个js剧本
So what ? CommonJS 中逐步破裂出了 AMD,这个在浏览器环境有很好支撑的module范例,个中最有代表性的完成则是 requirejs
AMD
正如 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.
翻译过来就是说:异步模块范例 API 定义了一种模块机制,这类机制下,模块和它的依靠能够异步的加载。这个异常适合于浏览器环境,由于同步的加载模块会对机能,可用性,debug调试,跨域接见发作题目。
确切,在浏览器环境下,AMD有着本身奇特的上风:
由于源码和浏览器加载的一致,所见即所得,代码编写和debug异常轻易。尤其是在多页面的web项面前目今,差别页面的剧本js都是依据依靠关联异步按需加载的,不必手动处置惩罚每一个页面加载js剧本的状况。
然则,AMD 有一个不能不认可的作为一个module system的不足之处
叨教?在 AMD(requireJS)内里怎样运用一个第三方库的?
平常都邑阅历这么几个步骤:
- 运用的第三方库不想成为 global 的,只要援用的地刚刚可见
- 须要的库支不支撑 AMD ?
- 不支撑 AMD,我须要 fork 提个 patch 吗?
- 支撑AMD,我的项目根门路在哪儿?库在哪儿?
- 不想要运用库的悉数,要不要设置个 shim?
- 需不须要设置个 alias ?
一个库就须要问这么些个题目,而且都是人工手动的操纵
最最症结的题目是你辛辛苦苦搞定的设置项都是相关于你当前项目的
当你想用在其他项目或许是单元测试,那末OK,你还得修正一下
由于,你相对的是当前项目的根门路,一旦根门路发作转变,统统都发作了变化
requireJS 运用之前必需设置,同时该设置很难重用
相比较于 CommonJS 内里假如要运用一个第三方库的话,仅仅只须要在 package.json 内里设置一下 库名和版本号,然后npm install一下以后就能够直接 require 运用的体式格局,AMD 的处置惩罚几乎弱爆了 !!!
关于 AMD 的这个不足之处,又有社区大神提出了能够在 browser 运转的 CommonJS 的体式格局,而且经由历程模块定义设置文件,能够很好的举行模块复用
比较着名的就有 substack 的 browserify, tj 曾主导的 component,另有厥后的 duo,webpack,时期就转眼进入了 browser 上的 CommonJS
CommonJS in browser
由于 CommonJS 的 require 是同步的,在 require 处须要壅塞,这个在浏览器上并没有很好的支撑(浏览器只能异步加载剧本,并没有同步的文件I/O),CommonJS 要在 browser 上直接运用则必需有一个 build 的历程,在这个 build 的历程里举行依靠关联的剖析与做好映照。这里有一个典范的完成就是 substack 的 browserify。
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 有几个特征:
- 编写和 nodejs 一样的代码
- 在浏览器直接运用 npm 上的 module
为了能让browser直接运用nodejs上的module,browserify 内置了一些 nodejs module 的 browser shim 版本
比方:assert,buffer,crypto,http,os,path等等,详细见browserify builtins
这模样,browserify就处理了:
- CommonJS在浏览器
- 前后端代码复用
- 前端第三方库运用
component
component 经由历程 component.json 来举行依靠形貌,它的库治理是基于 github repo的情势,由于举行了显现的设置依靠,它并不须要对源码举行 require 关联剖析,然则时刻须要编写 component.json 也使得开辟者异常的痛楚,开辟者更愿望 code over configuration 的情势
duo
所以有了 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);不需用设置文件举行形貌,直接内嵌在代码内里
- 支撑源码transform,比方支撑 Coffeescript 或许 Sass
webpack
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 vs AMD
这永远是一个话题,由于谁也没法很好的庖代谁,尤其在浏览器环境内里,二者都有本身的长处和瑕玷
CommonJS
- 长处:简约,更相符一个module system,同时 module 库的治理也异常轻易* 瑕玷:浏览器环境必需build才运用,给开辟历程带来不方便
AMD
- 长处:生成异步,很好的与浏览器环境举行连系,开辟历程所见即所得
- 瑕玷:不怎样简约的module运用体式格局,第三方库的运用时的反复烦琐设置
dependency injection
前面提到的 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的instanceservice 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
一个优异的dependency injection container须要有下面这些特征:
- 无侵入式,与container之间的形貌不是显现经由历程container api挪用而是经由历程设置
- code over configuration,设置最好是内嵌于code的,自形貌的
- 完成异步剧本加载,由于已形貌了依靠关联,那末就无需蛋疼的再经由历程别的门路来处置惩罚依靠的剧本加载
- 代码能够前后端直接复用,能够直接援用,而不是说经由历程复制/粘贴而来的复用
- 在container之上完成别的,比方AOP,一致性设置,代码hot reload
这实在就是 bearcat 所做的事儿
bearcat 并非完成了 service locator 形式的module system,它完成了 dependency injection container,因而bearcat能够很好的与上面提到的种种CommonJS或许AMD连系运用,连系本身的上风来编写弹性、延续可保护的体系(运用)
bearcat
bearcat 的一个理念能够用下面一句话来形貌:Magic, self-described javaScript objects build up elastic, maintainable front-backend javaScript applications
bearcat 所提倡的就是运用简朴、自形貌的javaScript对象来构建弹性、可保护的前后端javaScript运用
固然能够有人会说,javaScript内里不仅仅是对象,还能够函数式、元编程什么的,实在也是要看运用场景的,bearcat更适合的场景是一个多人合作的、须要延续保护的体系(运用),假如是疾速开辟的剧本、东西、库,那末则该怎样简朴、怎样轻易,就怎样来
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的一个依靠 - 经由历程 bearcat.module(Function) 来把module注册到bearcat container中去
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 + browserify
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依靠治理都不是圆满的
应人而异,各取所需