11.4 方法 2:把函数也放入散列中
first class
Perl 语言中使用包把多个函数归集在一起。接下来,我们要介绍的是 JavaScript 语言中使用的另一种归集方法。这种方法把函数也放入散列中。
大家使用的编程语言大多应该可以把字符串赋值给变量。也可以把它作为函数的参数传递或作为函数的返回值返回。或许你会觉得这是理所当然的事情。事实上,并非所有的语言都如此。比如,FORTRAN 66 中就不能把字符串赋值给变量。另外,C 语言中就不可以把数组作为参数来调用函数 10。
10看起来像是把数组或者字符串传递给了函数,事实上只是指向数组开头位置的指针。
像这种不受限制,可以赋值给变量,也可以作为函数的参数传递,又可以作为函数的返回值返回的值被称为 first class 的值。这好比不受任何歧视的一等公民(first-class citizen)。最新出现的一些编程语言中,如 Java 语言、Perl 语言和 Python 语言,字符串就是 first class 的值。
在 JavaScript 语言中,函数也是 first class 的值。函数可以被赋值给变量,可以作为函数的返回值返回。接下来,我们来考察利用这一特征可以实现哪些功能。
把函数放入散列中
JavaScript 中的散列是用如下语句定义的。
JavaScript
{count: 0, name: "麻雀"}
这里像 0 和 " 麻雀 " 这样表达值的部分,可以放入函数 function( ){ … }(图 11.5)。

图 11.5 counter 是散列
接下来,我们来看这是如何实现的。下面的代码和 11.3.2 节中介绍的内容大体相同,实现了野鸟计数器的功能。
JavaScript
var counter = {
count: 0,
name: "麻雀",
push: function(){
this.count++;
console.log(this.name + ": " +
this.count + "只");
},
reset: function(){
this.count = 0;
console.log(this.name + ": " + "重置");
}
}
counter.push(); //-> 麻雀: 1只
counter.push(); //-> 麻雀: 2只
counter.push(); //-> 麻雀: 3只
counter.reset();//-> 麻雀: 重置
counter.push(); //-> 麻雀: 1只
在 Perl 语言中是通过包来实现的,而在 JavaScript 语言中是通过散列实现的。此外有没有别的不同之处呢?乍一看,还有关键字 this 的使用这一区别。this 是一个限定词,在函数 my_method( )与对象 obj 绑定之后通过 obj.my_method( ) 的形式调用此函数时,this 用于 在 my_method( ) 函数中引用 obj 对象本身。前面的 Perl 语言的例子中,我们用 my $value = shift 语句来显式地获取参数,而 JavaScript 语言中却是隐式地借助了 this 这一变量来表示。上述的例子中通过 counter.push( ) 语句来调用函数,因此 push 函数中使用的 this 变量指的就是 counter。所以这个例子中把 this 替换成 counter 程序也是可以正常执行的。
这个例子和使用包的例子都有一个共通的问题,就是只能创建一个计数器。但是这里要创建多个计数器也不是困难的事情。下面我们就来展示这是如何做到的。
创建多个计数器
为了创建多个计数器,我们需要定义一个散列初始化的函数。编写这样一个函数是出乎意料的简单,只需要把创建散列的代码挪到 makeCounter 函数中就可以。这样就达到了和 Perl 语言中使用包一样的效果 11,即:
11因篇幅所限,这里省略了 reset 方法。
可以创建多个对象
外观上是一个完整不可分的整体
无需人为记住初始化方法
JavaScript
function makeCounter(){
return {
count: 0,
push: function(){
this.count++;
console.log(this.count + "只");
}
}
}
var c1 = makeCounter();
var c2 = makeCounter();
c1.push(); //-> 1只
c2.push(); //-> 1只
c1.push(); //-> 2只
把共享的属性放入原型中
刚才的代码中,每次创建计数器时都会重新定义一个 push 函数。用以下语句确认 c1.push 和 c2.push 是否相同时,返回值是 false。这意味着两者是不同的。
JavaScript
console.log(c1.push === c2.push); //-> false
这是什么原因呢?在每次调用 makeCounter 时 push: funtion( ){…} 会被执行,定义一个新的函数。也就是说,创建 100 个计数器,就会有 100 个内容相同的 push 函数被定义。如果内存和 CPU 可以无限供应时,这不是什么问题。但现实没有这么美好。如果能把 push 函数这样所有计数器都共享的属性归集起来,个别计数器只是对其做引用,这样应该能节省内存和时间(图 11.6)。

图 11.6 把共享的属性归集起来
然而,归集起来后就要记得放入了哪里,使用的时候还要显式地指示出来,这样很麻烦。把共享的内容放在别的对象中,那就必须记住这一内容放在哪个对象中,人工来记忆这些是令人不快的。如果语言处理器能代劳并做出正确的判断就好了。
原型的操作
为解决这一问题,JavaScript 语言引入了原型的概念 12。当向一个对象查询 x 的值时,如果这个对象自己知道就自己给出答案。如果不知道,它会去查询它的原型再给出答案。下面的代码中,obj 对象就不知道 x 的值是多少。
12这里介绍的是用委托的方式实现原型这一概念的情景,不同语言中也可以在实例化时通过负责实现。至于这种方法中当实例化后原型发生了变更会怎样,在不同语言中是存在差异的。
JavaScript
obj = {}
obj.proto = {x: 1}
console.log(obj); // -> {}
console.log(obj.proto); // -> { x: 1 }
console.log(obj.x); // -> 1
在查询 x 的值时 (obj.x),obj 转向它的原型 (obj.proto) 才给出答案(图 11.7)13。
13通过 proto 这个名字访问原型并不是标准功能,根据处理器的不同会有不同的可能性。JavaScript 1.8.1 开始引入了 Object.getPrototypeOf (object) 的表达方式。

图 11.7 查询 obj.x 时发生的事情
用 new 运算符实现高效表达
除此之外,JavaScript 语言还引入了一种使得原型处理的表述更加方便的运算符。在函数 f 前使用 new 运算符后会执行以下四个操作:
创建新的对象 x
新创建的对象 x 的原型变为函数 f 的原型
把新创建的对象 x 传给 this,执行函数 f 的内容
返回对象 x
JavaScript
var Counter = function() {
this.count = 0; // ❶
}
Counter.prototype.push = function(){ // ❷
this.count++;
console.log(this.count + "只");
}
var c1 = new Counter(); // ❸
c1.push(); //-> 1只
c1.push(); //-> 2只
var c2 = new Counter();
console.log(c1.push === c2.push) //-> true // ❹ 相同
这段代码中,首先通过❷句在 Counter 的原型中追加了一个名为 push 的新的函数。在随后的❸句执行了上述四个操作。首先创建了一个空的对象,然后将这个对象的原型设为 Counter 的原型。这个原型中定义了 push 函数。接下来,把这个对象传给 this,再调用 Counter。Counter 中包含❶句的内容。执行这些内容会往这个对象中追加一个名字为 count 值为 0 的属性。最后,返回一个原型中包含 push 函数的、带有 count 属性的对象。不经意间我们就实现了图 11.6 右边部分的结构。❹句返回值是 true,由此可知 push 函数已经是共享的了。
这就是面向对象吗
在 Java 语言中学过面向对象的读者可能会心里犯嘀咕,觉得到现在为止讲的内容远远不够。有些人甚至可能要发怒,质问为什么关于类的话题还没有任何涉及。
笔者认为,不知道面向对象的读者当中很多读者也不知道何为类。只是大家看的此类书籍都是从类开始讲面向对象的。
的确,Java 语言是一门编程必从类开始的语言。笔者也深知,在教 授 Java 语言程序设计时,不先详细讲解并使之学会使用类是不行的。然而,类并不是从程序设计语言诞生之日起就存在的。它充其量只不过是几十年前某个人出于提高便利性的需要,试着创作出来的东西而已。尽管如此,我们总是被提醒类的存在并被鼓励去使用它,但又不太明白类为什么是必要的。
类的存在只不过是因为人们觉得有了它编写程序会更方便些,而约定的一种事项。它并不是什么物理法则或宇宙真理,仅仅是人们的一种约定而已。所以,为了理解为什么会有这样一种约定,我们需要考虑语言设计者的意图。
