一、原型继承
JavaScript使用了一种独特的对象创建和继承的方式,称为原型继承(prototypal inheritance)。这一方法背后的前提(相对大多数程序员所熟悉的传统的类/对象方案而言)是,一个对象的构造器能够从另一个对象中继承方法,建立起一个原型对象,所有的新的对象都将从这个原型创建。
这整个过程由prototype属性(存在于每一个函数中,因为任何函数都可以是一个构造器)促成。
使得这种形式的继承特别难以掌握的是,原型并不从其它的原型或者其它的构造器继承属性,而是从实际的对象中继承。
代码:
//创建Person对象的构造器 function Person( name ) { this.name = name; } //为Person对象加入一个新方法 Person.prototype.getName = function() { return this.name; }; //创建一个新的User对象构造器 function User( name, password ) { //注意这并不支持优雅的重载/继承, //如能够调用超类的构造器 this.name = name; this.password = password; }; //User对象继承Person对象的全部方法 User.prototype = new Person(); //我们添加一个自己的方法给User对象 User.prototype.getPassword = function() { return this.password; };
上例中最重要的一行是User.prototype = new Person();。我们来深入地看看这到底意味着什么。User是对User对象的函数构造器的引用。new Person()建创一个新的Person对象,使用Person构造器。将这一结果设为User构造器的prototype的值,这意味着不论任何时候你使用new User()的时候,新建的User类对象也将拥有你使用new Person()时创建的Person类对象的所有方法。
带着这一特殊的技巧,我们来看一些不同的开发者所编写的使得JavaScript中继承的过程简单化的封装。
二、类继承
关于类继承,Douglas 有一篇经典文章:
这篇文章里,老道分析了为什么要在 JavaScript 里模拟类继承:主要目的是复用。老道的实现方式是给Function.prototype
增加 method
和 inherits
两个方法,并提供了 uber
语法糖。
三、Dean Edwards--Base库
JavaScript对象创建和继承领域近期的成果是Dean Edwards所开发的Base库。这一特别的库提供了一些不同的方式来扩展对象的功能。除此之外,它甚至提供了一种直觉式的对象继承方式。Dean最初开发这个库是为了用于他的其它的项目,包括IE7项目(作为对IE一整套的升级)。Dean的网站上列出的例子相当易于理解并确实很好的展示了这个库的能力:http://dean.edwards.name/weblog/2006/03/base 。除此而外,你可以在Base源代码目录里找到更多的例子:http://dean.edwards.name/base/。
Base库是相当冗长而复杂的,它值得用额外的注释来说明(包含于http://www.apress.com/的Source Code/Download所提供的代码中)。除了通读注释过的代码以外,强烈建议你去看Dean在他的网站上提供的例子,因为它们非常有助于澄清常见的疑惑。
但作为起点,我将带你一览Base库的几个可能对你的开发很有帮助的重要的方面。具体地,在程序3-4展示了类创建、单父继承和重写父类函数的例子。
程序3-4. 利用Dean Edwards的Base库进行简单的类创建和继承的例子
代码:
//创建一个新的Person类 var Person = Base.extend({ //Person类的构造函数 constructor: function( name ) { this.name = name; }, //Person类的简单方法 getName: function() { return this.name; } }); //创建一个新的继承了Person类的User类 var User = Person.extend({ //创建User类的构造器, constructor: function( name, password ) { //该构造器顺次调用了父类的构造器方法 this.base( name ); this.password = password; }, //为User类创建另一个简单的方法 getPassword: function() { return this.password; } });
我们来看看在程序3-4中Base库是如何达到先前所归纳的三个目标从而创造出一种对象创建和继承的简单形式的。
Base.extend(...);:这一表达式用来创建一个新的基本的构造器对象。此函数授受一个参数,即一个简单的包含属性和值的对象,其中的属性都会作为原型方法被被增添到(所创建的构造器)对象中。
Person.extend(...);:这是Base.extend()语法的一个可替换版本。所有的创建的构造器都使用.extend()方法获取它们自己的.extend()方法,这意味着直接从它们继承是可能的。程序3-4中,正是通过直接从最初的Person构造器中直接继承的方式创建了User构造器。
this.base();:最后,this.base()方法用来调用父对象的被重写了的对象。你会发现这与Corockford's的类继承所使用的this.uber()函数截然是截然不同的,你无需提供父类的方法名(这一点有助于真正地清理并明晰化你的代码)。在所有的面向对象的JavaScript库中,Base库的重写父方法的功能是最好的。
个人而言,我觉得Dean的Base库能够出产最可读的、实用的和可理解的面向对象的JavaScript代码。当然,最终选择什么库要看开发者自己觉得什么最适合他。接下来你将看到面对对象的JavaScript代码如何在流行的Prototype库中实现。
四、Prototype库
代码:
//创建一个名为"Class"的全局对象 var Class = { //它拥有一个用来创建新的对象构造器的函数 create: function() { //创建一个匿名的对象构造器 return function() { //调用它本身的初始化方法 this.initialize.apply(this, arguments); } } } //为对象"Object"添加静态方法,用以从一个对象向另一个对象复制属性 Object.extend = function(destination, source) { //遍历欲扩展的所有属性 for (property in source) { //并将它添加到目标对象 destination[property] = source[property]; } //返回修改过的对象 return destination; }
Prototype确实只用了两个明显的函数来创建和维护其整个面向对象体系。你们可能已发现,仅通过看观察代码,也能断定它不如Base或者Crockford的类式方法那样强大。两个函数的前提很简单:
Class.create():这个函数简单地返回一个可用做构造器的匿名函数包装。这个简单的构造器做了一件事:调用和执行对象的initialze属性。这意味着,你的对象里至少有一个包含函数的initialize属性;否则,代码将会出错。
Object.extend():这个函数简单地从一个对象往另一个对象复制属性。当你使用构造器的prototype属性时你能设计出一种更简单的继承的形式(比JavaScript中可用的缺省的原型继承更简单)。
五、John Resig--Simple JavaScript Inheritance
I’ve been doing a lot of work, lately, with JavaScript inheritance – namely for my work-in-progress JavaScript book – and in doing so have examined a number of different JavaScript classical-inheritance-simulating techniques. Out of all the ones that I’ve looked at I think my favorites were the implementations employed by base2 andPrototype.
代码:
/* Simple JavaScript Inheritance * By John Resig http://ejohn.org/ * MIT Licensed. */ // Inspired by base2 and Prototype (function(){ var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /_super/ : /.*/; // The base Class implementation (does nothing) this.Class = function(){}; // Create a new Class that inherits from this class Class.extend = function(prop) { var _super = this.prototype; // Instantiate a base class (but only create the instance, // don't run the init constructor) initializing = true; var prototype = new this(); initializing = false; // Copy the properties over onto the new prototype for (var name in prop) { // Check if we're overwriting an existing function prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() { var tmp = this._super; // Add a new ._super() method that is the same method // but on the super-class this._super = _super[name]; // The method only need to be bound temporarily, so we // remove it when we're done executing var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } // The dummy class constructor function Class() { // All construction is actually done in the init method if ( !initializing && this.init ) this.init.apply(this, arguments); } // Populate our constructed prototype object Class.prototype = prototype; // Enforce the constructor to be what we expect Class.prototype.constructor = Class; // And make this class extendable Class.extend = arguments.callee; return Class; }; })();
Demo用例:
var Person = Class.extend({ init: function(isDancing){ this.dancing = isDancing; }, dance: function(){ return this.dancing; } }); var Ninja = Person.extend({ init: function(){ this._super( false ); }, dance: function(){ // Call the inherited version of dance() return this._super(); }, swingSword: function(){ return true; } }); var p = new Person(true); p.dance(); // => true var n = new Ninja(); n.dance(); // => false n.swingSword(); // => true // Should all be true p instanceof Person && p instanceof Class && n instanceof Ninja && n instanceof Person && n instanceof Class
个人感觉jquery作者的JS OO 库最简洁精悍,也比较易懂,糅合了Base2 和 Prototype的优点,虽然功能上并不是十分全面,但至少对于我们系统是够用的。