简单总结继承的几种方式
JavaScript作为一门弱类型的语言,本着精简的原则,它取消了类的概念,只有对象的概念,
更是有万物皆对象的说法。在基于类的面向对象方式中,对象(object)依靠类(class)来产生。
而在基于原型的面向对象方式中,对象(object)则是依靠构造器(constructor) 利用 原型(prototype)
构造出来的。而JavaScript语言正是如此,它是通过一种叫做原型(prototype)的方式来实现面向对象编程的。
它和其他的面向对象类编程语言一样,只是它的实现方式不同而已,或者说他们采用了不同的面向对象设计哲学。
那么下面就让我们来简单总结一下继承的几种方式:
- 扩展原型对象实现继承
构造函数有一个 prototype 属性,指向的就是原型对象,通过给原型对象添加属性和方法,让构造函数的实例
都可以访问到,从而实现继承
function Animal(name,color,say){
this.name = name;
this.color = color;
this.say = function(){
console.log('喵喵喵');
}
}
var cat = new Animal('cat','white');
// 如果给Animal的prototype属性上添加个 cry 方法 ,那么实例对象 cat将也会有 cry方法
Animal.prototype.cry = function(){
console.log('呜呜呜');
}
- 替换原型对象实现继承 (常用类型)
-
为什么会有该方式呢?
其实上面扩展原型对象的方法已经很方便了,但是为什么我们还需要使用该方式呢?试想一下,
当我们需要给构造函数的原型对象添加许多属性和方法的时候,岂不是要写很多冗余(重复)的代码。例如上面的例子
看下面代码:// 我们要给 构造函数Animal 添加很多的方法 即: Animal.prototype.aaa = function(){}; Animal.prototype.bbb = function(){}; Animal.prototype.ccc = function(){}; Animal.prototype.ddd = function(){}; Animal.prototype.eee = function(){}; ...... // 诸如这样的话,代码就显得不那么优雅,高效了吧。
-
实现方式?
重新给构造函数的prototype属性(原型对象)赋值,指向一个全新的对象,在这个对象中添加属性和方法,注:
一定要添加一个constructor属性,并且指向构造函数本身 具体看代码:Animal.prototype = { // 一定要指明constructor属性,不然的话,会根据原型链查找一直到Object.prototype constructor : Animal; saying : function(){}, crying : function(){}, doing : function(){} ...... }
- 混入继承
-
混入继承的使用场景:已知对象 o1, o2, 需要把 o1中的功能(属性方法)拷贝到 o2 中去
-
实现方式 :用 for...in... 对 o 进行遍历(可以将混入继承的模式封装成函数),如下所示
// target : 目标对象(接收数据的对象) // source : 接收对象 (数据从哪个对象中来) function mixin(target,source){ for(var key in source){ target[key] = source[key]; } return target; }
-
原理其实很简单,jQuery中的$.extend 方法利用的就是混入继承的原理
- 原型 + 混入继承
-
本质上就是对混入继承的一次运用
-
只不过目标对象为原型对象而已
// 运用上面封装的混入继承的函数 将对象{a:10,b:20,c:function(){}}的功能考本到Animal原型对象上去 mixin(Animal.prototype, {a:10,b:20,c:function(){}} ); // 还可以 这样操作 给Animal构造函数 的原型对象添加一个extend方法,在该方法中调用mixin函数,这样的话也可以实现 Animal.prototype.extend = function(){ mixin(Animal.prototype, source); } // 不过两种方法没有本质之差,看大家易于接受哪个了
- 经典继承 —> 道格拉斯《JavaScript语言精粹》中提到的一种继承模型
-
实现的功能:已知一个对象 o1 需要创建一个新的对象 o2 ,这个新的对象需要继承自对象 o1,代码如下:
// 可以将经典继承 封装成一个函数 function create(o){ function F(){}; // 创建一个构造函数 F.prototype = o; // 将F 的原型指向 对象 o; return new F(); // 将 构造函数的实例 返回出去,这样的话 F的实例对象,就会继承自 o } var o2 = create(o1); // 即:o2 继承于 o1 ; o2.__proto__ = o1;
-
使用场景 :要创建一个对象(不需要关心构造函数),新对象需要继承自另一个指定的对象
-
ES5中 :Object.create( ) 的实现原理就源自于经典继承
- 借用构造函数 实现继承
-
先看一下代码 再解释:
// 需要两个构造函数 function Person(name,age,gender){ this.name = name; this.age = age; this.gender = gender; } // function Student(name,age,){ // this.name = name; // this.age = age; // this.gender = gender; // } // 这样的话 name,age 属性都一样,就会产生重复的代码 我们可以巧妙的利用call 来简单的实现 function Student(name,age){ Person.call(this,name,age); // 其实就是用 call的方法,call借用Person的功能 this.gender = gender; }
-
借用Person中的构造函数的功能,this表示构造函数的实例,即Student的实例对象可以继承name,age属性;
-
借用构造函数实现继承,子构造函数借用父构造函数来完成,给子类Student的实例添加属性
-
注意:由于要借用父类构造函数,所以父类构造函数的功能对子类对象要适用,例如下面情况就最好不用call
// 需要两个构造函数 function Person(name,age,gender){ this.name = name; this.age = age; this.gender = gender; } // function Student(name,age,){ // this.name = name; // this.age = age; // } function Student(name,age){ Person.call(this,name,age); }
-
-
上述情况,就最好不要使用call来借用父类构造函数Person的功能了,因为,gender属性是Student子类构造函数
并不需要的,这样的话,就会在Student中产生不必要的属性和方法,如果子类函数还要有gender方法的话,那么就会和之前的产生冲突,交叉污染 -
其他情况:遇到需要实现功能,该对象上没有这个功能,可以适当地去寻找已经有功能的对象