文章源自:http://www.diguage.com/archives/82.html
Javascript模块化编程(三):模块化编程实战,试用SeaJS
前段时间转载了阮一峰老师的两篇讲解Javascript模块化编程的文章:“JavaScript模块化编程(一):模块原型和理论概念详解”,介绍了Javascript模块原型和理论概念;Javascript模块化编程(二):模块化编程实战,require.js详解,介绍了在实战中,如何利用RequireJS库,进行模块化编程。
在这两篇文章发布出来之后,在和网友的交流讨论中,了解到了SeaJS,这个由国人玉伯自己创建的模块化编程库。然后,我就想学习学习,再写篇文章给大家介绍一下。
背景介绍
官网的资料是最靠谱的。在SeaJS的官网上发现,有一个“5分钟上手SeaJS”的例子,然后就从这个例子的开始学习。不过,只看明白了个六六七七。我没看明白和我平时的JavaScript编程有啥区别。另外,我没有动手实践,心里面不踏实。所以,动手写个程序,玩味一下。后来,就想到了“猜手机号游戏”!
由于官网已经有使用SeaJS的教程,我就不重复这方面的工作了,而且我也觉得我我肯定没有官网写的好。由于我不清楚,使用SeaJS进行“模块化编程”和我平时不进行“模块化编程”的区别。所以,我准备从另外一个角度来介绍SeaJS:将一个没有进行模块化编程的程序,改造成使用SeaJS进行“模块化编程”的程序。由于这个想法的跨度比较大,信息量也比较多。所以我把我的想法组织成了三篇文章:第一篇文章,“给哥三十五次机会,哥就能猜中你的手机号”,通过一个小游戏,来吸引大家的兴趣;第二篇,“‘猜手机号游戏’的源码分析:二分查找+面向对象”,来讲解在没有进行模块化编程时,程序的实现细节;然后,这是第三篇,在没有进行模块化编程的基础上,将原来的程序改造成一个使用SeaJS进行模块化编程的例子。
在阅读这篇文章之前,请阅读前两篇,尤其是“‘猜手机号游戏’的源码分析:二分查找+面向对象”。同时,还建议阅读一下“JavaScript模块化编程(一):模块原型和理论概念详解”和Javascript模块化编程(二):模块化编程实战,require.js详解,规范、系统一下关于Javascript模块化编程的知识。
CMD模块定义规范介绍
想享受模块化编程带来的良好封装,就必须遵循模块化编程的规范。在 SeaJS 中,所有 JavaScript 模块都遵循 CMD(Common Module Definition) 规范。该规范确定了模块的基本书写格式和基本交互方式。所以,使用SeaJS之前,必须阅读一下SeaJS所要求遵循的规范。
鉴于规范覆盖的东西比较多,看多了头大。所以,我把这个规范提炼简化一下,只关注我们需要用到的。至于,更详细的CMD模块定义规范,等先把例子跑通,理解了整个流程,然后再回头看规范,梳理、规范这部分知识。
在介绍简化版规范之前,D瓜哥提两个也许大家都回“纳闷”的问题:
- 如何定义模块?
- 如何获取外部依赖的模块?
CMD模块定义规范中的主要内容正是回答这两个问题。下面请看经D瓜哥简化的规范如下:
- 定义、封装模块的方法。(CMD模块定义规范中有好多定义方法。简单起见,目前只考虑使用如下这一种方式。)如下:
define(function(require, exports, module) { // The module code goes here });
这里需要特别说明一下,向参数传递的三个参数名必须按照代码所示中那样写,不能简写,或者使用其他字符串代替;同时,在函数 内,exports不能被改写成其他值;可以把exports看成对象添加属性,如exports.key,然后对其复制,又如exports.key = “dValue”。
- 对外提供模块接口。在上一步中,我们在函数内定义了模块,但是这是在函数内定义的,在函数外部不容易访问到。该怎么向外提供模块接口呢?定义方法如下:
define(function(require, exports, module) { // 实用这种方式向外提供模块接口 module.exports = { foo: 'bar', doSomething: function() {}; }; // 或者。由于,D瓜哥将模块封装成了一个对象,所以,本例中,使用这个方式。 module.exports = yourFunctionName; });
传给 factory 构造方法(就是define(function(){})方法参数中那个函数,称为factory。函数只是factory的一种形式,其他形式以后再补 充。)的 exports 参数是 module.exports 对象的一个引用。只通过 exports 参数来提供接口,有时无法满足开发者的所有需求。 比如当模块的接口是某个类的实例时,需要通过 module.exports 来实现。D瓜哥这里就是一个对象,所以只能使用module.exports 。
- 获取外部依赖模块。模块定义要了,需要使用的时候,就可以使用require函数获取外部依赖。具体代码如下:
define(function(require, exports, module) { // 使用require函数获取外部依赖 var a = require('./a'); a.doSomething(); });
require函数的参数是a.js文件的相对路径。后缀名可以省略,在SeaJS加载模块的时候会自动加上的。另外,这里可以执行回调函数。不过,我们的任务是跑起来。因为不需要回调函数,所以这部分先略过了。
总结一下:define函数,定义模块;module对象,保存模块信息;require函数,获取外部依赖模块。
看到这里,估计大家还是一头雾水。没关系,慢慢往下看,下面的例子跑起来的时候,你再回头看就会明白的。
模块化改造
先声明一下,下面的改造过程会参考“5分钟入门”的说明。所以,建议大家先看看。当然,一起看也可以。
通过看”5分钟入门”的例子可以看出,SeaJS的目录结构还是有点复杂的。所以,最简单的方法就是,把她的例子下载下来,在她的基础之上修改:“5分钟入门”例子下载。
目录结构
下载完成后,解压到任意目录下。请看一下目录,
- hello-seajs/下放我们的html文件;
- hello-seajs/assets/sea-modules下存放的是我们需要用到的第三方模块块;
- hello-seajs/assets/main,这个目录可以说最重要,是存放我们自己编写的JavaScript和CSS文件的地方。下面还有四个子目录及一个文件:
- src存放正常的代码;
- test存放测试代码;
- docs存放文档;
- examples存放示例代码;
- package.json是打包的配置文件;
“改造”模块代码
下面,我们开始改造我们的模块。
首先,把我GuessNumber.js放到hello-seajs/assets/main/src/下。然后,按照“第1条规范”的要求改 造这个文件中代码。由于整个文件就是GuessNumber对象的定义。同时,这个JavaScript文件又没有引用其他模块。所以,只需要在文件的第 一行增加define,在最后一行增加括号分号就行。具体代码如下:
define(function(require, exports, module){ /** * numberScope 需要猜测的数字范围 */ function GuessNumber(numberScope){ // 为了突出修改的代码,我把一些相同的代码省略了, // 完整代码请看:http://www.diguage.com/archives/80.html } GuessNumber.prototype = { constructor: GuessNumber, // 完整代码请看:http://www.diguage.com/archives/80.html } });
其次,目前我们已经定义为一个模块。但是外部如何访问这个GuessNumber?所以,我们要向外部提供一个接口,提供方式参考“第2条规范”。具体代码见第18行:
define(function(require, exports, module){ /** * numberScope 需要猜测的数字范围 */ function GuessNumber(numberScope){ // 完整代码请看:http://www.diguage.com/archives/80.html } GuessNumber.prototype = { constructor: GuessNumber, // 完整代码请看:http://www.diguage.com/archives/80.html } module.exports = GuessNumber; });
这时,一个接口已经全部定义完成。下面,我们书写调用这个模块的例子。
在“规范”的第三条中,我们说明了加载外部依赖模块的方法,我们只需要按说明照做就行。另外,还需要补充一下模块加载时需要注意的地方。具体请看代码注释:
define(function(require) { // 这是引入jQuery类库,我们下面说明为什么这样下。 var $ = require('jquery'); // 引入GuessNumber模块,也就是GuessNumber.js文件。 // 参数中传递的是GuessNumber.js文件的相对路径。 // .js的后缀名可以省略,SeaJS在加载的时候会自动加上。 var GuessNumber = require("./GuessNumber"); // 完整代码请看:http://www.diguage.com/archives/80.html //格式化显示结果 function formatResult(num, type) { //…… } // …… $("#initButton").click(function(){ guess.start(scopeArr[type].min, scopeArr[type].max); showResult(); }); });
从上面的代码中,可以看出,main.js文件的改造,只是把原来的
$(document).ready(function(){ // 主要的业务代码 });
改造成了,
define(function(require) { // 这是引入jQuery类库,我们下面说明为什么这样下。 var $ = require('jquery'); // 引入GuessNumber模块,也就是GuessNumber.js文件。 // 参数中传递的是GuessNumber.js文件的相对路径。 // .js的后缀名可以省略,SeaJS在加载的时候会自动加上。 var GuessNumber = require("./GuessNumber"); // 和原文件相同的业务代码 });
另外,加了两行倒入必要关联模块的代码。仅此而已。
main.js与GuessNumber.js不同的还有一点,main.js不需要向外提供访问接口。这点也要注意一下。
到这里所有的JavaScript都已经修改完毕了。下面,我们修改一下如何在HTML中的引入方式。
在页面中加载模块
原来的写法是,按顺序使用<scrip>标签把jQuery、GuessNumber.js以及main.js文件引入到HTML 页面中即可。如果使用SeaJS,则需要先加载SeaJS的类库,然后使用JavaScript通过SeaJS的接口来加载所需的模块,也就是模块对应的 JavaScript文件。具体代码如下:
<!-- 首先,首先我们需要引入 sea.js --> <script src="assets/sea-modules/seajs/1.3.1/sea.js"></script> <script type="text/javascript"> seajs.config({ alias: { // 指定使用的jQuery版本以及说明jQuery的路径 // 请注意:这里知名了jQuery的路径,所以,我们 // 在引入jQuery库时,只需要填写jquery即可。 'jquery': 'gallery/jquery/1.8.2/jquery' } }); // 然后SeaJS通过 use 方法来加载模块,以后打包后也是修改这里 // 也许你会疑问为什么不加载GuessNumber.js文件, // 这个在使用require引入依赖时,SeaJS自动加载需要的外部文件 // 另外,这里的.js后缀名也可以省略,SeaJS会自动补全。 seajs.use('./assets/main/src/main'); </script> <!-- 这里只展示了和JavaScript引入相关的代码 --> <!-- 完整代码请看:http://www.diguage.com/archives/80.html 中的HTML代码 -->
到此,改造工作就全部完成了。你可以打开一下inde.html文件,看看效果了。
打包部署
根据“高性能网站的十四条黄金法则”中的实践,我们在实际项目上线时,为了提高页面的加载速度,必定要压缩一下JavaScript文件。这些,SeaJS也考虑到了,甚至做得更好:还做了文件合并。
这里,需要先介绍一下,SPM,一个基于命令行的前端项目管理工具。 SPM 和 SeaJS 关系密切,你甚至可以认为SPM是为SeaJS专门打造的工具。首先,请“安装教程”安装好这个工具。按照过程可能会有一个问题,请参考下面的“出现的问题”。
使用SPM打包,需要修改一下打包的配置文件。配置文件是:hello-seajs/assets/main/package.json。打开后内容如下:
{ "name": "main", "version": "1.0.0", "dependencies": { "jquery": "gallery/jquery/1.8.2/jquery" }, "root": "hello-seajs", "output": { "main.js": ".", "main.css": "." }, "spmConfig": { "build": { "to": "../sea-modules/{{root}}/{{name}}/{{version}}" } } }
不过,这个需要根据我们的实际情况来修改。root属性,由于我们的模块是“猜数”,所以将其修改为GuessNumber;output属性,我们只需 要输出JS,所以删除main.css。另外,需要注意,第十四行,这个是打包后的输出路径。好了,开始打包。打包需要执行如下指令:
$ cd hello-seajs/assets/main $ spm build ... BUILD SUCCESS! $
打包结束后,在hello-seajs/assets/中就会发现多了一个GuessNumber文件夹,那个就是打包输出出来。
这里说明一下:D瓜哥只在Linux下执行了这么命令。不知在Windows是否好使。为了方便大家测试运行,打包结果已提交,下载的代码中包含打包结果。
观察这个结果,大家会发现只有一个main.js和main-debug.js;顾名思义,main.js是用于生产部署的,经过压缩的文 件;main-debug.js是为测试使用的,只是合并了代码并没有压缩,使用的时候直接引用这个两个文件中的一个就行,直接把seajs.use() 中的路径改一下就OK。GuessNumber.js哪里去了啊?大家可以打开main-debug.js看看(main.js也行,只是压缩过来,可读 性不好),原来,GuessNumber.js已经合并到了main.js中了。SPM把两个文件合并成一个文件了,这样在浏览器访问网页时,就可以减少 一个HTTP请求,提高网页的加载速度。
另外,大家也可能会注意到在原来main.js中定义的define()函数,在新的main.js有了一些变化,多了两个参数:第一个参数模 块的ID,主要是为了方便区别一个文件中的各个模块;第二个参数是模块依赖的外部模块的路径,因为依赖的模块可能有多个,所以这个参数是一个数组。第三个 参数是原来的function,也就是factory。更详细的解释请看:为什么要用 spm 来压缩 CMD 模块?
懒人要把懒进行到底!打包后还要修改SeaJS的加载路径,这点其实还可以使用如下代码来避免:
// 这个路径只有在部署到服务器上才行,直接打开文件不好使。 seajs.use(location.host === 'localhost' ? './assets/main/src/main' : 'GuessNumber/main/1.0.0/main');
如果是非静态页面,也可以使用变量来配置。
折腾中出现的问题
折腾这么个玩意,难免出现一些问题,D瓜哥遇到了三个问题。这些问题主要集中在SPM环境搭建过程中。给大家分享一下。
第一个问题:按照seajs时,提示info.json不存在的错误。终端显示如下:
d@dPC:~/Dev/hello-seajs/assets$ spm install seajs Start installing ... success create global config.json to /home/d/.spm Downloading: http://modules.spmjs.org/info.json [ERROR] Caught exception: Error: not found config http://modules.spmjs.org/info.json
大家可以在浏览器地址中打开http://modules.spmjs.org/info.json,会发现可以打开。这是怎么回事呢?
我查阅了一下SeaJS论坛,里面有类似的问题。其中的一个回复,我拿过来当作解答吧:这段时间是举国同庆的日子,网络不稳定。至于原因,你懂得。估计等过了这段时间就没事了。所以,既然浏览器可以访问,则内容就可以访问到。遇到这个问题,多试两次就可以了。
第二个问题:按照jquery库时,提示Error: ALREADY_EXISTS。终端显示如下:
d@dPC:~/Dev/hello-seajs/assets$ spm install gallery.jquery Start installing ... Downloading: http://modules.spmjs.org/gallery/info.json Downloaded: http://modules.spmjs.org/gallery/info.json Downloading: http://modules.spmjs.org/gallery/jquery/1.8.2/jquery.tgz Downloaded: http://modules.spmjs.org/gallery/jquery/1.8.2/jquery.tgz ** This module already exists: /home/d/Dev/hello-seajs/assets/sea-modules/gallery/jquery/1.8.2 Turn on --force option if you want to override it. [ERROR] Caught exception: Error: ALREADY_EXISTS
其实,问题正如反馈信息所示,jQuery库已经存在,不需要再次下载了。我们在hello-sea这里例子的源代码中构建,这个源代码中已经包含了jQuery了,在这里这步可以忽略。
第三个问题:修改了package.json后,重新编译报错。终端显示如下:
[WARN] http://modules.spmjs.org/GuessNumber/config.json null
这个不影响编译,直接忽略就行了。另外说明一下,在第一次打包时,没见这个错误;第二次会出现。
代码下载
为了方便大家下载代码,我把代码托管到了Github上,大家可以去Github上下载、提交您的修改。Github页面:GuessNumber;不想去Github上下载的,也可以直接点击下载:点击下载。
深入学习
上面的例子只是简要把一个例子跑起来了,给大家一个比较形象的认知。但是,这个例子实在是太简单了。我还需补充我们刚才为了易于理解而简化的一些知识。为了更深入的了解SeaJS,请继续阅读“SeaJS 使用文档”。另外,这里有几个需要重点阅读,具体如下:
把这个列表中的东西看完,SeaJS的学习应该就可以出师了。有好的资料请给我推荐,我再补充上来。
遗留问题
经过上面这些折腾,我们已经成功运行起来一个使用SeaJS进行模块化编程的例子。但是,我们还是有很多的疑问。具体疑问如下:
- D瓜哥在main.js中,并没有使用$(document).ready();等DOM加载完再运行,并也没有讲JS放到HTML文件的最后,为啥还能顺序执行呢?莫非SeaJS有什么内部机制,保证在DOM加载完成后再执行我们自己编写的JavaScript代码?
- 这里例子很小,并没有很多很多的模块。在模块很多的情况下,如果组织模块?这个还需要写更多的例子,实验一下。
- 同样,在很多模块的情况下,难道要建很多目录准备很多的main.js,让众多的HTML分别加载吗?
刚刚D瓜哥开窍了一下,main.js只是一个例子,可以根据自己的组件名称命名,然后在组件中加载相对应的JavaScript文件即可。另 外,在配置package.json时,突然觉得,在/assets/main/src/下每个目录应该算是一个模块,都有一个打包的配置文件 package.json,用于配置该模块的必要信息。不知这样理解是否正确?这个还有待考证。
相关推荐
### JavaScript模块化编程 #### 一、理解JavaScript模块化 ##### 1.1 模块化的概念 在软件工程中,模块化是一种重要的设计思想,指的是将复杂的系统分解成一系列独立的功能单元(即模块),每个模块负责实现系统...
SeaJS便是一个专注于JavaScript模块化开发的库,它提供了一种在浏览器端进行模块化编程的方式,类似于服务器端的CommonJS,但又针对浏览器环境进行了优化。 1. 模块化概念: 模块化是将代码按照功能划分为独立的...
本文将深入探讨JavaScript模块化的概念、问题以及几种常见的模块化规范。 **为什么需要JavaScript模块化** 1. **代码组织与复用**:模块化允许开发者将复杂的应用分解为独立的、可重用的单元,每个单元专注于特定...
CommonJS规范旨在促进JavaScript模块化,使得代码可复用性和可维护性得到显著提升。SeaJS 的出现,使得在前端开发中可以像在服务器端使用Node.js那样,方便地进行模块化的编程。 SeaJS的核心功能包括以下几个方面:...
这是自己编写的模仿seajs模块加载的模块加载器,用于学习交流之用。大致模仿seajs的模块化加载实现。
SeaJS是中国开源社区贡献的一款JavaScript模块加载框架,其主要目标是为Web开发提供一种遵循CommonJS规范的模块化解决方案。这个框架的版本v0.9.1是一个免费版,适用于各种项目开发,帮助开发者更有效地组织和管理...
Seajs是一款轻量级的JavaScript模块加载器,它遵循CommonJS规范,旨在解决浏览器环境中的模块化问题。在深入理解seajs源代码之前,我们首先需要了解模块化的基本概念和CommonJS规范。 模块化是软件开发中的一种组织...
在 JavaScript 开发中,模块化是一个关键的实践,它有助于提高代码的可读性、可维护性和复用性。SeaJS 正是这样一个工具,它遵循了 CommonJS 规范,允许开发者在浏览器环境中实现类似服务器端 Node.js 的模块化开发...
SeaJS 是一个用于浏览器端的 JavaScript 模块加载器,它旨在帮助开发者实现模块化的 JavaScript 编程。SeaJS 的出现,是为了解决 JavaScript 在大型项目中的组织和依赖管理问题,使得代码更加清晰、可维护,并且支持...
总结来说,SeaJS 是一个强大的JavaScript模块加载器,它遵循CommonJS规范,提供了模块化编程的支持,帮助开发者更好地组织和管理Web应用的JavaScript代码。通过异步加载、动态配置和丰富的插件系统,SeaJS 使得...
Seajs是中国开源社区非常受欢迎的一款JavaScript模块加载器,它的出现为Web开发引入了CommonJS规范,使得前端开发更加模块化,便于代码管理和维护。Seajs 2.3.0是该库的一个稳定版本,提供了丰富的功能和优化。 一...
1. **模块化编程**:Seajs基于CommonJS规范,引入了模块化编程的概念,允许开发者将复杂的JavaScript代码拆分为可重用的模块,降低了代码的耦合度,提高了代码的可维护性。每个模块都有自己的作用域,不会相互污染。...
Seajs 是一个轻量级的前端模块加载器,它的出现是为了在浏览器端实现 CommonJS 规范,使得 JavaScript 的组织和开发变得更加模块化。Seajs 版本 2.3.0 是其稳定的一个版本,提供了更完善的特性和优化。 在...
JS模块化标准技术.JAVASCRIPT的几种模块化技术 作为前端重要组成部分的javascript语言,其面向对象功能非常差.所以要实现代码的模块化,需要一些标准:AMD,CMD 针对2种模块化也有现成的js模块化库SeaJs,require.js 今天...
Seajs和ES Modules(ESM)是两种不同的模块加载规范,分别代表了JavaScript模块化发展的不同阶段。Seajs是早期广泛使用的CMD(Common Module Definition)规范的实现,而ES Modules则是现代浏览器和Node.js原生支持...
Seajs的核心理念是让JavaScript模块化变得简单,帮助开发者解决在大型Web项目中代码组织、依赖管理以及加载性能等问题。 ## Seajs的下载与安装 要在项目中使用Seajs,首先需要从官方网站或者其他可靠的源获取最新...
SeaJS是一个遵循CommonJS规范的JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制。使用SeaJS可以提高JavaScript代码的可读性和清晰度,解决目前JavaScript编程中普遍存在的依赖关系混乱和代码...
**SeaJS 和 RequireJS 是两种广泛使用的 JavaScript 模块加载器和依赖管理工具,它们的主要目的是解决 JavaScript 在浏览器端的异步加载和模块化问题。** **SeaJS** SeaJS 是一个轻量级的模块加载器,由中国淘宝...
JavaScript模块化是编程实践中将大型复杂项目分解为可管理的小块的重要方法。随着Node.js的出现,JavaScript模块化成为了一种普遍的实践,它允许开发者将代码组织成独立的模块,便于重用、测试和维护。CommonJS是最...