1

JS继承

原型链

构造函数、原型、实例的关系

每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例包含一个指向构造函数的指针。
原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现。

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

SubType继承SuperType是通过创建SuperType实例,并将该实例赋给SubType.prototype实现的。本质是重写原型对象,代之以一个新类型的实例。
instance指向SubType的原型,SubType的原型又指向SuperType的原型。要注意的是instance.constructor现在指向的是SuperType,应为SubType的原型指向了SuperType的原型,而该原型对象的constructor属性指向的SuperType。

原型搜索机制

调用instance.getSuperValue()会经历一下三个步骤:
1)搜索实例
2)搜索SubType.prototype
3)搜索SuperType.prototype,最后一步才会找到该方法。在找不到属性或方法的情况下,搜索过程总要一环一环地前行到原型链末端为止。

确定原型与实例的关系

1.instanceof
instance instanceof Object
2.isPrototypeOf
Object.prototype.isPrototypeOf(instance)

定义方法

1.子类型需要覆盖超类型中的某个方法,或需要添加超类型中不存在的某个方法。给原型添加方法的代码要放在替换原型的语句后面。

    function SuperType(){
        this.property=true;
    }
    SuperType.prototype.getSuperValue=function () {
        return this.property;
    };
    function SubType(){
        this.subproperty=false;
    }
    //继承SuperType
    SubType.prototype=new SuperType();
    //添加新方法
    SuperType.prototype.getSubValue=function () {
        return this.subproperty;
    }
    //重写超类型中的方法
    SubType.prototype.getSuperValue=function () {
        return false;
    }
    var instance=new SubType();
    console.log(instance.getSuperValue());

2.通过原型链实现继承时,不能使用对象字面量创建原型方法。这样会重写原型链。

    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();
    console.log(instance.getSuperValue());  //error

以上代码先把SuperType的实例赋值给原型,紧接着又将原型替换成一个对象字面量。由于现在的原型包含的是一个Object实例,而非SuperType的实例。因此,原来的SuperType和SubType之间的原型链被切断。

原型链的问题

1.包含引用类型的原型属性会被所有实例共享。

    function SuperType(){
        this.nums=[1,2];
    }
    function SubType(){

    }
    //继承SuperType
    SubType.prototype=new SuperType();
    var instance=new SubType();
    instance.nums.push(5);
    console.log(instance.nums);  //[1,2,5]
    var instance1=new SubType();
    instance1.nums.push(0);
    console.log(instance1.nums);  //[1,2,5,0]

2.创建子类型的实例时,不能向超类型的构造函数中传递参数。

借用构造函数(伪造对象/经典继承)

在子类型构造函数内部调用超类型构造函数。

function SuperType(){
    this.nums=[1,2];
}
function SubType() {
    SuperType.call(this);
}
SubType.prototype=new SubType();
var instance=new SubType();
instance.nums.push(3);
console.log(instance.nums);

var instance2=new SubType();
console.log(instance2.nums);

通过使用call()方法(或apply()方法),在新创建的SubType实例的环境下调用了SuperType构造函数。这样就会在新SubType对象上执行SuperType函数中定义的所有对象初始化代码。SubType的每个实例都会具有自己的nums属性的副本。
1.传递参数
子类型构造函数向超类型构造函数传递参数。

function SuperType(name){
    this.name=name;
    this.school=['whu'];
}
function SubType() {
    SuperType.call(this,"张三");
    //实例属性
    this.age=20;
}
var instance=new SubType();
instance.school.push('hhu');
console.log(instance.name);
console.log(instance.age);
console.log(instance.school);
var instance2=new SubType();
console.log(instance2.school);

2.存在的问题
方法都在构造函数中定义,函数无法复用;
在超类型的原型中定义的方法,对于子类型而言是不可见的,那么所有类型都只能使用构造函数模式。

组合继承

使用原型链实现对原型属性和方法的继承,而通过构造函数来实现实例属性的继承。

function SuperType(name){
    this.name=name;
    this.city=['武汉','杭州'];
}
SuperType.prototype.sayName=function () {
    console.log(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 () {
    console.log(this.age);
}

var instance1=new SubType('chen',18);
instance1.city.push('北京');
console.log(instance1.city);
instance1.sayName();
instance1.sayAge();
var instance2=new SubType('huang',19);
console.log(instance2.city);
instance2.sayName();
instance2.sayAge();

原型式继承

function object(o){
    function F(){}
    F.prototype=o;
    return new F();
}

在object函数内部,先创建一个临时性的构造函数,然后将传入对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。本质上是object()对传入其中的对象执行了一次浅复制。

var navy={
    name:'海军',
    weapon:['航母','驱逐舰']
};
function object(o){
    function F() {

    }
    F.prototype=o;
    return new F();
}
var navy1=object(navy);
navy1.name="俄罗斯";
navy1.weapon.push('巡洋舰');
var navy2=object(navy);
navy2.name='美国';
navy2.weapon.push('护卫舰');
console.log(navy.weapon);

ECMAScript5新增Object.create()方法规范了原型式继承,接收两个参数:一个是作用于新对象原型的对象,(可选)二是一个新对象定义额外属性的对象.

var car={
    name:'奔驰',
    weapon:['车轮','发动机']
};
var car1=Object.create(car,{
    name:{
        value:'宝马'
    }
});
console.log(car1.name);

原型式的问题依然是包含引用类型的属性会所有实例被共享

寄生式继承

    function object(o){
        function F() {

        }
        F.prototype=o;
        return new F();
    }
    function createAnother(original) {
        var clone=object(original);  //通过调用函数创建一个新对象
        clone.sayHi=function () {   //以某种方式增强这个对象
            console.log('hi');
        };
        return clone;   //返回对象
    }
    var person={
        name:'coder',
        ability:['Java','R']
    };
    var another=createAnother(person);
    another.sayHi();

寄生式的问题依然是不能做到函数复用

寄生组合式继承

组合继承最大的问题就是会调用两次超类型构造函数:一次是创建子类型原型时候,一次是在子类型构造函数内部。

    function SuperType(name){
        this.name=name;
        this.city=['南京','苏州'];
    }
    SuperType.prototype.sayName=function () {
        console.log(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 () {
        console.log(this.age);
    }

在第一次调用SuperType构造函数时,SubType.prototype会得到两个属性:name和colors;都是SuperType的实例属性,只不过现在位于SubType的原型中.当调用SubType构造函数,又会调用一次SuperType的构造函数,这一次又在新对象上创建了实例属性name和colors.于是这两个属性就屏蔽了原型中的两个同名属性.
寄生组合式继承通过借用构造函数来继承属性,通过原型链的混成形式来继承方法.其基本思想是:不必为了指定子类型的原型而调用超类型的构造函数,所需要的只是超类型原型的一个副本.本质上,就是使用寄生式继承来继承超类型的原型,然后再讲结果指定给子类型的原型.

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

inheritPrototype函数接受两个参数:子类型构造函数、超类型构造函数
1.创建超类型原型的一个副本
2.为创建的副本添加constructor属性,弥补因重写原型而失去的默认的constructor属性.
3.将新创建的对象(即副本)赋值给子类型的原型.

    function SuperType(name){
        this.name=name;
        this.city=['南京','苏州'];
    }
    SuperType.prototype.sayName=function () {
        console.log(this.name);
    }
    function SubType(name,age) {
        SuperType.call(this,name);//第二次调用SuperType
        this.age=age;
    }
    inheritPrototype(SubType,SubType);
    SubType.prototype.sayAge=function () {
        console.log(this.age);
    }

上述只调用了一次SuperType构造函数,并因此避免了在SubType.ptototype上创建不必要的、多余的属性.与此同时,原型链还能保持不变.


啊哈hl
141 声望1 粉丝

be a better man