JS类的继承

倾然丶 夕夏残阳落幕 提交于 2020-02-08 18:38:51

首先回顾一下构造函数、原型和实例的关系:

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,实例都包含一个指向原型对象的内部指针

 在JS中,实现继承主要是依靠原型链来实现;

基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。

 

一、原型链


1.1 概念

假设让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。

首先原型链的基本模式代码如下

function FatherType(){      
  this.property = true; 
}

FatherType.prototype.getFatherValue = function(){ 
  return this.property; 
}; 

function SonType(){ 
  this.sonproperty = false; 
} 

SonType.prototype = new FatherType();    //继承了 FatherType
SonType.prototype.getsonValue = function (){ 
  return this.sonproperty ; 
};
 
var son1= new SonType(); 
alert(instance.getFatherValue());     //true
以上代码定义了两个类型:FatherType 和 SonType。每个类型分别有一个属性和一个方法。它们 的主要区别是 SonType 继承了 FatherType ,而继承是通过创建 FatherType 的实例,并将该实例赋给 SonType.prototype 实现的。实现的本质是重写原型对象,使其成为一个新类型的实例。换句话说,原来存在于 FatherType 的实例中的所有属性和方法,现在也存在于 SonType.prototype 中了。在确立了 继承关系之后,我们给 SonType.prototype 添加了一个方法,这样就在继承了 FatherType 的属性和方法的基础上又添加了一个新方法。这个例子中的实例以及构造函数和原型之间的关系如图
 
终结果就是这样的:son1 指向 SonType 的原型, SonType 的原型又指向 FatherType 的原型。 getFatherValue() 方法仍然还在 FatherType .prototype 中,但 property 则位于 SonType .prototype 中。这是因为 property 是一 个实例属性,而 getFatherValue()则是一个原型方法。既然 SonType .prototype 现在是 FatherType 的实例,那么 property 当然就位于该实例中了。

 

1.2 原型搜索机制

当以读取模式访问一个实例属性时,首先会在实例中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型

在通过原型实现继承的情况下,搜索过程就要沿着原型链继续向上。就拿图例来说,调用son1.getFatherValue()会精力三个搜索步骤:①搜索实例; ② 搜索 SonType.prototype; ③ 搜索 FatherType.prototype,最后一步才会找到该方法。在找不到属性或或方法的情况下,搜索过程要向上到原型链末端才会停下,若还找不到,返回null

 

1.3 原型链中要注意的三点

1、默认的原型Object

所有引用类型默认继承Object(通过原型链实现)。即所有函数的默认原型都是Object的实例,默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承 toString() 、valueOf() 等默认方法的根本原因。

2、确定原型和实例的关系

有两种方式,instanceof 、isPrototypeOf()

alert(son1 instanceof Object); //true 
alert(son1 instanceof FatherType); //true 
alert(son1 instanceof SonType); //true

alert(Object.prototype.isPrototypeOf(son1)); //true 
alert(FatherType.prototype.isPrototypeOf(son1)); //true 
alert(SonType.prototype.isPrototypeOf(son1)); //true

3、定义在原型上的方法要谨慎

  • 给子类原型添加方法的代码要放在继承父类原型之后,否则方法无效
  • 通过原型链实现继承后,不能使用对象字面量给子类原型上添加方法,这会导致重写原型链,继承无效

 

1.4 原型链实现继承的缺点

1、若父类的原型包含引用类型值的属性,子类的所有实例共享这个属

2、在创建子类型的实例时,不能向父类型的构造函数中传递参数

 

 

二、借用构造函数


概念:在子类型构造函数的内部调用父类型构造函数

function FatherType(){
  this.colors = ["red", "blue", "green"]; 
} 
function SonType(){  
  FatherType.call(this);    //继承了 FatherType
} 

var instance1 = new SonType(); 
sob1.colors.push("black");   
alert(son1.colors); //"red,blue,green,black" 

var son2= new SubType(); 
alert(son2.colors); //"red,blue,green"
在子类构造函数内“借调”了父类构造函数。通过使用 call()方法(apply()方法),我们实际上是在(未来将要)新创建的 SonType 实例的环境下调用了 FatherType 构造函数。 这样一来,就会在新 SonType 对象上执行 FatherType ()函数中定义的所有对象初始化代码。结果, SonType 的每个实例就都会具有自己的 colors 属性的副本了。
 

 

借用构造函数的优缺点

  • 优点:相对原型链,借用构造函数可以在子类构造函数中向父类构造函数传递参数
  • 缺点:方法都在构造函数中定义,因此函数无法复用

 

 

三、组合继承


概念:结合原型链和借用构造函数的一种继承模式

原理:使用原型链实现对原型属性和方法的继承;通过借用构造函数实现对实例属性的继承

function SuperType(name){ 
  this.name = name; 
  this.colors = ["red", "blue", "green"]; 
} 
SuperType.prototype.sayName = function(){ 
  alert(this.name);
}; 
function SubType(name, age){ 
  SuperType.call(this, name);      //继承属性
  this.age = age; 
} 

SubType.prototype = new SuperType();    //继承方法
SubType.prototype.constructor = SubType; 
SubType.prototype.sayAge = function(){ 
  alert(this.age); 
}; 

var instance1 = new SubType("Nicholas", 29); 
instance1.colors.push("black"); 
alert(instance1.colors);          //"red,blue,green,black" 
instance1.sayName();              //"Nicholas"; 
instance1.sayAge();               //29 

var instance2 = new SubType("Greg", 27); 
alert(instance2.colors);          //"red,blue,green" 
instance2.sayName();              //"Greg"; 
instance2.sayAge();               //27

组合继承的优点:结合了原型链和借用构造函数的优点,既通过在原型上sing一方法实现了函数复用,又能保证每个实例都有自己的属性。

 

 

 

 

参考:《JS高级程序设计》

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!