分类 技术笔记 下的文章

使用 JSDoc 3 自动生成 JavaScript API 文档

软件并不只是包括可以在计算机上运行的电脑程序,与这些电脑程序相关的文档,一般也被认为是软件的一部分。简单的说软件就是程序加文档的集合体。

随着项目的升级进化,前端产生的 JavaScript 代码越来越多越复杂;同时,也有越来越多的 JavaScript 问题出现,其中 JavaScript 文档的编写和维护成为一个亟待解决的难题。

许多现代编程语言都有自己的集成化文档生成工具,像 Java 有 JavaDoc ,.NET 有 NDoc,PHP 有 PHPDoc,这些自动化文档工具可以根据代码中的注释自动生成代码文档。本文将介绍的 JSDoc 也是这样的工具。

简介

JSDoc 3 是 JsDoc Toolkit 的升级版本,JsDoc Toolkit 是一个自动化文档工具,它是发布在 Google code 上的一个开源项目,和其他语言的文档工具一样,它可以自动从 JavaScript 代码中提取注释生成格式化文档。从 2010年7月27日 开始,JsDoc Toolkit 2 已经停止开发了,因此如果要使用 JSDoc 的话,还是推荐采用 JSDoc 3。现在 JSDoc 3 代码托管在 GitHub 上,项目地址:https://github.com/jsdoc3/jsdoc

安装

JSDoc 3 在本地安装依赖于 Nodejs,因此首先需要下载 Nodejs 进行安装:

http://nodejs.org/
http://nodejs.org/dist/v0.10.25/x64/node-v0.10.25-x64.msi

安装完后,使用以下命令安装 JSDoc 3 即可(:必须使用超级管理员身份安装):

# stable
npm install jsdoc@"<=3.3.0" -g
# dev
npm install git+https://github.com/jsdoc3/jsdoc.git -g

# test
C:\Users\moodpo>jsdoc --help
JSDoc 3.3.0-alpha3 (Wed, 25 Dec 2013 17:07:35 GMT)
......

使用

JSDoc 3 生成 API 文档完全依赖于你的代码注释,因此了解 JSDoc 3 的注释规则是最重要的。首先,注释必须要以 /** 开头,如果使用 /* 将不被识别;其次,每个注释块都应该包含唯一的主标签,和零个或多个副标签。

1. JSDoc 3 标签规范

标签 描述
@abstract/@virtual This member must be implemented (or overridden) by the inheritor.
@access Specify the access level of this member - private, public, or protected.
@alias Treat a member as if it had a different name.
@augments/@extends 标明一个函数继承另一个函数,如 A 继承 B 则可以在 A 的注释中加 `@augments B`
@author Identify the author of an item.
@borrows 参考,如 A 和 B 两个函数意义相同,则可以在 B 的注释中加 `@borrows A as B`,而不需重复添加注释
@callback/@typedef 标明此方法是一个回调函数
@classdesc 对一个类的描述
@constant/@const 常量标识
@constructor/@class 标明是构造器函数,可使用 `new` 关键字实例化
@constructs 当使用对象字面量形式定义类时,可使用此标签标明其构造函数
@copyright 描述版权信息
@default 默认值
@deprecated 弃用
@desc/@description 如果在注释开始描述可省略此标签
@enum 一个类中属性的类型相同时,使用此标签标明
@event 标明一个可触发的事件函数,一个典型的事件是由对象定义的一组属性来表示。
@example 示例,代码可自动高亮
@exports 标识此对象将会被导出到外部调用
@external/@host 标识此类、命名空间或模块来自外部
@file 描述文件功能
@fires/@emits 标明当一个方法被调用时将出发一个特殊类型的事件
@global 全局变量标识
@ignore 忽略此注释块
@inner 标明此代码是父类的内部变量
@instance 标明此代码是父类的实例
@kind 标明代码在其文档中的类型,如Class、Modual等
@license 采用的开源协议版本
@link 内联标签,创建一个链接,如 `{@link http://github.com Github}`
@member/@var 记录一个基本数据类型的成员变量
@memberof 指定一个变量所属的父类
@method/@function/@func 标记一个方法或函数
@mixes 标记一个对象混合了另一个对象的所有成员
@mixin 一个 `mixin` 提供了旨在将方法添加到对象上的功能
@module 标明当前文件模块,在这个文件中的所有成员将被默认为属于此模块,除非另外标明
@name 指定一段代码的名称,强制 JSDoc 使用此名称,而不是代码里的名称
@namespace 指定一个变量为命名空间变量
@param 标记方法参数及参数类型
@private/@protected/@public 标明变量访问等级
@property 标明一个对象的属性
@readonly 只读
@requires 标明运行代码所需模块
@returns/@return 标明返回值、类型及描述
@see 链接到一个参考位置
@since 描述此功能来自哪一版本
@static 描述一个不需实例即可使用的变量
@summary 对描述信息的短的概述
@this 说明 `this` 关键字所代表的意义
@throws 描述方法将会出现的错误和异常
@todo 描述函数的功能或任务
@tutorial 插入一个指向向导教程的链接
@type 描述代码变量的类型
@version 版本信息

更多内容请查看官方文档

2. 使用 conf.json 文件

安装完 JSDoc 3 后默认使用的配置文件为 conf.json.EXAMPLE,内容如下:

{
    "tags": {
        "allowUnknownTags": true
    },
    "source": {
        "includePattern": ".+\\.js(doc)?$",
        "excludePattern": "(^|\\/|\\\\)_"
    },
    "plugins": [],
    "templates": {
        "cleverLinks": false,
        "monospaceLinks": false
    }
}

我们可以定义自己的 conf.json 文件,使用此文件可以指定 JSDoc 输出目录、命令行操作参数、使用的插件、输出模版以及读取的文件等,以下是完整的文件内容:

// 使用时请删除所有注释
{
    "tags": {
        "allowUnknownTags": true //允许输出位置标签
    },
    "source": { // 指定读取的源文件
        "include": [], // 包含文件列表
        "exclude": [], // 不包含文件列表
        "includePattern": ".+\\.js(doc)?$", // 正则方式
        "excludePattern": "(^|\\/|\\\\)_"
    },
    "plugins": [], // 启用插件列表,可在 JSDoc 目录下找到所有的插件
    "templates": {
        "cleverLinks": false,
        "monospaceLinks": false
    },
    "opts": { // 命令行操作参数配置,详细内容请查看 http://usejsdoc.org/about-commandline.html
        "template": "templates/default",  // same as -t templates/default
        "encoding": "utf8",               // same as -e utf8
        "destination": "./out/",          // same as -d ./out/
        "recurse": true,                  // same as -r
        "tutorials": "path/to/tutorials", // same as -u path/to/tutorials, default "" (no tutorials)
        "query": "value",                 // same as -q value, default "" (no query)
        "private": true,                  // same as -p
        "lenient": true,                  // same as -l
        // these can also be included, though you probably wouldn't bother
        // putting these in conf.json rather than the command line as they cause
        // JSDoc not to produce documentation. 
        "version": true,                  // same as --version or -v
        "explain": true,                  // same as -X
        "test": true,                     // same as -T
        "help": true,                     // same as --help or -h
        "verbose": true,                  // same as --verbose, only relevant to tests.
        "match": "value",                 // same as --match value, only relevant to tests.
        "nocolor": true                   // same as --nocolor, only relevant to tests
    }
}

一个快速、简洁和强大的新博客框架

新的博客已经使用了很长时间,回头一想,竟然未记录下任何东西,以至于当间隔两个月回来写篇文章要提交时出现了波折,甚至生成命令也忘的差不多了。果然是好记性不如烂笔头!

基于静态文件的博客引擎已经有很多了,譬如一开始我使用的 Liquidluck 。Liquidluck 提供了静态博客生成,提交 Github ,而且还提供了 Webhook 方式的接口,使生成的静态文件自动同步到 Github上。然而其最大的缺点是,未支持 Windows 系统。以前我只能 SSH 到 VPS 上进行各种操作,直接将静态文件生成到 Nginx 下,而与 Github 的同步功能则显得多余。

这一切直到遇到了 Hexo 框架才改变,而她却是一个台湾大学生使用 Node.js 编写的,心中不免产生无限敬意。

Hexo 框架使用 Github 的 page 服务,但是却并不像 Octopress 那样复杂,既需要懂得一些 Ruby 的知识,又需要一些繁琐的配置。使用 Hexo 框架你只需知道 Github 的 Page 服务,以及几个命令即可。下面就是本博客的创建过程,仅当自己以后方便,更详细的内容还是去查看官方文档吧。

1. 安装前的必备工作

Hexo 使用 node.js ,所以 安装 node.js 是必须的,此外还要有 Git 工具。

1.1 安装 Git

  • Linux (Ubuntu, Debian): sudo apt-get install git-core
  • Linux (Fedora, Red Hat, CentOS): sudo yum install git-core

1.2 安装 Node.js

$ curl https://raw.github.com/creationix/nvm/master/install.sh | sh

或者

$ wget -qO- https://raw.github.com/creationix/nvm/master/install.sh | sh

最后安装 Node.js

$ nvm install 0.10

2. 安装 Hexo

$ npm install -g hexo

3. 配置 Hexo

使用以下命令生成静态博客所使用的所有文件:

$ hexo init <folder>

目录结构说明:

.
├── _config.yml // 全局配置文件
├── package.json  // 
├── scaffolds // 生成文件时的头格式
├── scripts
├── source 
|   ├── _drafts
|   └── _posts // Makedown文章
└── themes 

全局配置文件中提供了多种参数配置,包括站点信息,作者,个性化静态地址,标签,分类,高亮,甚至 Disqus 的评论,谷歌统计等。

4. 开始写文章

$ hexo new [layout] <title>
// [layout] 是指 scaffolds 中定义的格式
// <title> 是指文件名称

$ hexo generate
// 生成静态文件

$ hexo deploy
// 将静态文件上传到 Github 中

JavaScript 规范编程笔记:继承

JavaScript是一门弱类型的语言,从不需要类型转换。对象的起源是无关紧要的。对于一个对象来说重要的事它能做什么,而不是它从哪里来。JavaScript提供了一套更为丰富的代码重用模式。它可以模拟那些基于类的模式,同时它也可以支持其他更具表现力的模式。在JavaScript中可能的继承模式有很多。下面将介绍几种最为直接的模式。

在基于类的语言中,对象是类的实例,并且类可以从另一个类继承。JavaScript是一门基于原型的语言,这意味着对象直接从其他对象继承。

1. 伪类

当一个函数对象被创建时,Function构造器产生的函数对象会运行类似这样的一些代码:

this.prototype = {constructor: this};

该prototype对象是存放继承特征的地方。因为JavaScript语言没有提供一种方法去确定哪个函数是打算用来作构造器的,所以每个函数都会得到一个prototype对象。

当采用构造器调用模式,即使用new前缀去调用一个函数时,这将修改函数执行的方式。如果new运算符是一个方法而不是一个运算符,它将可能会像这样执行:

Function.method('new', function(){

    // 创建一个新对象,它继承自构造器函数的原型对象
    var that = Object.beget(this.prototype);

    // 调用构造器函数,绑定 this 到新对象上
    var other = this.apply(that, arguments);

    // 如果它的返回值不是一个对象,就返回该新对象
    return (typeof other === 'object' && other) || that;
});

以下是一个示例:

var Mammal = function(name){
    this.name = name;
};

Mammal.prototype.get_name = function(){
    return this.name;
};

Mammal.prototype.says = function(){
    return this.saying || '';
};

var myMammal = new Mammal('Herb the Mammal');
var name = myMammal.get_name();

以下我们可以构造另一个伪类来继承Mammal,通过定义它的构造函数并替换它的prototype为一个Mammal的实例来实现:

var Cat = function(name){
    this.name = name;
    this.saying = 'meow';
};

Cat.prototype = new Mammal();

Cat.prototype.purr = function(n){
    var i, s = '';
    for(i = 0; i < n; i += 1){
        if(s){
            s += '-';
        }
        s += 'r';
    }
    return s;
};

Cat.prototype.get_name = function(){
    return this.says() + ' ' + this.name + ' ' + this.says();
};  

var myCat = new Cat('Henrietta');
var says = myCat.says();
var purr = myCat.purr(5);
var name = myCat.get_name();

document.writeln('says : ' + says + '; purr : ' + purr + '; name : ' + name);
// says : meow; purr : r-r-r-r-r; name : meow Henrietta meow

伪类模式本意是想向面向对象靠拢,但是它看起来格格不入。我们甚至可以自行定义一个inherits方法来隐藏内部的细节:

Function.method('inherits', function(Parent){
    this.prototype = new Parent();
    return this;
});

伪类形式可以给不熟悉JavaScript的程序员提供便利,但是它也隐藏了该语言的真实本质。借鉴类的表示法可能误导程序员去编写过于深入复杂的层次结构。许多复杂的类层次结构产生的原因就是静态类型检查的约束。JavaScript完全摆脱了那些约束。在基于类的语言中,类的继承是代码重用的唯一方式。显然,JavaScript有着更多且更好的选择。

2. 对象说明符

var myObject = maker({
    first: f,
    last: l,
    state: s,
    city: c
});

当与JSON一起工作时,这还可以有一个间接的好处。JSON文本只能描述数据,但有时数据表示的是一个对象,如果构造器取得一个对象说明符,可以容易的做到,因为我们可以简单的传递该JSON对象给构造器,而它将返回一个构造完全的对象。

3. 原型

基于原型的继承相比于基于类的继承在概念上更为简单:一个新对象可以继承一个旧对象的属性。通过构造一个有用的对象开始,接着可以构造更多和那个对象类似的对象。可以完全避免把一个应用拆解成一系列嵌套抽象类的分类过程。

var myMa = {
    name : 'Herb the Mammal',
    get_name : function(){
        return this.name;
    },
    says : function(){
        return this.saying || '';
    }
}; 

// 差异化继承
var myCatMa = Object.beget(myMa);

myCatMa.name = 'Henrietta';
myCatMa.saying = 'meow';
myCatMa.purr = function(n){
    var i, s = '';
    for(i = 0; i < n; i += 1){
        if(s){
            s += '-';
        }
        s += 'r';
    }
    return s;
};
myCatMa.get_name = function(){
    return this.says() + ' ' + this.name + ' ' + this.says();
};

document.writeln(myCatMa.get_name());

4. 函数化

上述几种继承方式的一个弱点是我们没法保护隐私,对象的所有属性都是可见的。幸运的是,我们有一个更好的选择,那就是模块模式的应用。以下是构造一个产生对象的函数的步骤:

  1. 创建一个新对象,可以使用多种方式;
  2. 选择性的定义私有实例变量和方法;
  3. 给这个新对象扩充方法,这些方法将拥有特权去访问参数,以及新定义的变量;
  4. 返回新对象。

以下是上述步骤的伪码模板:

var constructor = function (spec, my){
    var that, ……;// 其他的私有实例变量
    my = my || {};

    // 把共享的变量和函数添加到my中

    that = // 一个新对象

    // 添加给 that 的特权方法

    return that;
};

spec对象包含构造器需要构造一个新实例的所有信息。spec的内容可能会被复制到私有变量中,或者被其他函数改变,或者方法可以在需要的时候访问spec的信息。my对象是一个为继承链中的构造器提供共享的容器。my 对象可以选择性的使用。

让我们将这个步骤应用到mammal例子中,此处不需要my,所以我们先抛开它,但将使用一个spec对象:

var mammal = function(spec){
    var that = {};
    that.get_name = function(){
        return spec.name;
    };

    that.says = function(){
        return spec.saying || '';
    };

    return that;
};

var myMammal = mammal({name: 'Herb'});

document.writeln(myMammal.get_name());

var cat = function(spec){
    spec.saying = spec.saying || 'meow';
    var that = mammal(spec);
    that.purr = function(n){
        var i, s = '';
        for(i = 0; i < n; i += 1){
            if(s){
                s += '-';
            }
            s += 'r';
        }
        return s;
    };
    that.get_name = function(){
        return that.says() + ' ' + spec.name + ' ' + that.says();
    };

    return that;
};

var myCats = cat({name: 'Cate',saying: 'Ni Ma B'});

document.writeln(myCats.get_name());

函数化模式还给我们提供了一个处理父类方法的方法:

Object.method('superior',function(name){
    var that = this,
        method = that[name];
    return function(){
        return method.apply(that, arguments);
    };
});

以下是在coolcat上的实验,它将返回父对象cat中的方法执行结果:

var coolcat = function(spec){
    var that = cat(spec),
        super_get_name = that.superior('get_name');
    that.get_name = function(n){
        return 'like ' + super_get_name() + ' bady';
    };
    return that;
};

var myCoolCat = coolcat({name: 'Bix'});
// like meow Bix meow baby

函数化模式有很大的灵活性。它不仅不像伪类模式那样需要很多功夫,还让我们得到更好的封装和信息隐藏,以及访问父类方法的能力。

如果使用函数化的样式创建一个对象,并且该对象的所有方法都不使用this 或 that,那么该对象就是持久性的。一个持久性的对象就是一个简单功能函数的集合。

JavaScript 规范编程笔记:函数(二)

1. 给类型增添方法

JavaScript 允许给语言的基本类型增加方法。以下是各种例子:

// 通过给 Function.prototype 增加方法使得该方法对所有函数可用
Function.prototype.method = function(name, func){
    this.prototype[name] = func;
    return this;    
};

// 给数字类型添加一个提取数字中的整数部分的方法
Number.method('integer', function(){
    return Math[this < 0 ? 'ceiling' : 'floor'](this);
});

document.writeln((-10 / 3).integer());  // -3

// 给字符串添加一个 trim 方法
String.method('trim', function(){
    return this.replace(/^\s+|\s+$/g, '');
});

document.writeln('"' + " neat ".trim() + '"');

基本类型的原型是公共的结构,所以在类库混用时需要判断,只在确定没有该方法时才添加它。

// 有条件的增加一个方法
Function.prototype.method = function(name, func){
    if(!this.prototype[name]){
        this.prototype[name] = func;
    }
    return this;
};

2. 变量作用域

JavaScript只有函数作用域,而不支持会计作用域。那意味着定义在函数中德参数和变量在函数外部是不可见的,而且在一个函数中的任何位置定义的变量在该函数中的任何地方都可见。

3. 闭包

作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量。一个更有趣的情形是内部函数拥有比它的额外部函数更长的生命周期。

// 此函数定义了一个value变量,该变量对increment和getValue方法总是可用的,
// 但函数的作用域使得它对其他的程序来说是不可见的

// 该函数将返回一个包含两个方法的对象。
var myObject = function(){
    var value = 0;
    return {
        increment: function(inc) {
            value += typeof inc === 'number' ? inc : 1;
        },
        getValue: function(){
            return value;
        }
    };
}();

下面是一个更有意义的函数。

// 创建一个名为quo的构造函数
// 它构造出带有get_status方法和status私有属性的一个对象。
var quo = function(status){
    return {
        get_status: function(){
            return status;
        }
    };
};

var myQuo = quo('amazed');
document.writeln(myQuo.get_status());
当我们调用quo时,它返回包含get_status方法的一个新对象。该对象的一个引用保存在myQuo中。即使quo已经返回了,但get_status方法仍然享有访问quo对象的status属性的特权。get_status方法并不是访问该参数的一个拷贝,它访问的就是该参数本身。这使可能的,因为函数可以访问它被创建时所处的上下文环境,这被称为**闭包**。
// 另一个更有用的例子
var fade = function(node){
    var level = 1;
    var step = function(){
        var hex = level.toString(16);
        node.style.backgroundColor = '#FFFF' + hex + hex;
        if(level < 15){
            level += 1;
            setTimeout(step, 100);
        }
    };

    setTimeout(step, 100);
}

fade(document.body);

理解内部函数能访问外部函数的实际变量而无需复制是很重要的:

// 糟糕的例子

// 当点击一个节点时,按照预想应该弹出一个对话框显示节点的序号
// 但它总是会显示节点的数目
var add_the_handlers = function(nodes){
    var i;
    for (i = 0; i < nodes.length; i += 1) {
        nodes[i].onclick = function(){
            alert(i);
        }
    };
};

add_the_handlers 函数目的是给每个事件处理器一个唯一值(i)。它未能达到目的是因为事件处理器函数绑定了变量i,而不是函数在构造时的变量i的值。

// 更好的例子
var add_the_handlers_bb = function(nodes){
    var i;
    for (i = 0; i < nodes.length; i += 1) {
        nodes[i].onclick = function(b){
            return function(){
                alert(b);
            };
        }(i);
    };
};

现在,我们定义了一个函数并立即传递i进去执行,而不是把一个函数赋值给onclick。那个函数将返回一个事件处理器函数。这个事件处理器函数绑定的是传递进去的i的值,而不是定义在add_the_handlers函数里的i的值。那个被返回的函数将被赋值给onclick。

4. 模块

我们可以使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。

以下是一个模块的例子,它的任务是寻找字符串中的HTML字符实体并替换为它们对应的字符。它保存字符实体的名字和它们对应的字符放入一个闭包中。

String.method('deentityify', function(){
    // 字符实体表,它映射字符实体的名字到对应的字符。
    var entity = {
        quot: '"',
        lt: '<',
        gt: '>'
    };

    return function(){
        // 这才是 deentityify方法
        return this.replace(/&([^&;]+);/g, 
                function(a,b){
                    var r = entity[b];
                    return typeof r === 'string' ? r : a;
                }
            );
    };

}());

document.writeln('&lt;&quot;&gt;'.deentityify()); // <">

请注意最后一行,我们用()运算符立即调用我们刚刚构造出来的函数。这个调用所创建并返回的函数才是deentityify方法。在这个例子中,只有deentityify方法有权访问字符实体表这个数据对象。

模块模式的一般形式是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可访问到的地方。

由此,我们可以使用模块模式产生安全的对象,类似JavaBean的形式:

var serial_maker = function(){

    var prefix = '';
    var seq = 0;
    return {
        set_prefix: function(p){
            prefix = String(p);
        },
        set_seq: function(s){
            seq = s;
        },
        gensym: function(){
            var result = prefix + seq;
            seq += 1;
            return result;
        }
    };
};

var seqer = serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(1000);
var unique = seqer.gensym();

JavaScript 规范编程笔记:函数(一)

一般来说,所谓编程就是将一组需求分解成一组函数与数据结构的技能。

JavaScript中最好的特性就是它对函数的实现,它几乎无所不能。函数包含子组语句,它们是JavaScript的基础模块单元,用于代码复用、信息隐藏和组合调用。函数指定对象的行为。

1. 函数对象

在JavaScrpit中函数也是对象。对象字面量产生的对象连接到Object.prototype上,而函数对象连接到Function.prototype上(该原型对象本身连接到Object.prototype)。每个函数在创建时附有两个附加的隐藏属性:函数的上下文和实现函数行为的代码(JavaScript创建一个函数对象时,会给该对象设置一个“调用”属性)。

每个函数对象在创建时也随带有一个prototype属性。它的值是一个拥有constructor属性且值即为该函数的对象。

因为函数是对象,所以它们可以像任何其他的值一样被使用。函数可以存放在变量、对象和数组中,函数可以被当作参数传递给其他函数,函数也可以再返回函数。而且,因为函数是对象,所以函数可以拥有方法。

2. 函数字面量

// 创建一个名为 add 的变量,并用来把两个数字相加的函数赋值给它
var add = function (a, b){
    return a + b;   
};

函数字面量可以出现在任何允许表达式出现的地方。函数也可以被定义在其他函数中。一个内部函数自然可以访问自己的参数和变量,同时它也能方便的访问它被嵌套在其中的那个函数的参数与变量。通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为闭包

3. 调用

在JavaScript中一共有四种调用模式:方法调用模式、函数调用模式、构造器调用模式和apply调用模式。调用函数时,除了声明时定义的形式参数,每个函数接收两个附加的参数:this和arguments。参数this在面向对象变成中非常重要,它的值取决于调用的模式。调用运算符是跟在任何产生一个函数值的表达式之后的一对圆括号。圆括号中包含了参数(如果有的话),当实际参数的个数过多时,超出的参数值将被忽略,如果实际参数值过少,缺失的值将会被替代为undefined。

方法调用模式

当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this被绑定到该对象。

// 常见 myObject,它有一个value属性和一个increment方法
// increment方法接受一个可选的参数。如果参数不是数字,那么默认使用数字1。
var myObject = {
    value: 0,
    increment: function(inc){
        this.value += typeof inc === 'number' ? inc : 1;
    }
};

myObject.increment();
document.writeln(myObject.value); // 1

myObject.increment(2);
document.writeln(myObject.value); // 3

this到对象的绑定发生在调用的时候,这个“超级”迟绑定使得函数可以对this高度复用。通过this可取得它们所属对象的上下文的方法称为公共方法

函数调用模式

当一个函数并非一个对象的属性时,那么它被当作一个函数来调用:

var sum = add(3, 4);   // sum 为 7

当函数以此模式调用时,this被绑定到全局对象。这是一个设计错误,这个错误的后果是方法不能利用内部函数来帮助它工作。解决方法是:如果该方法定义一个变量并给它赋值为this,那么内部函数就可以通过哪个变量访问到this。按照约定,我给这个变量命名为that:

// 给myObject增加一个double方法。
myObject.double = function(){
    var that = this; // this指向全局变量的解决方法

    var helper = function(){
        that.value = add(that.value, that.value);
    };

    helper(); // 以函数的形式调用helper。
};

// 以方法的形式调用double。
myObject.double();
document.writeln(myObject.value); // 6

构造器调用模式

尽管原型继承有着强大的表现力,但它并不被广泛理解。JavaScript本身对其原型的本质也缺乏信心,所以它提供了一套和基于类的语言类似的对象构建语法。

如果在一个函数前面带上new来调用,那么将创建一个隐藏连接到该函数的prototype成员的新对象,同时this将会被绑定到那个新对象上。

// 创建一个名为Quo的构造器函数,它构造一个带有status属性的对象。
var Quo = function(string){
    this.status = string;
};

// 给Quo的所有实例提供一个名为get_status的公共方法。
Quo.prototype.get_status = function(){
    return this.status;
};

// 构造一个Quo实例
var myQuo = new Quo("confused");

document.writeln(myQuo.get_status()); // 令人困惑

结合new前缀调用的函数被称为构造器函数。按照约定,它们保存在以大写格式命名的变量里。如果调用构造器函数时没有在前面加上new,可能会发生非常糟糕的事情,既没有编译时警告,也没有运行时警告,因此大写约定非常重要。不推荐使用这种形式的构造器函数。

Apply 调用模式

因为JavaScript是一门函数式的面向对象编程语言,所以函数可以拥有方法。apply方法让我们创建一个参数数组并用其去调用函数。它允许我们选择this的值。apply方法接收两个参数,第一个将被绑定给this的值,第二个就是一个参数数组。

var array = [3, 4];
var sum = add.apply(null, array); // 7

var statusObject = {
    status: 'A-OK'
};

// get_status 没有参数,statusObject指定了将被绑定给this的值
var status = Quo.prototype.get_status.apply(statusObject);
document.writeln(status); // A-OK

4. 参数

当函数被调用时,会得到一个“免费”奉送的参数,那就是 arguments 数组。通过它函数可以访问所有它被调用时传递给它的参数列表,包括那些没有被分配给函数声明时定义的形式参数的多余参数。这使得编写一个无须指定参数个数的函数成为可能:

// 构造一个将很多个值想加的函数
var sum = function(){
    var i, sum = 0;
    for(i = 0; i < arguments.length; i += 1){
        sum += arguments[i];
    }   
    return sum;
};

document.writeln(sum(4, 8, 15, 16, 23, 42));  // 108

因为语言的一个设计错误,arguments并不是一个真正的数组,arguments拥有一个length属性,但它缺少所有数组方法。

5. 返回

return 语句可用来使函数提前返回。当return被执行时,函数立即返回而不再执行余下的语句。一个函数总是会返回一个值。如果没有指定返回值,则返回undefined。

6. 异常

JavaScrpit提供了一套异常处理机制。throw语句中断函数的执行,它应该抛出一个exception对象,该对象包含可识别异常类型的name属性和一个描述性的message属性。你也可以添加其他的属性。

var add_throw = function(a,b){
    if(typeof a !== 'number' || typeof b !== 'number'){
        throw {
            name: 'TypeError',
            message: 'add needs numbers'
        };
    }
    return a + b;
};

该exception对象将被传递到一个try语句的catch从句:

var try_it = function(){
    try {
        add_throw('sever');
    } catch (e) {
        document.writeln(e.name + ': ' + e.message);
    }
};

try_it();

一个try语句只会有一个将捕获所有异常的catch代码块。如果你的处理手段取决于异常的类型,那么异常处理器必须检查异常对象的name属性以确定异常的类型。