zoukankan      html  css  js  c++  java
  • JavaScript | 继承

    —————————————————————————————————————————————————————————

    继承 - ECMAScript只支持实现继承(依靠原型链),不支持接口继承(函数没有签名)

    原型链

    • 利用原型让一个引用类型继承另一个引用类型的属性和方法,
    • 构造函数、原型、实例的关系:每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针。实例包含一个指向原型对象的内部指针,在创建实例之后即指向原型对象
    • 而当A原型对象的指针指向B个原型对象时(此时A原型对象与B实例同级),就形成了一条原型链。
    • 图解:

      原型搜索机制:当读取模式访问一个实例属性时,首先会在实例中搜索该属性,如果没有找到该属性则沿着原型链向上查找

      在例子<Demo-1>中,调用instance.getSuperValue(),先搜索实例instance,再搜索SubType.prototype,再搜索SuperType.protorype,最后一步才找到该方法。

      默认的原型:所有的引用类型默认都继承了Object,所以默认原型的指针都会指向Object.prototype,完整的原型链如下:

      instance SubType.prototype SuperType.prototype Object.prototype

    • p.s.

      必须替换掉实例的原型后才能给实例添加方法

      不能使用对象字面量创建原型方法,这样做会重写原型链,如<Demo-3>

    • 缺点:

      包含引用类型值(Function Object Array)的原型属性会被所有实例共享,在通过原型来实现继承时,原型实际上会变成另一个类型的实例,所以原先的实例属性就变成了现在的原型属性了。<Demo-4>

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

    // "use strict";
    
    // Demo - 1
    // SuperType 拥有一个属性和一个方法
    // SubType 拥有一个属性和一个方法,又从SuperType那里继承了一个属性一个方法
    function SuperType(){
        this.property = "111";
    }
    SuperType.prototype.getSuperValue = function(){
        return this.property;
    }
    function SubType(){
        this.subproperty = "222";
    }
    // p.s.new操作之前,SubType.prototype指向的是function,不允许为function()定义.getSubValue方法,所以要将添加方法放在修改原型指向之后
    // 操作之后SubType.prototype指向SuperType
    SubType.prototype = new SuperType();
    SubType.prototype.getSubValue = function(){ // 必须在SubType替换原型之后才能定义
        return this.subproperty;
    }
    var instance = new SubType();
    console.log(instance.property); // 111
    console.log(instance.getSuperValue()); // 111
    console.log(instance.subproperty); // 222
    console.log(instance.getSubValue()); // 222
    console.log(instance.constructor); // f SuperType(){} 原本SubType中的constructor属性被重写
    // 重写SuperType.getSuperValue()
    // 如果要重写这个方法,会屏蔽原来的方法
    // 换句话说,当通过SubType的实例调用getSuperValue时调用的就是这个重新定义的方法,但通过SuperType的实例调用时还会继续调用原来的方法
    var beforeReWrite = new SuperType();
    SuperType.prototype.getSuperValue = function(){
        console.log("rewrite");
    }
    console.log(instance.getSuperValue()); // rewrite,this.property = undefined
    console.log(SuperType.prototype.getSuperValue()); // rewrite,this.property = undefined
    console.log(beforeReWrite.getSuperValue());
    
    // Demo - 2
    // 确认原型和实例的关系
    console.log(instance instanceof Object); // true
    console.log(instance instanceof SuperType); // true
    console.log(instance instanceof SubType); // true
    // 另一种方法
    console.log(Object.prototype.isPrototypeOf(instance)); // true
    console.log(SuperType.prototype.isPrototypeOf(instance)); // true
    console.log(SubType.prototype.isPrototypeOf(instance)); // true
    
    // Demo - 3
    function SuperType2(){
        this.property = "1111";
    }
    SuperType2.prototype.getSuperValue = function(){
        return this.property;
    }
    function SubType2(){
        this.subproperty = "2222";
    }
    SubType2.prototype = new SuperType2();
    SubType2.prototype = {
        getSubValue:function(){
            return this.subproperty;
        },
        someOtherMethod:function(){
            return false;
        }
    }
    var instance2 = new SubType2();
    console.log(instance2 instanceof Object); // true
    console.log(instance2 instanceof SuperType2); // false,原型链被切断
    console.log(instance2 instanceof SubType2); // true
    // console.log(instance2.getSuperValue()); // error
    
    // Demo - 4
    function SuperType3(){
        this.colors = ["red","blue","green"];
    }
    function SubType3(){}
    SubType3.prototype = new SuperType3();
    var instance3 = new SubType3();
    instance3.colors.push("black");
    console.log(instance3.colors); // ["red", "blue", "green", "black"]
    var instance4 = new SubType3();
    console.log(instance4.colors); // ["red", "blue", "green", "black"]

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

    • 在子类型构造函数的内部调用超类型构造函数
    • 优点:

      解决了单独使用原型链共享引用类型值属性的问题

      可以在子类型构造函数中向超类型构造函数传递参数

    • 缺点:

      无法避免构造函数模式存在的问题:方法都在构造函数中定义,无法实现函数复用

    // "use strict";
    
    function SuperType(name) {
        this.name = name;
        this.colors = ["111", "222", "333"];
    }
    
    function SubType() {
        SuperType.call(this, "name1");
        this.age = 20;
    }
    
    var instance = new SubType();
    instance.colors.push("444");
    console.log(instance.colors); // ["111", "222", "333", "444"]
    console.log(instance.name); // name1
    console.log(instance.age); // 20
    var instance2 = new SubType();
    console.log(instance2.colors); // ["111", "222", "333"]

    组合继承(伪经典继承)

    • 将原型链和借用构造函数组合,使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承
    • 对应创建对象 <组合使用构造函数模式和原型模式>
    • 优点:最常用
    • 缺点:需要调用两次超类型构造函数,一次在创建子函数原型时,另一次在子函数构造函数内部。调用子类型构造函数时需要重写属性
    // "use strict";
    function SuperType(name) {
        this.name = name;
        this.colors = ["111", "222", "333"];
    }
    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("hugh", 20);
    instance1.colors.push("444");
    console.log(instance1.colors); // ["111", "222", "333", "444"]
    instance1.sayName(); // hugh
    instance1.sayAge(); // 20
    var instance2 = new SubType("dong", 21);
    console.log(instance2.colors); // ["111", "222", "333"]
    instance2.sayName(); // dong
    instance2.sayAge(); // 21

    原型式继承

    • 对应创建对象 <动态原型模式>
    • 没有使用严格意义上的构造函数,借助已有的对象创建新对象
    • 优点:

      在不想创建构造函数,只想让一个对象与另一个对象保持类似的情况下,原型式继承完全可以胜任

    • 缺点:

      包含引用类型值的属性始终都会共享,就像原型模式一样

    // "use strict";
    function object(o){
        function F(){} // 创建临时性构造函数
        F.prototype = o; // 将传入的对象作为构造函数的原型
        return new F(); // 返回临时类型的一个新实例
    }
    
    var person = {
        name:"hugh",
        friends:["111",'222','333']
    };
    
    var anotherPerson = object(person);
    anotherPerson.name = "dong";
    anotherPerson.friends.push("444");
    
    var yetAnotherPerson = object(person);
    yetAnotherPerson.name = "hehe";
    yetAnotherPerson.friends.push("555");
    
    console.log(person.friends); // ["111", "222", "333", "444", "555"]
    console.log(person.name); // hugh
    console.log(anotherPerson.friends); // ["111", "222", "333", "444", "555"]
    console.log(anotherPerson.name); // dong
    console.log(yetAnotherPerson.friends); // ["111", "222", "333", "444", "555"]
    console.log(yetAnotherPerson.name); // hehe
    
    // 使用Object.create()规范化原型式继承
    // 以这种方式指定的任何属性都会覆盖原型对象上的同名属性
    var otherPerson1 = Object.create(person);
    otherPerson1.friends.push("666");
    console.log(yetAnotherPerson.friends); // ["111", "222", "333", "444", "555", "666"]
    var otherPerson2 = Object.create(person,{
        name:{
            value:"test"
        }
    });
    console.log(otherPerson2.name);

    寄生式继承

    • 对应创建对象 <寄生构造函数 / 工厂模式>
    • 创建一个仅用于封装继承过程的函数,在内部增强对象,最后返回对象
    • 示范集成模式时使用的object()函数不是必须的,任何能够返回新对象的函数都适用于此模式
    • 使用场景:在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式
    • 缺点:无法做到函数复用,类似于构造函数模式
    // "use strict";
    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: "hugh",
        friends: ['111', '222', '333']
    };
    var person1 = createAnother(person);
    person1.sayHi();
    console.log(person1.name);
    console.log(person1.friends);

    寄生组合式继承

    • 优点:

      最理想的继承范式

      解决组合继承重写属性的问题,只调用了一次SuperType构造函数

      避免了在SubType.prototype上创建不必要的属性

      原型链保持不变

      能够正常使用instanceofisPrototypeOf()

    "use strict";
    function object(o) {
        function F() {}
        F.prototype = o;
        return new F();
    }
    
    // 1.创建超类型原型的一个副本
    // 2.为创建的副本添加constructor属性,弥补因重写原型而失去的属性
    // 3.将新创建的对象(即副本)赋值给子类型的原型
    function inheritProtoType(subType,superType){
        var prototype = object(superType.prototype); // 创建对象
        prototype.constructor = subType; // 增强对象
        subType.prototype = prototype; // 指定对象
    }
    function SuperType(name){
        this.name = name;
        this.colors= [1,2,3,4];
    }
    SuperType.prototype.sayName = function(){
        console.log(this.name);
    }
    function SubType(name,age){
        SuperType.call(this,name);
        this.age = age;
    }
    inheritProtoType(SubType,SuperType);
    SubType.prototype.sayAge = function(){
        console.log(this.age);
    }

  • 相关阅读:
    Bundles
    使用二进制协议 (附源码)
    河内之塔 算法
    什么是DCI
    C#利用ODP.NET往oracle中高效插入百万数据
    分析Sizzle引擎
    data格式加载图片
    jQuery获取checkbox选中项等操作及注意事项
    日期类型函数转换的特殊性
    QT中代码中与设计器中控件信号与SLOT连接(原来还可以这样连接)
  • 原文地址:https://www.cnblogs.com/hughdong/p/7264122.html
Copyright © 2011-2022 走看看