到目前为止,您应该对 JavaScript 有一个正确的评价了,它绝非只如一个玩具那么简单。事实上,很多人都使用过其对象模型创建过极为复杂、设计良好的面向对象软件。但对象模型尤其是用于继承的对象模型又非您一贯认为的那样。
Java 语言是基于类的。当构建应用程序时,也同时构建了可以作为所有对象的模板的新类。然后调用 new来实例化该模板,创建一个新对象。而在 JavaScript 中,所创建的是一个原型,此原型是一个实例,可以创建所有未来的对象。
现在先暂且放下这些抽象的概念,去查看一些实际代码。比如, 创建了一个简单的 Animal,它具有name 属性和 speak 动作。其他动物会从这个基础继承。
构造函数
对于 Java 开发人员而言,清单 7 中的代码看起来多少有点生疏和奇怪。实际上对于没有亲自构建过对象的许多JavaScript 开发人员来说,这些代码同样看起来有点生疏和奇怪。也许,下面的解释可以让大家能够更好地理解这段代码。
实际上,您只需重点关注其中三段信息。首先,JavaScript 用嵌套函数表示对象。这意味着清单 7 中的Animal 的定义是一种有效的语法。第二,JavaScript 基于原型或现有的对象的实例来构造对象,而非基于类模板。funct() 是一种调用,但 new Animal() 却基于 Animal 内的原型构造一个对象。最后,在 JavaScript 中,对象只是函数和变量的集合。每个对象并不与类型相关,所以可以自由地修改这种结构。
如您所见,JavaScript 基于在 Animal 中指定的原型定义一个新对象:myAnimal。继而可以使用原型中的属性和函数,甚或重定义函数和属性。这种灵活性可能会让 Java 开发人员受不了,因为他们不习惯这种行为,但它的确是一种十分强大的模型。
继承
现在我还要更深入一步。您还可以使用名为 prototype 实例变量来指定对象的基础。方法是设置prototype 实例变量使其指向继承链的父。如此设置 prototype 之后,您所创建的对象会为未指定的那些对象继承属性和函数。这样一来,您就可以模仿面向对象的继承概念。
创建了一个 Dog 原型。此原型基于 Animal。Dog 重定义 speak() 方法但却不会对 name() 方法做任何改动。随后,将原型 Dog 设置成 Animal。图 7 显示了其结果:
这也展示了 JavaScript 是如何解决到属性或方法的引用问题的:
- JavaScript 基于原始的原型创建实例,该原型在构造函数中定义。任何对方法或属性的引用都会使用所生成的原始副本。
- 您可以在对象内像定义其他任何变量一样重新定义这些变量。这样做必然会更改此对象。所以您显式定义的任何属性或函数都将比在原始的原型中定义的那些属性或函数优先级要高。
- 如果您显式设置了名为 prototype 的实例变量,JavaScript 就会在此实例中寻找任何未定义的实例变量或属性。这种查找是递归的:如果 在 prototype 内定义的实例不能找到属性或函数,它就会在其 原型中查找,依此类推。
那么,JavaScript 的继承模型到底是什么样的?这取决于您如何对它进行定义。您需要定义继承行为以便可以覆盖它。然而,从本质上讲,JavaScript 更像是一种函数式语言,而非面向对象的语言,它使用一些智能的语法和语义来仿真高度复杂的行为。其对象模型极为灵活、开放和强大,具有全部的反射性。有些人可能会说它太过灵活。而我的忠告则是,按具体作业的需要选择合适的工具。