JavaScript继承

492 阅读5分钟

继承

关于继承是面向对象语言设计最为津津乐道的概念,比如java语言的extends

# 类继承
class 父类 {
}
 
class 子类 extends 父类 {
}
# 接口继承
interface A {
  String s = "A";
}
interface B extends A {
  String s = "B";
}

但是和java不同的是JavaScript实现继承是依靠原型链来实现的

原型链

基本思想是利用原型让一个引用对象继承另一个引用对象的属性和方法,我们首先理一下构造函数,原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针,如下

var p = new Object()
# 原型对象-> __proto__
# 构造函数指针 ->constructor

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

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

SuperType.prototype.getSuperValue = function(){
 return this.property;
};
function SubType(){
 this.subproperty = false;
}
# 继承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
 return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); #true 

上述代码不做解释,其实现的本质莫非于重写原型对象,代之以一个新类型的实例,所有函数的默认原型都是 Object 的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype

确定原型和实例的关系

  • instanceof
  • isPrototypeOf()
子类型有时候需要重写超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎 样,给原型添加方法的代码一定要放在替换原型的语句之后,而且在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链

原型链的问题

  • 来自包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享;而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。

  • 创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上, 应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数


只有思想不滑坡,办法总比困难多,有了问题当然就要想到对应的解决方案如下:

解决问题

借用构造函数

又叫伪造对象或者经典继承,这种方法思想相当简单即在子类型构造函数内部调用超类型构造函数

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

构造继承的最大的缺点就是,虽然父类和子类的属性是互不影响的,但是子类同时拷贝了父类的方法,这些方法应该是通用的,不应该有这么多份相同的,所以引出下面这一种模式:

组合继承

伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式,是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

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 

原型式继承

原型继承ECMAScript 5有一个方法Object.create(),这个方法用于创建一个新对象。被创建的对象继承另一个对象的原型,在创建新对象时可以指定一些属性

// proto: 对象,要继承的原型 
// propertiesObject添加到新创建对象的可枚举属性(即其自身的属性,而不是原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数

Object.create(proto[propertiesObject]) 

使用例子

// 基类
function User() {
  this.name = '谭婧杰';
}

User.prototype.create = function(name) {
  this.name = name;
};


User1.prototype = Object.create(User.prototype);
var user1 = new User1();

console.log(user1 instanceof User);  // true
console.log(user1 instanceof User1);  // true

寄生式继承

创建一个只用于封装继承过程的函数,这个函数在内部以一种形式来增强对象

// 代码来源https://www.cnblogs.com/czhyuwj/p/5616585.html
function object(o){
   function F(){

   }
  F.prototype=o;
   return new F();
 };



function createAnother(original){
    var clone = object(original); // 通过调用函数创建一个新对象
    clone.sayHi = function(){ //以某种方式增强真个对象
        alert("hi");
    }
    return clone; //返回这个对象
}

 var person={
            name:"Nicholas",
            friends:["Shelby","Court","Van"]
       }
 var now = createAnother(person);
 now.sayHi(); // hi

寄生组合式继承

前面组合继承的不足就是,不管在什么情况下都会调用两次超类型构造函数

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); //第二次调用 SuperType()

 this.age = age;
}
SubType.prototype = new SuperType(); //第一次调用 SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
 alert(this.age);
}; 

寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背 后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型 原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型

function inheritPrototype(subType, superType){
 var prototype = object(superType.prototype); //创建对象
 prototype.constructor = subType; //增强对象
 subType.prototype = prototype; //指定对象
} 

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;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
 alert(this.age);
};