第六章:依赖管理《Developing JavaScript Web Applications》学习笔记

为什么要依赖管理

没有依赖管理,代码会变得混乱不堪,如下:

<script src="jquery.js" type="text/javascript" charset="utf-8"></script>
<script src="jquery.ui.js" type="text/javascript" charset="utf-8"></script>
<script src="application.utils.js" type="text/javascript" charset="utf-8"></script>
<script src="application.js" type="text/javascript" charset="utf-8"></script>
<script src="models/asset.js" type="text/javascript" charset="utf-8"></script>
<script src="models/activity.js" type="text/javascript" charset="utf-8"></script>
......

依赖管理系统除了能解决实际的编程复杂度和可维护性的问题,还能解决性能方面的问题。浏览器需要针对每个JavaScript 文件都发起一个HTTP 请求,尽管可以将这些请求放入异步队列,但大量的HTTP 连接总会造成性能的下降,每个连接都包含额外的HTTP 头信息、Cookie,并都要做TCP 的三次握手。

通过CommonJS管理依赖(服务端JS)

CommonJS是服务器端JS的模块管理标准。声明CommonJS 模块非常简单、直接。命名空间的概念也被融入在内。模块被分隔为不同的文件,通过给exports 对象添加内容来对外暴露模块的变量和方法,exports 变量是在解释器中定义好的:

// maths.js
exports.per = function(value, total) {
return( (value / total) * 100 );
};
// application.js
var Maths = require("./maths");

客户端JS呢?

Commonjs的API设计其实很像python等语言。如果使用在nodejs等服务器上,是不会有什么问题的。但是如果在浏览器端使用commonjs,将会产生很大的局限性,根本在于require函数只能同步运行。

如application.js文件所示,js解析器运行到require("./maths");的时候,是需要向后端请求maths.js的文件的。同时,这里没有使用js的异步回调,在require之后直接获取了maths的per方法,必须使用一次同步的请求。如果程序运行在服务器,文件请求都是本地获取,那么这几个同步的请求其实对性能是没有什么影响的。但是在浏览器端就存在根本的差异了:一方面,浏览器必须请求application.js,加载完成并执行到require("./maths");的时候再去串行的请求maths.js,同时进程只能挂起等待请求的结束,再继续下面的脚本;另一方面,同步的请求不仅会住后面的请求队列,同时浏览器的渲染进程也同时进度了等待状态。这样我们的浏览器可能就得等所有的同步请求串行的加载结束之后才能开始渲染页面。这对于一个良好体验的因特网应用是不能容忍的。

那么,如何在客户端JS中也实现CommonJS 呢?CommonJS 团队为此提出了一个规范:“模块转换格式”。这种转换格式将CommonJS 的模块包装在一个回调函数中,以便更好地处理客户端的异步加载。
我们对上一个例子做一些改进。将它包装进一个“转换格式”里,以启用异步加载,这样就可以完美支持浏览器了:

// maths.js
require.define("maths", function(require, exports){
exports.per = function(value, total) {
return( (value / total) * 100 );
};
});
// application.js
require.define("application", function(require, exports){
var per = require("./maths").per;
assertEqual( per(50, 100), 50 );
}), ["./maths"]); // 给出它的依赖 (maths.js)

关于这种回调异步加载机制的详细内容,可以参考https://github.com/jayli/sandbox

模块加载器

为了在客户端使用CommonJS 模块,我们需要引入模块加载器类库。可选的类库有如下:

  1. SeaJs--https://github.com/seajs/seajs
  2. Yabble--https://github.com/jbrantly/yabble
  3. RequrieJS--https://github.com/jrburke/requirejs

个人推荐seaJs 此处不再详细介绍

包装模块

现在我们有了管理模块依赖和命名空间的方法,但仍然有一个问题自始至终都没有解决——每个文件都独占一个HTTP 请求。我们依赖的所有模块都从远程加载,尽管是以异步的形式,还是会造成很严重的性能问题——将应用的启动时间推后。
包装模块的工具:https://github.com/maccman/rack-modulr

标签: 依赖管理

添加新评论