你的位置:首页 > Java教程

[Java教程]玩转JavaScript OOP[4]实现继承的12种套路


概述

在之前的文章中,我们借助构造函数实现了“类”,然后结合原型对象实现了“继承”,并了解了JavaScript中原型链的概念。

理解这些内容,有助于我们更深入地进行JavaScript面向对象编程。

由于JavaScript是一门基于对象和原型的弱语言,灵活度非常高,这使得JavaScript有各种套路去实现继承。本篇文章将逐一介绍实现继承的12种套路,它们可以适用于不同的场景,总一种套路适合你。

(亲:文章有点长,请点击右侧的「显示文章目录」按钮,以便导航和阅读哦。)

01.原型链(经典模式)

这是实现继承的经典方式,这种方式我就不再多做介绍了,具体请参考上一篇文章。

隐藏代码
function Person(name) {  this.name = name;}Person.prototype.sayHello = function() {  return 'Hello, I am ' + this.name +'!';}function Employee(name,email) {  this.name = name;  this.email = email;}Employee.prototype = new Person();Employee.prototype.constructor = Employee;var emp = new Employee('keepfool','keepfool@xxx.com');

实现要点

  • 基于构造函数和原型链
  • 子类构造函数的原型指向父类构造函数的一个对象
  • 重写子类构造函数原型对象的constructor

02.仅继承父构造函数的原型

以上的sayHello()方法是定义在Person.prototype上的,name属性在Employee()构造函数也定义了,所以我们可以只用继承副高早函数的原型。

/* * 继承方式02:共用父构造函数的原型对象 */		function Person(name) {	this.name = name;}Person.prototype.sayHello = function() {	return 'Hello, I am ' + this.name +'!';}function Employee(name,email) {	this.name = name;	this.email = email;}Employee.prototype = Person.prototype;Employee.prototype.constructor = Employee;

和01方式唯一的区别在于Employee.prototype = Person.prototype这行代码。

实现要点

  • 基于构造函数,但不基于原型链
  • 子构造函数和父构造函数共用一个原型对象
  • 重写子类构造函数原型对象的constructor

优点

  • 由于共用一个原型对象,访问对象的属性和方法时无需遍历原型链,使得访问效率得以提升
  • 实现继承关系时,不需要新建父构造函数的实例

缺点

  • 由于共用一个原型对象,所以当子对象修改复杂类型的属性时,会同时影响父对象。
Employee.prototype.sayHello = function(){  return 'Hello, I am ' + this.name + ', my email is ' + this.email;}var person = new Person('Jack'); 

这段代码修改了Employee.prototype.sayHello方法,同时也影响了Person.prototype.sayHello方法。

image

03. 借用构造函数

在01和02两种方式中,Person()构造函数和Employee()构造函数都定义了name属性。
如果Person()和Employee()构造函数相同的属性很多,在Employee()构造函数中将会出现大量重复的this.xxx = xxx赋值操作。

使用apply()方法借用Person()构造函数,可以减少这些重复的赋值操作。

初版

function Person(name) {	this.name = name;		//调用Person.apply(this,[name])后,emp对象也会拥有favorites属性	this.favorits = ['orange','apple'];}Person.prototype.sayHello = function() {	return 'Hello, I am ' + this.name +'!';}function Employee(name,email) {	Person.apply(this,[name]);	this.email = email;}var emp = new Employee('keepfool', 'keepfool@xxx.com');

注意:当未指定Employee.prototype = new Person()时,emp对象是可以访问favorites属性的。因为Person.apply(this,[name,age]);中的this是Employee()构造函数的实例,调用apply方法时,Person()构造函数中的属性和方法都会被分配给this对象,所以emp对象是可以访问favorites属性的。

完整版

/* * 继承方式03:借用构造函数 */		function Person(name) {	this.name = name;		//调用Person.apply(this,[name])后,emp对象也会拥有favorites属性	this.favorits = ['orange','apple'];}Person.prototype.sayHello = function() {	return 'Hello, I am ' + this.name +'!';}function Employee(name,email) {	// 第2次调用Person()构造函数	Person.apply(this,[name]);	this.email = email;}// 第1次调用Person()构造函数Employee.prototype = new Person();Employee.prototype.constructor = Employee;var emp = new Employee('keepfool', 'keepfool@xxx.com');

实现要点

  • 在子构造函数中使用apply()方法,借用父构造函初始化子构造函数的属性和方法
  • 基于构造函数和原型链,子类构造函数的原型指向父类构造函数的一个对象
  • 重写子构造函数原型对象的constructor

缺点

  • 父构造函数会被调用2次:第1次是Employee.prototype = new Person();,第2次是调用Person.apply()方法。

04.临时构造函数

隐藏代码
/* * 继承方式04:使用临时构造函数 */// Personfunction Person(name) {  this.name = name;  this.favorites = ['orange','apple'];}Person.prototype.sayHello = function() {  return 'Hello, I am ' + this.name +'!';}// Employeefunction Employee(name,email) {  this.name = name;  this.email = email;}// Developerfunction Developer(name, email, skills){  this.name = name;  this.email = email;  this.skills = skills;}/* 1.借助临时构造函数实现Employee()继承Person() */var F = function() {};F.prototype = Person.prototype;Employee.prototype = new F();Employee.prototype.constructor = Employee;/* 2.借助临时构造函数实现Developer()继承Employee() */var F = function(){};F.prototype = Employee.prototype;Developer.prototype = new F();Developer.prototype.constructor = Developer;var emp = new Employee('keepfool','keepfool@xxx.com');var dev = new Developer('Jack','Jack@xxx.com',['C#','JavaScript','HTML5'])

注意:子构造函数只继承定义在父构造函数原型对象上的属性和方法,例如:Employee()只继承定义在Person.prototype上的属性和方法,Person()构造函数中定义的favorites属性不会被Employee()继承。

实现要点

  • 基于构造函数和原型链,临时构造函数的原型指向父构造函数的原型对象
  • 子构造函数的原型指向临时构造函数的一个实例
  • 重写子构造函数原型对象的constructor

缺点

  • 每次实现继承时都需要创建临时构造函数

uber——让子对象访问父对象

在介绍uber前,我们先看下面一则代码:

隐藏代码
// Personfunction Person(name) {  this.name = name;}Person.prototype.type = 'Person';Person.prototype.toString = function(){  return this.type;}// Employeefunction Employee(name,email) {  this.name = name;  this.email = email;}// Developerfunction Developer(name, email, skills){  this.name = name;  this.email = email;  this.skills = skills;}var F = function() {};F.prototype = Person.prototype;Employee.prototype = new F();Employee.prototype.constructor = Employee;Employee.prototype.type = 'Employee';var F = function(){};F.prototype = Employee.prototype;Developer.prototype = new F();Developer.prototype.constructor = Developer;Developer.prototype.type = 'Developer';var emp = new Employee('keepfool','keepfool@xxx.com');var dev = new Developer('Jack','Jack@xxx.com',['C#','JavaScript','HTML5'])

这则代码通过临时构造函数构建了Developer → Employee → Person继承关系。
调用emp.toString(),会输出"Employee"; 调用dev.toString(),则会输出"Developer"。

image 

如果希望emp.toString()输出"Person,Employee",dev.toString()输出"Person,Employee,Developer",我们该如何做呢?
这意味着我们需要遍历原型链。

在构建继承关系时,可以为子构造函数引入uber属性,并将它指向父构造函数的原型对象(因为toString()方法是定义在Person.prototype上的)。

uber这个词表示“超级的”(不是优步哦),意指引用父类。为什么不用super呢?因为super是JavaScript的一个保留关键字。

隐藏代码
// Personfunction Person(name) {  this.name = name;}Person.prototype.type = 'Person';Person.prototype.toString = function(){  return this.constructor.uber ? this.constructor.uber.toString() + ',' + this.type : this.type;}// Employeefunction Employee(name,email) {  this.name = name;  this.email = email;}// Developerfunction Developer(name, email, skills){  this.name = name;  this.email = email;  this.skills = skills;}var F = function() {};F.prototype = Person.prototype;Employee.prototype = new F();Employee.prototype.constructor = Employee;/* 1.引入uber属性,使它指向Person()构造函数的原型对象 */Employee.uber = Person.prototype;Employee.prototype.type = 'Employee';            var F = function(){};F.prototype = Employee.prototype;Developer.prototype = new F();Developer.prototype.constructor = Developer;/* 2.引入uber属性,使它指向Person()构造函数的原型对象 */   Developer.uber = Employee.prototype;Developer.prototype.type = 'Developer';var emp = new Employee('keepfool','keepfool@xxx.com');var dev = new Developer('Jack','Jack@xxx.com',['C#','JavaScript','HTML5'])

再次调用emp.toString()和dev.toString()方法:

image

有些人可能不是明白这个过程,也可能会对下面这几行代码产生好奇:

Person.prototype.toString = function(){  return this.constructor.uber ? this.constructor.uber.toString() + ',' + this.type : this.type;}

我们以emp对象为例来解释为什么emp.toString()会输出"Person,Employee"。

  1. 由于Employee.prototype.constructor === emp.constructor === Employee,所以Employee.uber === emp.constructor.uber。
  2. this.constructor.uber中的this是emp对象, 我们将它看作emp.constructor.uber,也就是Employee.uber,
  3. 而Employee.uber指向Person()的原型对象,执行this.constructor.uber.toString()相当于执行Person.prototype.toString()方法,这个方法输出为'Person'。
  4. type属性是定义在原型对象上的,this.type就是emp.type,由于Employee.prototype.type = 'Employee',所以this.type的值为'Employee'。
  5. 因此,emp.toString()方法的输出结果最终是'Person, Employee'。

将继承封装为函数

在构建Devloper → Employee → Person的继承关系时,我们使用了2次临时构造函数,这些代码是重复的,我们可以把实现继承关系的代码提炼为一个函数。

隐藏代码
/* * 继承方式04:使用临时构造函数-精简版 */// Personfunction Person(name) {  this.name = name;}Person.prototype.type = 'Person';Person.prototype.toString = function(){  return this.constructor.uber ? this.constructor.uber.toString() + ',' + this.type : this.type;}// Employeefunction Employee(name,email) {  this.name = name;  this.email = email;}// Developerfunction Developer(name, email, skills){  this.name = name;  this.email = email;  this.skills = skills;}function extend(Child,Parent){  var F = function(){};  F.prototype = Parent.prototype;  Child.prototype = new F();  Child.prototype.constructor = Child;  Child.uber = Parent.prototype;}extend(Employee,Person);Employee.prototype.type = 'Employee';extend(Developer,Employee);Developer.prototype.type = 'Developer';var emp = new Employee('keepfool','keepfool@xxx.com');var dev = new Developer('Jack','Jack@xxx.com',['C#','JavaScript','HTML5'])

这段代码引入了一个extend()函数,指定了2个参数,分别表示Child和Parent,并将临时构造函数封装在exntend()中。

当我们需要构建继承关系时,首先定义好构造函数,然后将构造函数传入extend()方法就可以实现继承了。 使用extend()方法,既能够让我们的代码保持整洁,又能够达到重用的目的。

05.拷贝父构造函数原型对象的属性

由于继承是为了使代码能够重用,难道我们就不能简单地将一个对象的属性拷贝给另外一个吗?

这当然是可以的,沿用上面的代码,我们将extend()方法替换为extend2()方法:

function extend2(Child,Parent){  var p = Parent.prototype;  var c = Child.prototype;  for(var i in p){    c[i] = p[i];  }  c.uber = p;}

通过for循环,我们将Parent.prototype中的属性拷贝到了Child.prototype。

extend2()和extend()的比较

extend2()方法和extend()方法有两个不同之处。

extend2()不需要重写Child.prototype.constructor

在extend()方法中,由于Child.prototype = new F()这行代码使得Child.prototype被覆盖了,所以extend()方法需要重写Child.prototype.constructor。

而在extend2()方法中,Child.prototype没有被覆盖,所以无需重写Child.prototype.constructor,Child.prototype.constructor本来就是指向Child的。

extend2()拷贝原型属性

下面这段代码,定义了一个Person()和Employee()构造函数,Person.prototype提供了一个基础类型的type属性,以及一个复杂类型的toString()方法。

// Personfunction Person(name) {	this.name = name;}Person.prototype.type = 'Person';Person.prototype.toString = function(){	return this.constructor.uber ? this.constructor.uber.toString() + ',' + this.type : this.type;}// Employeefunction Employee(name,email) {	this.name = name;	this.email = email;}

如果使用extend()函数构建继承关系,则type属性既不是emp对象的属性,也不是Employee.prototype的属性(提示:Employee.prototype === emp.__proto__)。
image

如果使用extend2()函数构建继承关系,type属性会成为Employee.prototype的属性。

image

需要注意的是,toString()方法由于是复杂类型,extend2()方法只拷贝了toString()方法的引用。
也就是说Employee.prototype.toString和Person.prototype.toString是同一个引用。

image

extend()和extend2()效率的比较

通过以上示例,可以看到extend2()函数的效率是不如extend()函数的,因为extend2()在拷贝属性时,每一个定义于Parent.prototype中的属性,都要在Child.prototype中重建。

但这并不是很糟糕,因为extend2()方法只重建了基础类型,复杂类型则是拷贝了引用,重建基础类型属性造成的性能的损失是可以接受的。

extend2()方法有两个好处:

  1. 可以减少在原型链的遍历和查找,因为基础类型的属性通过第一层的原型链就能找到,因为这些属性已经拷贝到Child.prototype上了。
  2. Parent.prototype上定义的方法得以重用,且无须在Child.prototype中重建,例如上述的toString()方法。

引用类型拷贝的隐患

实际上,复杂类型(引用类型)都是通过拷贝引用来完成的,共用一个引用可能会导致一些预期之外的结果。
例如:在Person.prototype上定义一个favorites属性,它是数组类型的,当然也是引用类型。

Person.prototype.favorites = ['orange','apple'];

然后我们使用Employee()构造函数创建两个对象。

var emp1 = new Employee('Jack', 'jack@xxx.com');var emp2 = new Employee('Rose','rose@xxx.com');emp2.favorites;		// ['orange','apple']emp1.favorites.push('banana');emp2.favorites;		// ['orange','apple','nanana']

image

emp2.favorites最开始是['orange','apple'],然后通过数组的push方法更改了emp1.favorites,由于emp2.favorites和emp1.favorites指向同一个引用,所以emp2.favorites也变成了 ['orange','apple','nanana']。

也就是说,emp1的改变影响了emp2,这不是我们预期的结果。

所以当使用extend2()实现继承时,对待引用类型应该谨慎,应该尽量避免对象修改类型为引用类型的属性。

06.借用构造函数并拷贝原型

借用构造函数会带来重复执行两次构造函数的问题,我们可以结合apply()和extend2()函数来修复这个问题。
使用apply()方法调用父构造函数,获取父构造函数自身的属性,然后使用extend2()函数继承父构造函数的原型属性。

隐藏代码
/* * 继承方式06.借用构造函数并拷贝原型 */		function Person(name) {	this.name = name;	this.favorites = ['orange','apple'];}Person.prototype.type = 'Person';Person.prototype.sayHello = function() {	return 'Hello, I am ' + this.name +'!';}function Employee(name,email) {	// 调用Person()构造函数	Person.apply(this,[name]);	this.email = email;}function extend2(Child,Parent){	var p = Parent.prototype;	var c = Child.prototype;		for(var i in p){		c[i] = p[i];	}		c.uber = p;}// 继承Person()构造函数的原型对象的属性extend2(Employee,Person);Employee.prototype.type = 'Employee';var emp = new Employee('keepfool','keepfool@xxx.com');

这时,emp对象不仅继承了Person()构造函数中的属性,也继承了Person()构造函数原型对象上的属性。
另外,emp对象还可以通过uber属性访问父对象。

image

这种方式是03和05的结合,它使得我们可以在不重复调用父构造函数的情况下,同时继承父构造函数的自身属性和原型属性。

实现要点

  • 基于构造函数和原型链工作
  • 拷贝父构造函数的原型属性

07.对象的浅拷贝

在这之前,我们使用构造函数来创建“类”,并使用new构造函数创建对象。 然后,我们通过Child.prototype = new Parent()来构建继承关系,这里的new Parent()也是调用了构造函数。

在实现“类”、“继承”这些概念的过程中,构造函数充当了一个中间人的作用,继承的目的是对象的属性和方法可以被其他对象重用。

如果不使用构造函数,直接进行对象之间的拷贝难道不可行吗?
这当然是可行的,我们首先介绍一种方式——对象的浅拷贝。

/* * 继承方式07:对象的浅拷贝 */		function shallowCopy(p){	var c = {};	for(var i in p){		c[i] = p[i];	}	c.uber = p;	return c;}var person = {	type : 'Person',	toString : function(){		return this.type;	}}var emp = shallowCopy(person);emp.type = 'Employee';emp.name = 'keepfool';emp.email = 'keepfool@xxx.com';// 在重写emp的toString()方法前,emp.toString === person.toString为trueemp.toString = function(){	return this.uber.toString() + ', ' + this.type;}

注意:
1. c.uber = p这行代码,表示子对象的uber属性指向父对象。
2. toString()方法是引用类型,拷贝时只拷贝引用。然后emp.toString = function() { ... }将重写了该方法,重写不会影响person.toString。

该方式不仅继承了父对象的属性,还可以通过uber属性来访问父对象。

image

实现要点

  • 基于对象工作
  • 遍历父对象的所有属性,基础类型的属性完全拷贝,复杂类型的属性则只拷贝引用
  • 子对象的uber属性引用父对象

优点

  • 不用定义构造函数
  • 不用遍历原型链

缺点

  • 由于没有构造函数,子对象的属性每次都要手动声明,例如emp.name,emp.email。
  • 由于是浅拷贝,所以存在引用类型的拷贝隐患。

08.对象的深拷贝

在拷贝属性时,如果是引用类型的拷贝,由于共用一个对象,则可能存在一些隐患,深拷贝有助于解决这个问题。
浅拷贝和深拷贝最大的区别是:如果属性为复杂类型,浅拷贝是拷贝其引用,而深拷贝则会创建一个新的复杂类型。

/* * 继承方式08:对象的深拷贝 */		function deepCopy(p,c){	c = c || {};	for(var i in p){		// 属性i是否为p对象的自有属性		if(p.hasOwnProperty(i)){			// 属性i是否为复杂类型			if(typeof p[i] === 'object'){				// 如果p[i]是数组,则创建一个新数组				// 如果p[i]是普通对象,则创建一个新对象				c[i] = Array.isArray(p[i]) ? [] : {};				// 递归拷贝复杂类型的属性				deepCopy(p[i],c[i]);			}else{				// 属性是基础类型时,直接拷贝				c[i] = p[i];			}		}	}	return c;}

深拷贝的实现逻辑,已经很清晰地在注释中描述了,请注意这段代码是如何处理数组类型和对象类型的。

下面这段代码使用deepCopy()方法创建了一个child对象。

var parent = {	name : 'keepfool',	age : 28,	favorites : ['orange','apple'],	experience : {		limit : 7,		skills : ['C#','JavaScript','HTML5']	}};// 修改child对象的属性,不会影响parent对象var child = deepCopy(parent);

如果修改child对象的复杂类型属性,不会对parent对象造成影响,因为child和parent是两个完全独立的个体,它们互不依赖。

image

实现要点

  • 基于对象工作
  • 遍历父对象的所有属性,基础类型的属性和复杂类型的属性都会重建
  • 复杂类型属性需要递归调用deepCopy()方法

09.使用object()函数

基于对象继承对象的理念,Douglas Crockford提出了一个建议,使用object()函数,接收父对象,然后返回父对象的原型。

function object(o) {  function F() {}  F.prototype = o;  return new F();}

如果要访问父对象,则为子对象添加uber属性,并指向父对象。

/* * 继承方式09:使用object()函数 */function object(o) {		var n;	function F() {}	F.prototype = o;	n = new F();	n.uber = o;	return n;}

使用object()函数和使用shallowCopy()函数是一样的,复杂类型的拷贝仍然是引用拷贝。

var person = {	type: 'Person',	favorites : ['orange','apple'],	toString: function() {		return this.type;	}}var emp = object(person);

image

实现要点

  • 基于对象和原型链工作
  • 使用了临时构造函数

10. 原型继承与属性拷贝的混合模式

继承的目的之一在于重用已有对象的属性,然后在子对象上扩展一些额外的属性。
既然如此,我们可以将原型继承和属性拷贝混合起来使用。

通俗地讲,就是我们不仅使用现有对象(使用原型的属性),还要基于现有的对象扩展一些属性。

/* * 继承方式10:原型继承与属性拷贝的混合模式 */function objectPlus(o, stuff) {	var n;	// 1. 从对象o继承原型	function F() {}	F.prototype = o;	n = new F();	n.uber = o;	// 2. 从对象stuff拷贝属性	for (var i in stuff) {		n[i] = stuff[i];	}	return n;}

objectPlus()方法有两个参数,第1个参数用于继承原型,第2个参数用于拷贝属性。
下面这段代码:person对象是一个参数,用于继承原型;{}匿名对象中定义了一些属性,用于扩展子对象的属性。

var person = {	type: 'Person',	favorites : ['orange','apple'],	toString: function() {		return this.type;	}}var emp = objectPlus(person, {	type : 'Employee',	name: 'keepfool',	email: 'keepfool@xxx.com',	toString : function(){		return this.uber + ',' + this.type;	}});

image

这种方式使得我们一次性完成了对象的继承和扩展。

实现要点

  • 基于对象和原型链工作
  • 遍历父对象的所有属性,基础类型的属性完全拷贝,复杂类型的属性则只拷贝引用

11. 多重继承


像C#,Java这样的面向对象编程语言,是不支持多重继承的(但是支持多重接口实现)。
但对于JavaScript这样的动态语言,实现多重继承就比较简单了。

下面这段代码定义了一个multi()函数,它没有显式地定义参数。但通过arguments可以获取调用函数时的参数,所以我们只需遍历arguments参数,然后拷贝每个参数对象的属性即可。

/* * 继承方式11:多重继承 */function multi() {	var n = {},		stuff, j = 0,		len = arguments.length;	for (j = 0; j < len; j++) {		stuff = arguments[j];		for (var i in stuff) {			n[i] = stuff[i];		}	}	return n;}

使用multi()函数:

var person = {  type: 'Person',  toString: function() {    return this.type;  }};var emp = {  type: 'Employee',  name: 'keepfool',  email: 'keepfool@xxx.com'};var dev = multi(person, emp, {  type: 'Developer',  age: 28,  skills: ['C#', 'JavaScript']});

image

注意:如果arguments数组中的对象存在相同的属性,则后遍历的对象属性会覆盖先遍历的对象属性。

实现要点

  • 遍历argument数组的每一个元素(每一个元素都是对象)
  • 遍历对象的所有属性,基础类型的属性完全拷贝,复杂类型的属性则只拷贝引用

12.寄生继承

寄生继承是指:在创建对象的函数中,创建要返回的对象时,首先直接吸收其他对象的属性,然后再扩展自己的属性。
这个过程就好似一个对象的创建是寄生在另外一个对象上完成的。

下面这段代码,employee()是寄生继承函数,实现寄生函数时借用了09条的object()函数。

/* * 继承方式12:寄生继承 */function object(o) {	var n;	function F() {}	F.prototype = o;	n = new F();	n.uber = o;	return n;}var person = {	type: 'Person',	toString: function() {		return this.type;	}};// employee()是寄生函数function employee(name, email) {	// 寄生peson对象	var that = object(person);		// 然后扩展自己的属性	that.type = 'Employee';	that.toString = function(){		return this.uber.type + ',' + this.type;	}		return that;}var emp = employee('keepfool','keepfoo@xxx.com');

注意:这段代码中的that不是一个关键字,它只是一个普通的对象。

image

实现要点

  • 基于对象和原型链工作
  • 使用object()函数

总结

由于这篇文章的篇幅较长,读到这儿的人,可能已经忘了我这篇文章开头讲的是什么内容了。但这并不要紧,咱们来个全篇的总结,能让你马上回忆起来。

编号原型链示例
01 原型链(经典模式)
Child.prototype = new Parent();						
02 仅继承父构造函数的原型
Child.prototype = Parent.prototype;						
03 借用构造函数
function Child() {	Parent.apply(this, arguments);}						
04 临时构造函数
function extend(Child,Parent) {	var F = function() {};	F.prototype = Parent.prototype;	Child.prototype = new F();	Child.prototype.constructor = Child;	Child.uber = Parent.prototype;}						
05 复制父构造函数的原型属性
function extend2(Child, Parent) {	var p = Parent.prototype;	var c = Child.prototype;	for (vari in p) {		c[i] = p[i];	}	c.uber = p;}						
06 借用构造函数并拷贝原型
function Child() {	Parent.apply(this, arguments);}extend2(Child,Parent);						
07 基于对象的浅拷贝
function shallowCopy(p) {	var c = {};	for (var i in p) {		c[i] = p[i];	}	c.uber = p;	return c;}						
08 基于对象的深拷贝
function shallowCopy(p) {	var c = {};	for (var i in p) {		c[i] = p[i];	}	c.uber = p;	return c;}						
09 原型继承
function object(o) {	function F() {}	F.prototype = o;	return new F();}						
10 原型继承与属性拷贝的混合模式
function objectPlus(o, stuff) {	var n;	function F() {}	F.prototype = o;	n = new F();	n.uber = o;	for (var i in stuff) {		n[i] = stuff[i];	}	return n;}						
11 多重继承
function multi() {	var n = {},stuff, j = 0,len = arguments.length;	for (j = 0; j < len; j++) {		stuff = arguments[j];		for (var i in stuff) {			n[i] = stuff[i];		}	}	return n;}						
12 寄生继承
function parasite(victim) {	var that = object(victim);	that.more = 1;	return that;}						

面对这么多的方法,你该如何选择呢?这取决于性能的需求、任务的目标以及设计风格等等。
如果你已经习惯了从“类”的角度去理解和分析问题,那么基于构造函数的继承实现比较适合你,01~06方式都是基于构造函数的。
如果你只是处理某些具体对象或实例,那么基于对象的继承实现比较适合你,07~12方式都是基于对象的。

参考

《Object-Oriented JavaScript 2nd Edition》