面向对象编程里面的继承

上一节我简单写了创建对象的集中方法,关于对象,还有一个有趣的概念,那就是继承。

OO语言,Object-Oriented,即面向对象编程。JS实现继承的方式叫做——实现继承,实现继承又是通过原型链来实现的。

  (#`皿´)   吼吼,听听这绕口令,就问你怕不怕!    (#`皿´)   

一、原型链

构造函数,原型和实例的关系,先默写吧。

 每个构造函数,都有一个原型对象。原型对象里包含一个指向构造函数的指针。而实例里包含一个指向原型函数的指针。他们之间的关系可以描述为:实例-->原型对象-->构造函数。

现在,我们假设,某一个原型对象a其实是某个实例类型A。那么,原型对象a(实例A)-->原型对象a的构造函数B,同时也是实例A-->A的原型对象(构造函数B)-->A的构造函数C。这样,一条原型链就出来了。

function SuperType(){
    this.property=true;
}
SuperType.prototype.getSuperValue=function(){
    return this.property;
}
function Sub(){
    this.sub='subValue';
}
Sub.prototype=new SuperType();//通过初始化继承了SuperType
Sub.prototype.getSub=function(){
    return this.sub;
}
var instance=new Sub();
console.log(instance.getSub());//subValue
console.log(instance.getSuperValue());//true

 上面代码可以看到,通过继承,sub拥有了superType的属性和方法。而他的实现方法就是:创建一个SuperType的实例,然后将这个实例赋给sub的prototype属性中! 

还有一个默认的链条。由于所有的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype.这也就是为什么所有的自定义类型都会继承toString()、valueOf()等默认方法的原因。

  

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

第一种方法:采用instanceof操作符。只要用这个操作符测试实例与原型链中出现过的构造函数,结果就会返回true。

console.log(instance instanceof Object);//true
console.log(instance instanceof Sub);//true
console.log(instance instanceof SuperType);//true

第二种方法:isPrototypeOf()方法。注意,顺序反过来。这个方法是属于对象的prototype属性的。

console.log(Object.prototype.isPrototypeOf(instance));//true
console.log(Sub.prototype.isPrototypeOf(instance));//true
console.log(SuperType.prototype.isPrototypeOf(instance));//true

三、原型链存在的问题

function Super(){
    this.colors=['red','blue','pink'];
}
function Sub(){
}
Sub.prototype=new Super();

var obj1=new Sub();
obj1.colors.push('black');
console.log(obj1.colors);//["red", "blue", "pink", "black"]
var obj2=new Sub();
console.log(obj2.colors);//["red", "blue", "pink", "black"]

看见没有,obj1把obj2给玷污了!

包含应用类型值的原型属性会被所有实例共享!所以,这就是我们为什么要在构造函数中定义属性,而不是在原型对象中定义属性。

在通过原型实现继承时,原型实际上会变成另一个类型的实例,于是,原先的是咧也就变成了现在的原型属性了。

四、借用构造函数继承=伪造对象继承=经典继承

为了弥补原型链继承的缺陷,就有了经典继承。基本思想是:在子类型构造函数的内部调用超类型构造函数。

函数是在特定环境中执行代码的对象,通过使用apply()和call()方法,可以在新创建的对象上执行构造函数。 

function Super(){
    this.colors=['red','blue','pink'];
}
function Sub(){
    //用call()方法继承Super
    Super.call(this);
}
var obj1=new Sub();
obj1.colors.push('black');
console.log(obj1.colors);//["red", "blue", "pink", "black"]
var obj2=new Sub();
console.log(obj2.colors);//['red','blue','pink']

 call()方法,“借调”了超类型构造函数。

相对原型链来说,构造函数的优点是:可以在子类型构造函数中向超类型的构造函数传递参数。比如下面代码:

function Super(name){
    this.name=name;
}
function Sub(){
    Super.call(this,'Alice');
    this.age=18;
}
var instance=new Sub();
console.log(instance.name+instance.age);//Alice18

五、组合继承=伪经典继承=原型链+构造函数

组合继承,又叫伪经典继承,将原型链与构造函数组合到一起,实现的继承模式。

function Super(name){
    this.name=name;
    this.color=['red','blue'];
}
Super.prototype.sayName=function(){
    console.log(this.name);
}
function Sub(name,age){
    Super.call(this,name);
    this.age=age;    
}
Sub.prototype=new Super();
Sub.prototype.constructor=Sub;
Sub.prototype.sayAge=function(){
    console.log(this.age);
}
var obj1=new Sub("Alice",18);
obj1.color.push('black');
console.log(obj1.color);//Array(3) ["red", "blue", "black"]
obj1.sayAge();//18
obj1.sayName();//Alice

组合模式避免了两者的缺点,是js中最常用的继承模式。instanceofhe isPrototypeOf()能够识别基于组合继承创建的对象。