js中的继承
发布于 5 年前 作者 songjun 292 次浏览 来自 分享

继承

OO语言分为接口继承和实现继承,ECMAScript支持实现继承,主要是依靠原型链来实现的

1 原型链

利用原型,让一个引用类型继承令一个引用类型的属性和方法
实现原型链的一种模式:
   function SuperType(){
       this.property = true;
   }
   SuperType.prototype.getSuperValue = function(){
       return this.property;
   }
   function SubType(){
        this.subProperty = false;
   }
   SubType.prototype = new SuperType();
   let instance = new SubType();
   console.log(instance.getSuperValue())  // true

1.1 别忘记默认的原型 Object

1.2 确定原型和实例的关系

    只要是原型链中出现的原型 都可以说是该原型链派生的实例的原型
        instanceOf
            console.log(instance instanceof Object) //true
            console.log(instance instanceof SuperType) // true
            console.log(instance instanceof SubType)  // true
        isPropertytypeOf
            console.log(Object.prototype.isPrototypeOf(instance)) //true
            console.log(SubType.prototype.isPrototypeOf(instance)) //true
            console.log(SuperType.prototype.isPrototypeOf(instance)) //true

1.3 谨慎的定义方法

    给原型添加的方法一定要放在替换原型的语句之后
    注意尽量不要重写原型
        如: Person.propertype = { }  // 这样会重写原型 谨慎操作

1.4 原型链的问题

1 由于 包含引用类型值的原型属性会被所有实例共享

    用代码说明问题
        function SuperType(){
            this.colors = ['red' , 'yellow']
        }
        function SubType(){ }
        SubType.prototype = new SuperType();

        let instance1 = new SubType();
        instance1.colors.push('black');
        console.log(instance1.colors)  // ["red", "yellow", "black"]

        let instance2 = new SubType();
        console.log(instance2.colors)  // ["red", "yellow", "black"]
        // 两个实例公用一个colors了
2 在创建子类型时的实例时,没法在不影响所有对象实例的情况下 向超类型的构造函数中传递参数

所以,实践中很少会单独使用原型链

2 借用构造函数

即在子类型构造函数内部调用超类型构造函数  call apply
为了解决原型中包含引用类型值的问题
    function SuperType(colors){
        this.colors = colors;
    }
    function SubType(colors){
        SuperType.call(this, colors);
        this.age = 10;
    }
    let instance1 = new SubType(['1' , '2']);
    console.log(instance1.colors)
问题:方法都在构造函数中定义,所以函数复用也无从谈起
      且超类型中定义的方法,对子类型而言也是不可见的,结果所有类型只能使用构造函数模式

3 组合继承

将原型链与组合继承结合的一种集成方法
使用原型链实现对原型属性和方法的继承,通过构造函数实现对实例属性的继承
    // 组合继承
    // 使用原型链实现原型属性和方法的继承 使用构造函数实现实例属性的继承
    function SuperType(name){
        this.name = name;
        this.colors = [1,2,3]
    }
    SuperType.prototype.sayName = function(){
        console.log(this.name)
    }
    function SubType(name , age){
        this.age = age;
        SuperType.call(this , name)
    }
    // 继承方法
    SubType.prototype = new SuperType();
    SubType.prototype.sayAge = function(){
        console.log(this.age)
    }
    let a = new SubType('a' , 10)
    a.colors.push(4)
    console.log(a.colors)
    a.sayName()
    a.sayAge();

    let b = new SubType('b' , 10)
    console.log(b.colors)
    b.sayName()
    b.sayAge();

4 原型式继承

借助原型可以基于已有的对象创建新对象,同时还不必创建自定义类型
这种原型式继承,必须有一个对象作为另一个对象的基础,把它传进object()函数,然后根据需求对得到的对象加以修改
    function object(o){
        function F(){}
        F.prototype = o;
        return new F();
    }
    // 例子
    let person = {
        name: 'nicolas',
        friends: ['jobs']
    }

    let person1 = object(person);
    person1.name = 'p1';
    // person1.friends.push('p1fw')
    person1.friends = [1,2,3]

    let person2 = object(person);
    person2.name = 'p2';
    person2.friends.push('p2fw')

    console.log(person)
    console.log(person1)
    console.log(person2)
不过,ES5通过新增Object.create()方法规范了原型式继承,这个方法接受两个参数,一个用作新对象原型的对象和(可选择的)一个为新对象定义额外属性的对象
在传入一个参数的情况下,Object.create()作用与上面object()作用相同
Object.create()第二个参数与Object.defineProperties()方法第二个参数格式相同
    let person = {
        name: 'jobs',
        friends: ['f']
    }
    let person1 = Object.create(person , {
        name:{
            value: 'jack'
        },
        friends:{
            value: ['d']
        }
    })
    console.log(person1)
在没有必要创建构造函数,而只想让一个对象与另一个对象类似的情况下,原型式继承是完全可以胜任的,但要注意包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。

5. 寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,然后返回一个对象
        function object(o){
            function F(){}
            F.prototype = o;
            return new F();
        }

        function createAnother(origin){
            let clone = object(origin);  // 通过调用函数创建一个新对象
            clone.sayHi = function(){    // 增强这个新对象
                console.log('hi')
            }
            return clone;                 // 返回创建的对象
        }

        let person = {
            name: 'a'
        }
        let anotherPerson = createAnother(person);
        anotherPerson.sayHi()
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似

6 寄生组合式继承

前面说过,组合式继承是最常用的继承方式,不过组合继承最大的问题是无论什么情况下,都会调用两次超类型的构造函数
        // 这是组合继承
        function SuperType(name){
            this.name = name
        }
        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()
        // 两次调用SuperType()的后果是 SubType()里会有两个name属性,一个在实例上,一个在SubType()原型上
        SubType.prototype.constructor = SubType;
        SubType.prototype.sayAge = function(){
            console.log(this.age)
        }
        let sub = new SubType('wu' , 20);
        console.log(sub)
        sub.sayAge()
        sub.sayName()

        
        // 所以用寄生组合式继承解决这个问题
        // 即 借用构造函数来继承属性,用原型链来继承方法
        function SuperType(name){
            this.name = name;
        }
        SuperType.prototype.sayName = function(){
            console.log(this.name)
        }
        function SubType(name , age){
            this.age = age;
            SuperType.call(this, name)
        }
        // inheritPrototype()函数实现了寄生组合式继承的最简单形式
        function inheritPrototype(SubType , SuperType){
            // 第一步 创建对象 创建超类型原型的一个副本
            let prototype = Object.create(SuperType.prototype); 
            // 第二步 为创建的副本添加constructor 属性,弥补因重写原型原型而失去默认的constructor属性
            prototype.constructor = SubType;
            // 第三步 将创建的对象赋值给子类型的原型
            SubType.prototype = prototype;
        }
        inheritPrototype(SubType , SuperType);
        SubType.prototype.sayAge = function(){
            console.log(this.age)
        }

        let sub = new SubType('wang' , 33);
        console.log(sub)
        sub.sayName()
        sub.sayAge()   
``
回到顶部