JavaScript继承(图解笔记)

JavaScript有多种继承模式,总结起来用到的方法有:原型链的传递、构造函数的借用、对象的复制。


JavaScript继承(图解笔记)_第1张图片

对于原型链和原型的解释,可参考:JavaScript 原型中的哲学思想
这篇文章讲得很清晰,让我们明白:所有JS对象源于null,并通过原型指针和原型对象来实现继承。

构造函数和原型对象的关系如下:


JavaScript继承(图解笔记)_第2张图片

每个构造函数都有一个prototype属性,指向函数的原型对象;原型对象中又有一个constructor属性,重新指向构造函数。而对象实例中有一个原型指针[[prototype]](在Firefox、Safari和Chrome中,对应属性proto),指向原型对象。
明白原型之后,就进入正题继承了。

1.原型链继承

function SuperType(){
    this.superproperty = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.superproperty;
}
function SubType(){
    this.subproperty = false;
}
SubType.prototype = new SuperType(); //原型链继承
SubType.prototype.getSubValue =function(){
    return this.subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue());//true

JavaScript继承(图解笔记)_第3张图片

通过SubType.prototype=new SuperType(),实现了以下三点:
A:重写了SubType的原型,让子类原型和子类构造函数之间断开联系。
B:子类原型是父类的实例,其原型指针[[prototype]]指向了父类的原型对象,这样子类就可以沿着原型链访问到父类的方法getSuperValue()。
C:子类原型是父类实例,通过父类构造函数,子类原型继承了父类的属性superproperty。
最终,子类继承了父类的方法和属性。
原型链继承的问题:
父类的实例属性成了子类的原型属性,如上面的superproperty,会被子类所有实例共享。该属性是基本类型值时没有问题,但如果是引用类型值(比如数组),那么修改实例1的该属性(比如向数组push一个新值),实例2也会跟着改变。
也就是说,实例们只有共性,不能保持个性。

2.构造函数继承

function SuperType(name){ 
    this.name = name;
}
function SubType(){
    //继承了SuperType,同时还传递了参数 
    SuperType.call(this,"Nicholas");
    this.age = 29;
}
var instance = new SubType();
alert(instance.name);  //"Nicholas"
alert(instance.age);  //29
JavaScript继承(图解笔记)_第4张图片

通过SuperType.call(this),在子类构造函数中调用了父类的构造函数,创建子类实例会执行子类的构造函数(含父类的构造函数),也就完成了继承。当然,子类和父类的原型是没有关系的,子类实例不能访问父类原型对象中的属性和方法。
构造函数继承的问题:
方法在构造函数中定义,无法实现函数复用。比如父类中有一个方法getName(),则每次创建子类实例的时候,都要创建一个新的getName(),通过instance1.getName() !== instance.getName()就可以验证这一点。
这就是说,实例们保持了个性,却不能共享方法。

3.组合继承

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);      //第二次调用SuperTyper()
    this.age = age;
}
//继承方法
SubType.prototype = new SuperType(); //第一次调用SuperTyper()
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
JavaScript继承(图解笔记)_第5张图片

通过借用构造函数来继承属性,原型链来继承方法。结合了两者的优点,让实例们即保持个性,又共享方法。
组合继承的问题:
两次调用父类的构造函数。第一次(A):SubType.prototype=new SuperType(),子类原型对象取得了父类的实例属性。第二次(B):
SuperType.call(this),创建子类实例时,调用父类构造函数,重写实例属性,屏蔽了原型对象上同名属性。

4.原型式继承

function object(o){ 
    function F(){};
    F.prototype = o; 
    return new F();
}
var person = { 
    name:"Nicholas", 
    friends:["Shelby","Court","Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

alert(person.friends);  //"Shelby,Court,Van,Rob,Barbie"
JavaScript继承(图解笔记)_第6张图片

以一个对象为基础,通过object()函数进行浅复制,再将得到的对象实例加以修改。
可以看到,这种继承方法是没有父类和子类的,只是通过复制对象来得到副本。
ES5有object.create()方法,是object()的规范化,可以传入两个参数:要复制的对象和额外的属性对象(如{name:{value:Greg}},这种方式会覆盖基础对象上的同名属性)。
原型式继承的问题:
和原型链继承一样,继承的属性由所有实例共享,改动一个实例的引用类型值时,所有实例都会改变。

5.寄生式继承

function createAnother(original){
    var clone = object(original);//object()函数创建对象
    clone.sayHi = function(){    //增强这个对象
        alert("hi");
    };
    return clone;                //返回这个对象
}
var person = {
    name:"Nicholas";
    friends:["Shelby","Court","Van"];
}     //基础对象
var anotherPerson = createAnother(person);  //新对象
anotherPerson.sayHi();   //"hi"

寄生式和原型式方法相同,都是复制一个基础对象来得到新对象,不同的是它将对象实例的修改放到也放到函数中,将整个过程(创建、增强、返回)封装了起来。

6.寄生组合式继承

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);   //只调用一次SuperTyper()
    this.age = age;
}
//继承方法
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
   alert(this.age);
}
JavaScript继承(图解笔记)_第7张图片

顾名思义,这种继承模式就是寄生式(复制)+组合式(原型链+构造函数),将几种方法组合起来。解决了组合式继承两次调用父类构造函数的问题。

代码来源:
《JavaScript 高级程序设计》

你可能感兴趣的:(JavaScript继承(图解笔记))