使用JSDoc3生成javascript项目的API文档

JSDoc样式的注释

选用JSDoc3作为注释规范,文档生成工具使用grunt-jsdoc,本文不再介绍选型过程和原因。对JSDoc3的深入学习使用,可以参考入门教程http://usejsdoc.org/index.html

JSDoc注释的样式如下例,与单行注释 // 和多行注释 /**/ 不同,而是类似于JAVA的JDoc和PHP的PHPDoc。

/**
* JSDoc注释。
*/

JSDoc的常用标签

函数注释示例

/**
* 函数注释的示例。
* @param {Integer} augend 被加数。
* @param {Integer} addend 加数。
* @return {Integer} 两数之和。
* @example
* add(1, 2) => 3
*/
function add(augend, addend){
    return augend + addend;
}

参数注释

@param 用于对函数、类的方法的参数进行注释,是JSDoc中最常用的注释标签。

@param 注释必须指定一个参数名,也可以有一个用大括号括起来的参数类型,以及参数的描述信息。参数类型可以是javascript内置的数据类型,如Array、Boolean、Date、Function、Number Object 、String 等,还可以是其他JSDoc所支持的(英文没看太懂,以后用到再认真学习)。

The parameter type can be a built-in JavaScript type, such as string or Object, or a JSDoc namepath to another symbol in your code. If you have written documentation for the symbol at that namepath, JSDoc will automatically link to the documentation for that symbol. You can also use a type expression to indicate, for example, that a parameter is not nullable or can accept any type; see the @type documentation for details.

下面直接使用http://usejsdoc.org/tags-param.html文档中的例子,说明 @param 标签中如何使用参数名、参数类型和参数描述信息。

只有参数名的注释:

/**
 * @param somebody
 */
function sayHello(somebody) {
    alert('Hello ' + somebody);
}

包括参数名和参数类型的注释:

/**
 * @param {string} somebody
 */
function sayHello(somebody) {
    alert('Hello ' + somebody);
}

包括参数名、参数类型和参数描述的注释:

/**
 * @param {string} somebody Somebody's name.
 */
function sayHello(somebody) {
    alert('Hello ' + somebody);
}

返回值注释

@return 说明函数的返回值。例如:

  • @return {Number}
  • @return {Number} Sum of a and b
  • @return {Number|Array} Sum of a and b or an array that contains a, b and the sum of a and b.

参见:http://usejsdoc.org/tags-returns.html

模块注释

@module标签用于标记当前代码文件属于哪个模块。

@link@see 标签中,使用 module:moduleName 可以链接到一个模块。例如,使用 {@link module:foo/bar},可以链接到由 "@module foo/bar 定义的模块。

如果没有提供模块名,将使用模块文件所在路径及文件名作模块名。

参考引用注释

@see 标签可以指向一个相关的引用。示例如下:

/**
 * Both of these will link to the bar function.
 * @see {@link bar}
 * @see bar
 */
function foo() {}

// Use the inline {@link} tag to include a link within a free-form description.
/**
 * @see {@link foo} for further information.
 * @see {@link http://github.com|GitHub}
 */
function bar() {}

类的注释

  • @name :类名称
  • @class :类描述
  • @constructor :表明这是一个构造函数,非常重要。
  • @extends :类继承的父类。
  • @type :数据的类型,主要用来注释属性。
  • @default :默认值,主要用来注释属性。
  • @abstract 标明一个成员是抽象的,需要子类去实现。
  • @public@protected@private:类、方法或属性的访问权限

类的注释示例

要把JSDoc3的注释生成API类库文档,@lends 是个很要紧的标签。

例如,@lends Sample.prototype 表示下面的对象归属于Sample。

较详尽的使用说明参见http://usejsdoc.org/tags-lends.html

有关类的定义和注释示例,如下:

/**
* @name Sample
* @class 示例类
* @public
* @constructor
*/
function Sample(){
    this.something = [];
}

Sample.prototype = 
/** @lends Sample.prototype*/
{
    /**
    * 属性示例。
    * @private
    * @type {Array}
    * @default []
    */
    something : [],

    /**
    * 方法示例。
    * @public
    * @param {String} arg 跟踪方法插件。
    */
    doSomething: function(arg){
    }
};

其他常用注释

  • @example : 示例代码。

  • @enum [<type>] : 一组同样类型的静态属性集合。switch 语句中的分支应该只使用枚举。

  • @overview :对当前代码文件的描述。

  • @copyright :代码的版权信息。

  • @author <name> [<emailAddress>] :代码的作者信息。

  • @version :当前代码的版本。

README.md 是很好的

如果你有为项目写说明文档的好习惯,碰巧又使用的是MarkDown格式,碰巧文件名又是README.md,那就很好了。

把README.md文件放在代码清单里边,JSDoc工具是自动为你生成API文档的首页,什么系统概况、设计需求、设计方案及版本更新记录等等的内容都可以放进来。

Grunt自动构建的配置

Grunt的安装使用,请参考教程《Grunt的安装和使用》

提示:JSDoc某些配置有git依赖, 需要在命令行中可以执行git命令。最好先安装一个msysgit(http://msysgit.github.io/),然后在环境变量中增加git的bin目录。

使用JSDoc3插件,在package.json中的NPM依赖配置参考,如下:

"devDependencies": {
    ... ...
    "grunt-jsdoc": "~0.5.4",
    "ink-docstrap": "~0.3.0",
    ... ...
}

使用JSDoc3插件,在Gruntfile中的配置参考,如下:

jsdoc: {
  dist : {
      src: ['README.md', 'src/sample.js'], 
      options: {
        destination: 'api',
        template: "libs/jsdoc3/docstrap/template",
        configure: "libs/jsdoc3/docstrap/template/jsdoc.conf.json"            
      }
  }
}

详细使用方法和参数,参考:https://github.com/krampstudio/grunt-jsdoc-plugin

参考资料

Grunt的安装和使用

安装Grunt前的准备

本教程的内容较少原创,多从其他文档上摘录,详见参考资料部分。

Grunt是一个自动化的项目构建工具。Grunt和Grunt的插件都是通过Node.js的包管理器npm来安装和管理的。

如果还没有安装Node.js,需要先从Node.js网站(http://nodejs.org/)下载安装。

Node.js安装中,以及后继的其他安装中,可能需要系统管理员权限。

安装Grunt之前,可以在命令行中运行node -v查看你的Node.js版本。0.8.0及以上版本的Node.js才能很好的运行Grunt。

安装Grunt的命令行接口

为了方便使用Grunt,你应该在全局范围内安装Grunt的命令行接口(CLI)。

在命令行中,执行如下命令:

npm install -g grunt-cli

这条命令将会把grunt命令植入到你的系统路径中,这样就允许你从任意目录来运行它(定位到任意目录运行grunt命令)。

认识Grunt的两个常用文件

使用Grunt构建的项目中,大多包含有package.json和Gruntfile。

package.json:用于存储已经作为npm模块发布的项目元数据(也就是依赖模块,包括Grunt本身)。

Gruntfile.js:用于配置或者定义Grunt任务和加载Grunt插件。

用一个现有的Grunt项目进行工作

如果已经安装好了Grunt的命令行接口(CLI),并且项目中已经有了package.json和Gruntfile,使用Grunt进行工作是非常容易的。

安装Grunt和相关Grunt插件

  1. 进入项目目录(在命令行窗口定位到项目目录;在windows系统下,也可以进入项目文件夹后,按Shift+鼠标右键,打开右键菜单,选择“在此处打开命令窗口(W)”)。
  2. 在命令行中,运行 npm install,安装项目相关依赖(插件,Grunt内置任务等依赖)。

有些Grunt插件,比如JSDoc3插件,需要在命令行中可以执行git命令。最好先安装一个msysgit(http://msysgit.github.io/),然后在环境变量中增加git的bin目录。

执行Grunt构建

在上述的命令行中执行 grunt,就可以运行Grunt构建了。

至此,通过安装Node.js、在命令行中运行 npm install -g grunt-clinpm installgrunt三个命令,就完成了使用现有的Grunt项目进行工作的所有准备。

准备一个新的Grunt项目

快速工作的项目脚手架

有很多项目的结构和内容都很类似,人们就设计开发了可以快速工作的模板,能够通过模板迅速的自动创建项目,称为脚手架工具(grunt-init)。

在Grunt的GitHub主页(https://github.com/gruntjs)上有很多之用的 grunt-init-* 脚本架模板工具,比如grunt-init-jquery、grunt-init-gruntplugin等等。

安装grunt-init

grunt-init 是一个用于自动创建项目的脚手架工具。

先在命令行中进行全局安装:

npm install -g grunt-init

这样,会把grunt-init命令植入到你的系统路径,从而允许你在任何目录中都可以运行它。

安装和使用grunt-init模板

把需要的模板放在你的 ~/.grunt-init/ 目录中(在Windows平台是%USERPROFILE%/.grunt-init目录)。

%USERPROFILE% 一般是指“C:\Users\你的用户名”这个目录。

建议使用如下的git clone命令把这个模板克隆到该目录:

git clone https://github.com/clientlab/grunt-init-gruntfile.git ~/.grunt-init/gruntfile

在Windows平台上稍微有些不方便,但是可以在文件浏览器中输入%USERPROFILE%,定义到该目录下,然后执行如下命令:

git clone https://github.com/clientlab/grunt-init-gruntfile.git ./.grunt-init/gruntfile

要想使用上述命令,需要先安装一个msysgit(http://msysgit.github.io/),然后在环境变量中增加git的bin目录。

最终的文件目录结构,如下:

.grunt-init/
.grunt-init/gruntfile/
.grunt-init/gruntfile/README.md
.grunt-init/gruntfile/template.js
.grunt-init/gruntfile/root/

然后进入一个准备开发项目的空目录,按Shift+鼠标右键,打开右键菜单,选择“在此处打开命令窗口(W)”,打开命令行,执行如下程序:

grunt-init gruntfile

请注意,此模板将在当前目录中生成文件,如果你不想覆盖现有文件,一定要使用一个新的空目录。

参考资料

使用grunt-run-grunt插件组织团队的开发

项目构建的需求

在组织项目的开发工作时,如果构建工具选型是Grunt,那么常常有以下几个需求:

  1. Gruntfile文件随着项目复杂性的增加,会变的越来越庞大。为了便于开发团队的分工协作,需要在某种程度上分拆Gruntfile,让不同的团队、不同的开发者关注不同的部分。
  2. 把Gruntfile分拆成不同的文件后,既要有统一的入口执行整体的构建,又要能够使分拆后的Gruntfile不依赖于其他部分独立构建,以确保团队分工的互不干扰。
  3. 在整个项目中,package.json和node_modules要能够共用。确保每一部分的开发者,只关注Gruntfile的编写,不需要关注全局共性的package.json和node_modules。

不同的团队、 不同的项目因为细化需求的不同,都会各自选择不同的解决方案。

Gruntfile的分拆方案

Gruntfile文件内的逻辑分组

使用任务的目标名称,在逻辑上分组构建任务。这是大多数项目最初会用到的方法,如grunt中文文档项目(https://github.com/basestyle/grunt-cn)

在传递给grunt.initConfig方法的对象中,使用如下类似的不同任务目标名称区分webapp和pc两类任务:

jshint:{
    webapp: ['app/www/js/main.js'],
    pc: ['js/chart.js','js/common.js','js/demand.js','js/dialog.js','js/formbeautify.js','js/jquery.autopagination.js',
        'js/jquery.cascadeselect.js','js/jquery.datepicker.js','js/jquery.formvalid.js','js/jquery.memberinput.js',
        'js/jquery.multiupload.js','js/jquery.tabs.js','js/pmstation.js','js/project.js','js/setting.js','js/tasktable.js','js/work.js']
},

在自定义的别名任务中,把两类任务分组成webapp和pc两个单任务:

grunt.registerTask('webapp', ['htmlhint:webapp','jshint:webapp','uglify:webapp','cssmin:webapp','copy:webapp','replace:webapp']);
grunt.registerTask('pc', ['jshint:pc','uglify:pc','cssmin:pc','copy:pc','replace:pc']);

如果两类任务调用的任务都相同,也可以使用动态别名任务的方法来处理。

代码示例如下:

grunt.registerTask('build', 'Run all my build tasks.', function(n) {
  if (n == null) {
    grunt.warn('Build num must be specified, like build:001.');
  }
  grunt.task.run('foo:' + n, 'bar:' + n, 'baz:' + n);
});

参见Grunt文档的《常见问题-“动态”别名任务》(中文/英文)。

Grunt配置编程处理在不同的文件中

将配置对象分拆出来,然后在Gruntfile.js中require进来,再将这些不同任务的对象组合成一个对象传递给grunt.initConfig。

上述方案是在我提出问题(有没有分拆Gruntfile.js的方案?(https://github.com/basestyle/grunt-cn/issues/34))后,TooBug[GitHub]和Vincent Hou给出的解决方案。

因为没有进一步展开,所以没有示例代码。

使用自定义任务把构建任务分解在不同的文件中

我在知乎上提出问题《有什么方案可以把较为庞大的gruntfile分拆?(http://www.zhihu.com/question/21766711)》后,墨磊[GitHub]给出的解决方案,基本上就是使用自定义任务把构建任务分解在不同的文件中的。

比如,在gruntjs.com(https://github.com/gruntjs/gruntjs.com)项目中,也是通过把blog、docs等任务从Gruntfile中分拆出来的。

代码示例如下:

// Load local tasks
grunt.loadTasks('tasks'); // getWiki, docs tasks

grunt.registerTask('build', ['clean', 'copy', 'jade', 'docs', 'blog', 'plugins', 'concat']);

上例中的docs、blog任务就是放在tasks目录下,使用grunt.loadTasks方法统一加载的。

选择grunt-run-grunt插件

前文的所述的几个方案,用于分拆Gruntfile虽然很不错,但是并不能解决最初提出的后两个需求:

  • 分拆后的Gruntfile,能够不依赖于其他部分的独立构建。
  • package.json和node_modules要能够共用。

我发现grunt-run-grunt(https://github.com/Bartvds/grunt-run-grunt)插件可以同时满足这三个需求。

很遗憾的是,这个插件关注的人不多,Fork的人也不多。或许其他的开发团队有更高明的方案也说不一定,还需要继续跟进。

项目的组织方案

在选型定案使用grunt-run-grunt插件以后,项目的组织就确定了。

入口Gruntfile的代码示例:

run_grunt:{
  options: {
    'base': require('path').resolve('.')
  },
  main:{
    src: ['src/pro/a.Gruntfile.js', 'src/frameworks/b.lib.Gruntfile.js', 'src/frameworks/c.lib.Gruntfile.js']        
  }
}

需要特别注意的是,options中的base配置是指整个项目所有的Gruntfile都是以这个入口Gruntfile所在的目录为base目录的。

这个扩展机制可以在options中配置最终传递给grunt的命令行参数,属性名是命令行参数名,属性值是命令行参数的值。

上例配置的意义是要求执行类似如下样式的Grunt命令:

grunt --base e:/dev/

各部分的Gruntfile与正常的Gruntfile没有什么区别。唯一的区别就是,这个方案把所有的Gruntfile的base目录设定为入口Gruntfile所在的目录。

比如,src/pro/a.Gruntfile.js中的代码示例如下:

concat: {
  "pro: {
    src: [
      "src/frameworks/b.lib.js",
      "src/frameworks/c.lib.js",
      "src/pro/a.js"
    ],
    dest: "dist/pro/a.js"
  }
}

示例中,不管是src目录,还是dest目录,都是以入口Gruntfile目录为基准的。

当然,base目录不这样安排是最简单的,在入口Gruntfile的run_grunt任务中不配置base就可以了。如此以来,所有的Gruntfile中的目录都是以本Gruntfile所在目录为base目录的。

在javascript项目中使用QUint进行单元测试

QUnit的简介

本文只介绍Qunit的使用,不再介绍单元测试框架的选型过程。

QUnit的官网介绍:

QUnit is a powerful, easy-to-use JavaScript unit testing framework.

QUnit是一个强大的、简单易用的javascript单元测试框架。

下载QUnit

可以到QUnit官网(http://qunitjs.com)上下载qunit.js和qunit.css两个文件。

虽然有jQuery CDN(http://code.jquery.com/)GitHub托管(https://github.com/jquery/qunit)的代码可用,但是为了稳定,还是下载了使用吧。

QUnit上手应用

以下的代码示例直接使用QUnit官网首页的示例。

QUnit单元测试的HTML页面代码示例:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>QUnit Example</title>
  <link rel="stylesheet" href="/resources/qunit.css">
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
  <script src="/resources/qunit.js"></script>
  <script src="/resources/tests.js"></script>
</body>
</html>

tests.js中的示例代码:

test( "hello test", function() {
  ok( 1 == "1", "Passed!" );
});

QUnit的常用API

断言是单元测试中最常用、最核心的概念,就是在测试的时候,对代码的运行结果作出的一些假设。如果断言成立,就说单元测试通过,反之则视为测试失败。

测试方法和模块

最常用的测试代码,示例如下:

test("该测试用例的主题", function(){/*- 具体的测试断言 -*/});

当测试用例较多时,可以用module()方法进行分组。示例如下:

module( "测试分组一" );
test( "测试方法1", function() {});
test( "测试方法2", function() {});
 
module( "测试分组二" );
test( "测试方法3", function() {});
test( "测试方法4", function() {});

在测试结果HTML页面上,会以 测试分组一:测试方法1 的形式呈现。

异步测试

javascript编程中,经常用到回调函数,所以异步测试也是经常用到的。

如下示例:

asyncTest( "asynchronous test: one second later!", function() {
  expect( 1 );
 
  setTimeout(function() {
    ok( true, "Passed and ready to resume!" );
    start();
  }, 1000);
});

注意start()函数的用法。

由于是异步调用,所以使用expect()告诉QUnit期待几个断言是个好习惯。

QUnit支持的断言

一个测试方法只有一个断言是个好习惯。只是好习惯哦!

一目了然,ok和equal是最常用的

  • ok( state, message ) : 真假断言,state为true则通过。
  • equal( actual, expected, message ) : 相等断言,actual和expected相等(==)则通过。
  • notEqual( actual, expected, message ) : 不等断言,actual和expected不相等(!=)则通过。
  • deepEqual( actual, expected, message ) : 递归相等断言,actual和expected全相等(包括其子元素都相等,适用于基本类型,数组和对象)则通过。
  • notDeepEqual( actual, expected, message ) : 递归不相等断言,actual和expected不全相等(包括其子元素都相等,适用于基本类型,数组和对象)则通过。
  • strictEqual( actual, expected, message ) : 全相等断言,actual和expected全相等(===)则通过。
  • notStrictEqual( actual, expected, message ) : 不全相等断言,actual和expected不全相等(===)则通过。
  • raises( block, expected, message ) : 异常断言,block中抛出异常则通过,expected为可选参数,是所期待抛出异常名的正则表达式。

与Grunt构建工具的集成

Grunt构建工具的安装和使用,可以参考教程《Grunt的安装和使用》

QUnit的Grunt任务

QUnit Grunt任务(grunt-contrib-qunit)的使用,请参见GitHub上的说明https://github.com/gruntjs/grunt-contrib-qunit

使用QUint作单元测试,PhantomJS是必须要安装的。PhantomJS(http://phantomjs.org/) 是一个无界面的WebKit浏览器引擎,还有配套的JavaScript API。它原生支持各种web标准技术:DOM处理,CSS选择器,JSON,Canvas,以及SVG。

安装grunt-contrib-qunit任务时,作为npm依赖项,PhantomJS会自己被安装。

也可以在命令行中使用以下的命令进行安装:

npm install phantomjs -g

友情提示:

本来这一切都是顺理成章,非常流畅的。可惜,一道国墙给这个世界的互联互通制造了些许障碍。

如果电脑上从来没有安装过PhantomJS,或者当前的版本不够高,安装程序会自动从bitbucket网站上下载,例如:http://cdn.bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.7-windows.zip。但是,非常不幸,这个地址是被墙了的。

你可以从各种不同的途径,获取这个压缩包,然后拷贝到C:\Users\Administrator\AppData\Local\Temp\phantomjs(具体地址根据自己的电脑作调整,或者查看安装报错的信息)。接着,重新启动安装,一切都OK了!

参考资料

佛经

我念一段佛经

我念一段佛经,

祈祷人心的光明。

授你无相的戒律,

约束你的怨嗔。

摩挲你的头顶,

消融你的仇恨。

我念一段佛经,

超度不能转世的灵魂。

阴暗的井底,

冰冷的井水,

一样都是地狱的幽禁。

彻骨的惩罚,

不过是洗手的金盆。

天地轮回中,

从来没有不能原谅的人。

我念一段佛经,

张扬无边的悲悯。

槐树

路边的槐树

2013-07-31

老树沧桑洗礼后,

更比往日枝叶稠。

槐花满地残香殒,

曾见昨夜风雨骤。

十周年

2011-05-03

十年风雨十年路,

一地槐花一地香。

也爱桃花风中舞,

可恨行色太匆忙。