diy1818

Good children, Constantly learning, Constantly growing.

JavaScript-继承

原型链

原型链是JS继承的主要方法,利用原型让 一个引用类型继承另一个引用类型的属性和方法.
构造函数,原型,实例的关系: 第个构造函数都有珍上原型对象(prototype),原型对象都包含一个 指向构造函数的指针,而实例都包含一个指向原型对象的内部指针.如下:

    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();
    alert(instance.getSuperValue());//true;

上面代码定义了两个类型:SuperType和SubType.SubType继承了SuperType, 而继承是通过创建SuperType的实例,并奖该实例赋给SubType.prototype实现的. 实现的本质是重写原型对象,代之以一个新类型的实例 .原来存在于SuperType的实例中的所有属性和方法,现在也存在于SubType.prototype中. 新原型不仅具有作为一个SuperType的实例所拥有的全部属性和方法,而且其 内部还有一个指针,指向了 SuperType的原型.

instance指向SubType的原型,SubType的原型又指向SuperType的原型.getSuperValue()方法仍然还在 SuperType.prototype中,但property则位于SubType.prototype中.这是因为 property是一个实例属性,而getSuperValue()则是一个原型方法. 即然SubType.prototype现在是SuperType的实例,那么property当然就位于该实例中了. instance.constructor现在指向的是SuperType,这是因为原来SubType.prototype中的 constructor被重写了的缘故.

通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上.上面的例子来说,调用 instance.getSuperValue()会经历三个搜索步骤:1>搜索实例;2>搜索SubType.prototype;3>搜索SuperType.prototype,最 后一步才会找到该方法.

谨慎地定义方法

子类型有时候需要重写超类型中的某个方法,或者需要添加超类型中不存在的某个方法. 给原型添加方法的代码一定要放在替换原型的语句之后. 如下:

   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;
    };
    //重写超类型中的方法,会屏蔽原型链中已经存在的那个方法.
    SubType.prototype.getSuperValue= function(){
        return false;
    };
    var instance = new SubType();
    alert(instance.getSuperValue());//false

    注意:在通过原型链实现继承时,不能使用对象字面量创建原型方法.因为这样做就会重写原型链.会报错.
//使用字面量添加新方法,会导致上一行代码无效
SubType.prototype = {
    getSubValue : function (){
        return this.subproperty;
    },
    someOtherMethod : function (){
        return false;
    }
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!

原型链的问题

原型链很强大,可以用来实现继承,最主要的问题来自包含引用类型值的原型. 包含引用类型的原型属性会被所有实例共享;而这也为什么要在构造函数中,而 不是在原型对象中定义属性的原因.

借用构造函数

在解决原型中包含引用类型值所带来问题的过程中,可以使用一种叫做借用构造函数的技术. (有时也叫做伪造对象或经典继承). 在子类型构造函数的内部调用超类型构造函数.函数虽在特定环境中执行代码的对象, 因此通过使用apply()和call()方法可以在(将来)新创建的对象上执行构造函数.

    function SuperType(){
        this.colors = ['r','b','g'];
    }
    function SubType(){
        //继承了SuperType
        SuperType.call(this);
    }
    var instance1 = new SubType();
    instance1.colors.push('c');
    alert(instance1.colors);
    var instance2 = new SubType();
    alert(instance2.colors);

上代码中通过call()方法(或apply()方法)借调了超类型的构造函数. 传递参数,可以在子类型构造函数中向超类型构造函数传递参数.如下:

    function SuperType(name){
        this.name = name;
    }
    function subType(){
        //继承了SuperType ,同时还传递了参数
        SuperType.call(this,'anry');
        //实例属性
        this.age = 27;
    }

    var instance = new subType();
    alert(instance.name);//'anry'
    alert(instance.age); //27

借用构造函数的问题

因为借用构造函数,方法都在构造函数中定义,因此函数就无法复用.而且在超类型的原型中定义 的方法, 对子类型来说是不可见的,结果 所有类型都只能使用构造函数模式.因此,借用构造函数的技术 也是很少单独使用的.

组合继承

指是的将原型链和借用构造函数的技术 组合到一块,从而发挥二者一种继承模式. 使用原型链视而不见对原型要属性和方法的继承,而通过借用构造函数来实现对 实例属性的继承.这样即通过在原型上定义方法实现了函数利用,又能够保证每个实例都有它自己的属性. 如下:

    function SuperType(name){
        this.name = name;
        this.colors = ['r','b','g'];
    }
    SuperType.prototype.sayName = function(){
        alert(this.name);
    };

    function SubType(name,age){
        //继承了SuperType ,同时还传递了参数
        SuperType.call(this,name);
        //实例属性
        this.age = age;
    }
    //继承方法
    SubType.prototype = new SuperType();
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function(){
        alert(this.age);
    };

    var instance1 = new SubType('anry',27);
    instance1.colors.push('c');

    alert(instance1.colors);//r,b,g,c
    instance1.sayName();//anry
    instance1.sayAge();//27

    var instance2 = new SubType('co',23);
    alert(instance2.colors);//r,b,g
    instance2.sayName();//co
    instance2.sayAge();//23

组合继承避免了原型链和借用构造函数的缺陷,成为javascript中最常用的继承模式.

原型式继承

没有使用严格意义上的构造函数,借助原型可以基于已有的对象创建新对象,现时还不必因此创建自定义类型.

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

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

    var person = {
        name: "Nicholas",
        friends: ["Shelby", "Court", "Van"]
    };
    var anotherPerson = Object.create(person);
    anotherPerson.name = "Greg";
    anotherPerson.friends.push("Rob");
    var yetAnotherPerson = Object.create(person);
    yetAnotherPerson.name = "Linda";
    yetAnotherPerson.friends.push("Barbie");
    alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"

Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式 相同 . 每个属性都是通过自己的描述符定义的, 以这种方式指定的任何属性都会覆盖原型对象上的同名属性,如下

    var person={
        name:'anry',
        friends:['r','b','g']
    }
    var anothPerson = Object.create(person,{
        name:{
            value:'co'
        }
    }
    alert(anothPerson.name);//co

寄生组合式继承

组合继承最大的问题就是无论在什么情况下,都会调用 两次超类型构造函数:一次是在创建子类型原型时候. 另一次是在子类型构造函数内部. 如下:

    function SuperType(name){
        this.name = name;
        this.colors = ["r", "b", "g"];
    }
    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);
    };

    var instance = new SubType('anry','27');
    instance.sayName();
    instance.sayAge();
    console.log(instance.colors);

所谓寄生组合继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法. 基本模式如下:

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

在函数内部,第一部是创建超类型原型的一个副本. 第二部是为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的 constructor属性. 最后一步,将新创建的对象(即副本)赋值给子类型的原型. 这样就可以用inPrototype()函数的语句,去替换前羰例子中为子类型原型赋值的语句了.如下:

    function inPrototype(Sub,Sup){
        var prototype = Object(Sup.prototype);
        prototype.constructor = Sub;
        Sub.prototype = prototype;
    }
    function SuperType(name){
        this.name = name;
        this.colors = ["r", "b", "g"];
    }
    SuperType.prototype.sayName = function(){
        console.log(this.name);
    };
    function SubType(name, age){
        SuperType.call(this, name); //第1次调用SuperType()
        this.age = age;
    }

    inPrototype(SubType,SuperType);

    SubType.prototype.sayAge = function(){
        console.log(this.age);
    };

    var instance = new SubType('anry','27');
    instance.sayName();
    instance.sayAge();
    console.log(instance.colors);

上例中的高效率体现在它只调用了一次SuperType构造函数,与此同时,原型链还能保持 不变,因此,还能够正常使用instanceof和isPrototypeOf().寄生组合式继承是引用 类型最理想的继承范式.

__proto__属性

每个对象都 有一个内部属性__proto__, 指向这个对象的原型对象, 通过这个内部属性,可以从实例对象读取原型对象的属性.

正常情况下,__proto__属性的指向与constructor.prototype属性是一致的.

    Array.prototype.p = 'abc'
    var a = new Array();
    console.log(a.__proto__.p);//abc
    console.log(a.constructor.prototype.p); //abc
    //都是用来读取原型对象,__proto__更简洁

__proto__ 目前还不是标准,但我们有时可以用来帮助理解继承

    var a = {x:1};
    var b = {__proto__:a};
    b.x
    //1

    //b对象本身并没有x属性,
    但是javascript引擎通过__proto__属性,
    找到它的原型对象a,
    然后读取a的x属性.

    //原型对象自己的__proto__属性,也可以指向其它对象,
    从而一级一级地形成"原型链"(prototype chain);

    var a = {x:1};
    var b = {__proto__:a}
    var c = {__proto__:b}

    c.x
    //1

    //空对象的__proto__属性,默认指向Object.prototype.

    var a = {};
    a.__proto__ === Object.prototype;
    //true

    //通过构造函数生成实例对象时,实例对象的__proto__属性
    自动指向构造函数的prototype对象。

    var f = function(){};
    var a = {};
    f.prototype = a;
    var o = new f();
    o.__proto__ === a;
    //true

属性的继承

属性的继承有两种,一种是对象自身的原生属性, 另一种是继承自原型的继承属性。

对象的原生属性

对象的本身的所有属性, 可以用Object.getOwnPrototyNames方法获得。

    Object.getOwnPropertyNames(Date);
//["length", "name", "arguments", "caller", "prototype", "UTC", "parse", "now"]

对象本身的属性中,有的是可以枚举的(enumberable), 有的是不可以枚举的,只获取那些可以枚举的属性,可以使用 Object.keys()方法

    Object.keys(Date);
    // []

    //判断对象是否具有某个属性,使用hasOwnProperty方法
    Date.hasOwnProperty("length");
    //true

    Array.hasOwnProperty('length');
    //true

对象的继承属性

用Object.create方法创造的对象,会继承所有的原型对象的属性

    var proto = {p1: 123};
    var o = Object.create(proto);

    o.p1; //123

    o.hasOwnProperty("p1");
    //false
    //o对象本身没有p1属性

获取所有属性

判断一个对象是否具有某个属性(不管是自身的还是继承的),可以使用in运算符.

    'lenght' in Date;
    //true

    "toString" in Date;
    //true

获得对象的所有可枚举属性(不管是自身的还是继承的),可以使用 for-in循环

    var o1 = {p1:123};
    var o2 = Object.create(o1,{
        p2:{value: "abc", enumerable: true}
    });

    for(p in o2){console.log(p);}
    //p2
    //p1

为了在for...in 循环中获得对象自身的属性,可以采用hasOwnProperty方法判断一下.

    for(var name in Object){
        if(Object.hasOwnProperty(name)){
            console.log(name);
        }
    }

获得对象的所有属性(不管是自身的还是继承的,以及是否可枚举), 可以使用下面的函数.

    function get_proper_name(obj){
        var props = {};
        while(obj){
            Object.getOwnPropertyNames(obj).forEach(function(p){
                props[p] = true;
            });
            obj = Object.getPrototypeOf(obj);
        }
        return Object.getOwnPropertyNames(props);
    }

    get_proper_name({a:1,b:2,c:3});

["a", "b", "c", "constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__lookupGetter__", "__defineSetter__", "__lookupSetter__"]

    get_proper_name(Date);

["__lookupSetter__", "__lookupGetter__", "arguments", "valueOf", "prototype", "length", "propertyIsEnumerable", "hasOwnProperty", "toString", "constructor", "parse", "isPrototypeOf", "UTC", "bind", "__defineSetter__", "apply", "__defineGetter__", "toLocaleString", "caller", "call", "name", "now"]