美文网首页Javascript高级程序设计 读书笔记
Javascript 面向对象的程序设计(原型链与继承)

Javascript 面向对象的程序设计(原型链与继承)

作者: Sue1024 | 来源:发表于2018-03-08 22:47 被阅读36次

    继承

    原型链

    讲原型的时候提到过继承,设计原型的初衷就是为了继承,原型链是实现继承的主要方法。
    那什么是原型链,还记得之前提到过的作用域链吗,它表示标识符在环境中的查找顺序,原型链与作用域链相似,它表示属性或方法在实例和构造函数间的追溯顺序,看一个例子:

    function Father() {
    }
    Father.prototype.familyName = "Zhao";
    function Child() {
    }
    var father = new Father();
    Child.prototype = father;
    var me = new Child();
    me.familyName;
    // "Zhao"
    

    当我们在me实例上访问familyName属性时,搜索过程会从原型链的末端开始逐步向上,即:
    me实例 → Child原型 → Father原型
    默认的原型

    由于所有引用类型都继承了Object 因此所有引用类型原型链的顶端都是Object.prototype,因此所有自定义类型都会继承toString() valueOf()等默认方法。

    确定原型和实例的关系
    1. instanceof
      用这个操作符测试实例和原型链上出现的构造函数,即返回true
    2. isPropertyOf
      用这个方法测试原型链中出现过的原型,即返回true
    谨慎地定义方法

    在上面的例子中,我们有一步替换原型对象的操作:

    Child.prototype = father;
    

    很多时候,我们想要在子类型中添加一些超类型没有的方法,应该放在替换原型对象之后。

    不能使用对象字面量

    通过原型链实现继承时,不能使用对象字面量来重写原型,因此这样做会切断子类型与超类型之间的联系。

    原型链的问题

    在上述例子中,Child构造函数的原型是Father的实例father,那么father实例的属性就变成Child的原型属性,接下来在Child的所有实例,均会共享father实例的属性,我们修改一下上述例子:

    function Father() {
        this.hobbies = [];
    }
    function Child() {
    }
    Child.prototype = new Father();
    var me = new Child();
    var sister = new Child();
    me.hobbies.push("dancing");
    sister.hobbies;
    // ["dancing"]
    

    me修改Child原型上的hobbies属性会影响到sister访问该属性时获得的值,注意,当我们直接在实例对象上对某个属性赋值时,我们相当于修改或添加实例中的某个属性(同访问不一样,不遵循原型链),比如:

    me.hobbies = [];
    sister.hobbies;
    //["dancing"]
    

    上述例子中,直接在me实例中添加属性hobbies,因此没有影响sister

    借用构造函数

    即在子类型的构造函数调用超类型的构造函数,举一个例子:

    function Father(givenName) {
        this.givenName = givenName;
    }
    function Child(givenName) {
        Father.call(this, givenName)
    }
    var child = new Child("Xianshu");
    child.givenName
    // "Xianshu"
    
    1. 私有属性
      使用这种方式,可以使每个实例从超类型中继承的属性私有化,一个实例更改属性值不会影响到其他属性,举一个例子:
    var child1 = new Child("Sue")
    var child2 = new Child("Jane")
    child1.givenName
    // "Sue"
    child2.givenName
    // "Jane"
    
    1. 传递参数
      通过这种方式,子类型可以在调用超类型构造函数时向其传参。

    2. 结构构造函数的问题
      首先没办法在实例间复用函数,比如:

    function Father(givenName) {
        this.givenName = givenName;
        this.cook = function() {
            return "delicious food";
        }
    }
    function Child(givenName) {
        Father.call(this, givenName)
    }
    var child1 = new Child("Sue")
    var child2 = new Child("Jane")
    child1.cook === child2.cook
    // false
    

    上述例子中,可以看出,两个实例的cook方法引用的不是同一块内存地址,说明cook被实例化了多次,这显然是冗余的。
    其次,超类型的原型中定义的方法对于子类型来说也是不可见的。

    组合继承

    使用原型链来实现方法的继承,使用构造函数实现属性的继承。比如:

    function Father(givenName) {
        this.givenName = givenName;
    }
    Father.prototype.cook = function() {
        return "delicious food";
    }
    function Child(givenName) {
        Father.call(this, givenName)
    }
    Child.prototype = new Father();
    Child.prototype.constructor = Child;
    var child = new Child("Sue")
    child.cook();
    // "delicious food"
    

    在创建Child构造函数时,首先继承超类型Father中的属性,然后创建一个Father实例,将其赋值给Child的原型,使其继承Father定义在原型中的方法,这里需要注意两点:

    1. new Father()中的givenName属性不会覆盖child中的givenName属性,这是由标识符在实例及其原型链上的搜索顺序决定的
    2. 需要修正Child.prototype.constructor属性

    原型式继承

    无需构造函数的一种继承方式,在一个对象的基础上创建出一个新对象:

    function object(o) {
        function F(){}
        F.prototype = o;
        return new F();
    }
    var person = { age : 18, hobbies: [] };
    var anotherPerson = object(person);
    anotherPerson.age;
    // 18
    
    

    上述例子中,person不是构造函数,只是一个普通的对象,anotherPerson继承了它的属性,需要注意的是,继承到的属性是在实例间共享的。

    anotherPerson.hobbies.push("dancing");
    anotherPerson.age = 19;
    var anotherPerson2 = object(person);
    anotherPerson2.age; // 18
    anotherPerson2.hobbies; // ["dancing"]
    

    寄生式继承

    寄生式继承封装了如下过程:

    1. 调用一个能够返回新对象的函数
    2. 以某种方式来增强返回的对象
    3. 返回对象
    function printBook(original) {
        var book = object(original);
        book.auther = "Sue";
        return book;
    }
    var book = {
        name: "travel notes",
        type: "travel"
    }
    var travelBook = printBook(book);
    travelBook.name
    // "travel notes"
    travelBook.auther
    // "Sue"
    

    上述例子中,travelBook不继承了book中的属性,而且还具有自己的属性。需要注意的是,使用寄生式继承,在第二步中添加的属性或方法不能在实例间复用。

    寄生组合式继承

    还记得我们在组合继承中提到的注意事项第一条吗...为什么Child原型中以及child实例中都包含givenName属性,这是因为我们调用了两次Father构造函数,第一次将属性赋予child实例,第二次是将Father的实例赋值给Child的原型,虽然第二次调用添加的givenName属性并没有影响到child.givenName,但两次调用毕竟是冗余的,况且Child只需要继承Father的原型,而Father的实例包含了额外的属性。寄生组合方式就是为了解决上述的问题:

    function Father(givenName) {
        this.givenName = givenName;
    }
    Father.prototype.cook = function() {
        return "delicious food";
    }
    function Child(givenName) {
        Father.call(this, givenName)
    }
    function inheritPrototype(subType, superType){
        var prototype = object(superType.prototype); 
        prototype.constructor = subType;
        subType.prototype = prototype; 
    }
    inheritPrototype(Child, Father);
    var child = new Child("Sue")
    undefined
    child.givenName
    // "Sue"
    "givenName" in Child.prototype
    // false
    child.cook()
    // "delicious food"
    child instanceof Father
    // true
    Father.prototype.isPrototypeOf(child)
    // true
    

    Child使用寄生组合的方式继承了Father,可以调用Father原型中的方法,同时,没有留下Father“私有”属性的痕迹,instanceof()isPrototypeOf()可以正常使用

    相关文章

      网友评论

      • 1dbe926d2941:😂发现我的java基本忘光了哈哈,,猜猜我是谁
        Sue1024:@七七七情 大学同学:dizzy_face:

      本文标题:Javascript 面向对象的程序设计(原型链与继承)

      本文链接:https://www.haomeiwen.com/subject/aadkfftx.html