8

概述

原型和闭包是JS的两个难点,最近碰到了原型继承的概念,正好在这里总结一下。

既然要实现继承,就一定要有一个父类。

        // 定义一个父类
        function father(name) {
            //属性
            this.name = name;
        }
        // 原型方法
        father.prototype.getName = function () {
            return this.name;
        }
        

原型链继承

基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。

回顾一下原型、实例和构造函数的关系。

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象内部的指针。
        // 子类
        function son(age) {
            // 属性
            this.age = age;
        };

        son.prototype = new father('jason');

        son.prototype.getAge = function () {
            return this.age;
        }

        let firstchild = new son('19');

        console.log(firstchild.getAge()) // 19
        

这里需要注意几点的是:

  • 默认原型

原型链的最顶端是Object,所有引用类型默认都是继承于Object的,所以默认也是有toString等方法的。

clipboard.png

  • 如何确定原型和实例的关系
    第一个方法是,instanceof,用于检测实例与原型链中出现过的构造函数。
     console.log(firstchild instanceof Object) //true
     console.log(firstchild instanceof son) //true
     console.log(firstchild instanceof father) //true
第二个方法是,isPrototypeOf方法。
        console.log(Object.prototype.isPrototypeOf(firstchild)) //true
        console.log(son.prototype.isPrototypeOf(firstchild)) //true
        console.log(father.prototype.isPrototypeOf(firstchild)) //true
  • 谨慎定义方法

子类型可能要重写父类型方法,或定义父类没有的方法。不管是啥,这个方法一定要写在替换原型语句的后面
还有原型链继承的时候,不能使用对象字面量创建原型方法。

例如:

        son.prototype = new father('jason');

        son.prototype = {
            getAge: function() {
                return this.age
            }
        }

这样会导致创建一个新的Object实例,而非原来的father。

  • 共享性和传参问题

    第一,引用类型的原型属性会被所有实例共享。

        function father(name) {
            this.name = name;
            this.colors = ['blue', 'red', 'white'];
        }
        
        let firstchild = new son('19');
        let secondchild = new son('20');
        firstchild.colors.push("black");
        console.log(firstchild.colors) // ["blue", "red", "white", "black"]
        console.log(secondchild.colors) // ["blue", "red", "white", "black"]
        

第二,不能像父类型构造函数传参数,书里准确说法是,没有办法在不影响所有实例的情况下,给父类构造函数传递参数。

小结

优点:

  • 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  • 父类新增原型方法/原型属性,子类都能访问到
  • 简单,易于实现

缺点:

  • 要想为子类新增属性和方法,必须要在new father()这样的语句之后执行,不能放到构造器中
  • 无法实现多继承
  • 来自原型对象的引用属性是所有实例共享的
  • 创建子类实例时,无法向父类构造函数传参

借用构造继承

在子类型的构造函数中调用父类的构造函数,使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(不用原型)

        function son(age) {
            father.call(this);
            this.age = age;
        };

        son.prototype = new father('jason');
        
        son.prototype.getAge = function () {
            return this.age;
        }

        let firstchild = new son('19');
        let secondchild = new son('20');
        firstchild.colors.push("black");
        
        console.log(firstchild.colors); // ["blue", "red", "white", "black"]
        console.log(secondchild.colors); // ["blue", "red", "white"]
  • 可以传递参数
  • 方法都在构造函数中定义,函数复用性丢失

总结

优点:

  • 由例子可见,解决了1中子类实例共享父类引用属性的问题
  • 创建子类实例时,可以向父类传递参数
  • 可以实现多继承(call多个父类对象)

缺点:

  • 实例并不是父类的实例,只是子类的实例
  • 只能继承父类的实例属性和方法,不能继承原型属性/方法
  • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

组合继承

也就是将原型链继承和构造函数继承融合,原型链实现对原型属性和方法的继承,构造函数实现对实例属性的继承。
这样既保证了原型上函数的复用,也保证了每个实例有自己的属性。

         function son(name, age) {
            father.call(this, name);
            this.age = age;
        };

        son.prototype = new father();
        
        son.prototype.getAge = function () {
            return this.age;
        }


        let firstchild = new son('jason', '19');
        let secondchild = new son('jason junior', '18');
        firstchild.colors.push("black");
        
        
        console.log(firstchild.colors); // ["blue", "red", "white", "black"]
        console.log(secondchild.colors); //["blue", "red", "white"]
        console.log(firstchild.getName()); // jason
        console.log(secondchild.getName()); // jason junior
        console.log(firstchild.getAge()); //19
        console.log(secondchild.getAge()); //18

特点:

  • 可以继承实例属性/方法,也可以继承原型属性/方法
  • 既是子类的实例,也是父类的实例
  • 不存在引用属性共享问题
  • 可传参
  • 函数可复用

缺点:

  • 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

原型式继承

为父类实例添加新特性,作为子类实例返回

        let p = {
            name: 'jason',
            colors: ['white', 'black', 'red']
        }
        function object (o) {
            function F() {};
            F.prototype = o;
            return new F();
        }

        let firstchild = object(p)
        let secondchild = object(p)

        firstchild.name = 'jason1'
        firstchild.colors.push('blue')

        secondchild.name = 'jason2'
        secondchild.colors.push('green')

        console.log(p.colors) // ["white", "black", "red", "blue", "green"]

ECMAScript 5新增Object.create()方法规范原型式继承。两个参数,一个参数是新对象原型的对象,一个参数是对象定义额外属性的对象,第二个可忽略,就等于上述object函数了

寄生式继承

创造一个用于封装继承过程的函数,该函数内部以某种方式增强对象。

        function create(o) {
            let clone = object(o);
            o.sayHi = function () {
                console.log('Hi')
            }
            return o;
        }

寄生组合继承

组合继承虽然好用,但是也有缺陷,就是会调用两次构造函数,一次在创建时候,一次在内部,那个call方法。

所谓寄生组合继承,即通过借用构造函数方式,继承属性,通过原型链形式继承方法。

沿用寄生方式:

        function inheritPrototype (sub, sup) {
            let prototype = object(sup.prototype);
            prototype.constructor = sub;
            sub.prototype = prototype;
        }
        function father(name) {
            this.name = name;
            this.colors = ['blue', 'red', 'white'];
        }

        father.prototype.getName = function () {
            return this.name;
        }

        function son(name, age) {
            father.call(this, name);
            this.age = age;
        };

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

        function inheritPrototype (sub, super) {
            let prototype = object(super.prototype);
            prototype.constructor = sub;
            sub.prototype = prototype;
        }

        inheritPrototype(son, father);

        son.prototype.getAge = function () {
            return this.age;
        }

总结

优点:

  • 堪称完美

缺点:

  • 实现较为复杂
参考 <<JavaScript高级程序设计>>总结

朝阳银枪小霸王
36 声望0 粉丝

你听见笑声,肯定不是我


« 上一篇
JS基础-闭包