js类的继承
1、类式继承
首先要做的是创建构造函数。按惯例,其名称就是类名,首字母应该大写。在构造函数中,创建实例属性要用关键字this 。类的方法则被添加到prototype对象中。要创建该类的实例,只需结合关键字new调用这构造函数即可。
/* Class Person. */ function Person(name) { this.name = name; } Person.prototype.getName = function() { return this.name; } var reader = new Person('John Smith'); reader.getName();
二、原型链
JavaScript的每个对象都有一个名为prototype的属性,这个属性要么指向另一个对象,要么是null.在访问对象的某个成员时,如果这个成员未见于当前对象,那么就会到prototype所指的对象中去查找。如果还是没有找到,那么就会沿着原型链逐一访问每个原型对象,直到找到这个成员。这意味着让一个类继承另一个类,只需将子类的prototype设置为超类的一个实例即可。
为了让Author继承Person,必须手工将Author的prototype设置为Person的一个实例。最后一步是将prototype的construct属性重设为Author(因为prototype属性设置为Person的实例)
时,其construct属性被抹除了。
function Author(name, books) { Person.call(this, name); // Call the superclass' constructor in the scope of this. this.books = books; // Add an attribute to Author. } Author.prototype = new Person(); // Set up the prototype chain. Author.prototype.constructor = Author; // Set the constructor attribute to Author. Author.prototype.getBooks = function() { // Add a method to Author. return this.books; }; var author = []; author[0] = new Author('Dustin Diaz', ['JavaScript Design Patterns']); author[1] = new Author('Ross Harmes', ['JavaScript Design Patterns']); console.log(author[1].getName()); console.log(author[1].getBooks());
三、extend函数
为了简化类的声明,可以把派生子类的整个过程包装在一个名为extend的函数中。它的作用与其他语言的extend关键字类似,即基于一个给定的类的结构创建一个新的类:
function extend(subClass, superClass) { var F = function() {}; F.prototype = superClass.prototype; subClass.prototype = new F(); subClass.prototype.constructor = subClass; }
其实所做的事与之前的是一样的。它先设置了prototype,然后再将其constructor重设为恰当的值。并且中间利用了一个空函数,这样就可以避免创建超类的实例。使用extend继承的写法:
function Person(name) { this.name = name; } Person.prototype.getName = function() { return this.name; } /* Class Author. */ function Author(name, books) { Person.call(this, name); this.books = books; } extend(Author, Person); Author.prototype.getBooks = function() { return this.books; };
但上面的存在一个问题就是超类Person的名称被固化在Author类的声明当中。更普世性的做法应该像下面这样:
/* Extend function, improved. */ function extend(subClass, superClass) { var F = function() {}; F.prototype = superClass.prototype; subClass.prototype = new F(); subClass.prototype.constructor = subClass; subClass.superclass = superClass.prototype; if(superClass.prototype.constructor == Object.prototype.constructor) { superClass.prototype.constructor = superClass; } } /* Class Author. */ function Author(name, books) { Author.superclass.constructor.call(this, name); this.books = books; } extend(Author, Person); Author.prototype.getBooks = function() { return this.books; }; Author.prototype.getName = function() { var name = Author.superclass.getName.call(this); return name + ', Author of ' + this.getBooks().join(', '); };
这个extend改进之后,多了一个superclass的属性,这个属性可以弱化Author和Person之间的耦合。extend后面三行用来确保超类的construtor已经被正确设置了。有了superclass的属性,就可以直接调用超类中的方法。这在既要重新定义超类的某个方法而又想访问其在超类中的实现时可以派上用场。例如,为了用一个新的getName的方法重新定义Person类中的同名方法,你可以先用Author.superclass.getName获得作者的名字,然后再次基础上添加新的信息。
四、原型继承
原型式继承与类式继承截然不同,我们在学习他的时候,最好忘记自己关于类和实例的一切知识,只从对象的角度来思考。使用原型式继承时,并不需要用类来定义对象的结构,只需直接创建一个对像就可以。这个对象随后可以被新的对象使用,该对象被称为原型对象。
下面使用原型对象来重新设计上面Person和Author:
var Person = { name: 'default name', getName: function() { return this.name; } }; var reader = clone(Person); alert(reader.getName()); // This will output 'default name'. reader.name = 'John Smith'; alert(reader.getName()); // This will now output 'John Smith'.
clone函数可以用来创建新的类Person对象,创建一个空对象,并且该对象的原型对象被设置为person。当新对象中找不到某个方法时就会在原型对象中查找。
你不必去为了创建Author而定义一个Person子类,只要执行一次克隆就可以:
var Author = clone(Person); Author.books = []; // Default value. Author.getBooks = function() { return this.books; }
然后你可以重定义该克隆中的方法和属性。可以修改Person的默认值。也可以添加新的属性和方法。这样一来就创建了一个新的原型对象,你可以将其用于创建新的Author对象:
var author = []; author[0] = clone(Author); author[0].name = 'Dustin Diaz'; author[0].books = ['JavaScript Design Patterns']; author[1] = clone(Author); author[1].name = 'Ross Harmes'; author[1].books = ['JavaScript Design Patterns']; author[1].getName(); author[1].getBooks();
clone函数的写法:
function clone(object) { function F() {} F.prototype = object; return new F; }
五、原型继承和类式继承之间的比较
可以自己去总结、
从内存,适用范围,优缺点等方面去分析
六、掺元类
有一种重用代码的方法不需要用到严格的继承,如果想把一个函数运用到多个类当中,可以通过扩充的方法让这些类共享函数。其实际大体做法就是:先创建一个包含各种通用的方法类,然后再扩充其他类,这种包含通用方法类称为掺元类,他们通常不会被实例化和直接调用,其存在的目的是向其他类提供自己的方法。
var Mixin = function() {}; Mixin.prototype = { serialize: function() { var output = []; for(key in this) { output.push(key + ': ' + this[key]); } return output.join(', '); } }; augment(Author, Mixin); var author = new Author('Ross Harmes', ['JavaScript Design Patterns']); var serializedString = author.serialize(); function augment(receivingClass, givingClass) { for(methodName in givingClass.prototype) { if(!receivingClass.prototype[methodName]) { receivingClass.prototype[methodName] = givingClass.prototype[methodName]; } } }
但是有时候你并不需要所有的方法,因此我们还需要提供额外的参数来选择我们所需要的方法。如果不提供,那就全部复制。
function augment(receivingClass, givingClass) { if(arguments[2]) { // Only give certain methods. for(var i = 2, len = arguments.length; i < len; i++) { receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]; } } else { // Give all methods. for(methodName in givingClass.prototype) { if(!receivingClass.prototype[methodName]) { receivingClass.prototype[methodName] = givingClass.prototype[methodName]; } } } }