JavaScript继承的实现方式

85 阅读3分钟

1.通过原型链继承

通过把构造函数A的原型指向构造函数B的实例上,实现A的实例继承B如下所示

        function SuperType() {
            this.superName = 'super'
        }
        SuperType.prototype.getSuperValue = function() {
            return this.superName;
        }

        function SubType() {
            this.subName = 'sub'
        }
        SubType.prototype = new SuperType();
        SubType.prototype.getSubValue = function() {
            return this.subName;
        }
        let sub = new SubType();
        console.log(sub.getSuperValue()); // super

缺点:(1)B的实例如果存在引用类型的属性,即A的原型对象上存在引用类型的属性,那么A的实例对该引用类型的属性的修改会影响到A的所有实例。(2)子类型在实例化时不能给构造函数传参。

2.盗用构造函数

通过在子类中调用父类构造函数来实现的

        function SuperType(name) {
            this.name = name;
        }

        function SubType() {
            // 继承SuperType并传参
            SuperType.call(this, 'zhangsan')
            this.age = 29;
        }
        let instance = new SubType();
        console.log(instance.name); // zhangsan
        console.log(instance.age); // 29

优点:解决了不能向父类传递参数的问题。
缺点:无法实现函数方法的复用,子类对象无法访问父类的原型对象的方法。

3.组合继承

结合了原型链和盗用构造函数两种方法实现的继承,如下代码所示:

        function SuperType(name) {
            this.name = name;
            this.colors = ["red", "blue", "green"];
        }
        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.sayAge = function() {
            console.log(this.age);
        };
        let instance1 = new SubType("zhangsan", 20);
        instance1.colors.push("black");
        console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
        instance1.sayName(); // "zhangsan"; 
        instance1.sayAge(); // 20
        let instance2 = new SubType("lisi", 22);
        console.log(instance2.colors); // ['red', 'blue', 'green']
        instance2.sayName(); // "lisi"; 
        instance2.sayAge(); // 22

优点:既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
缺点:调用了两次父类的构造函数,导致子类的原型对象中多了一些不必要的属性。

4.原型式继承

思想:在一个对象的基础上再创建一个新对象 原型式继承通过Object.create()方法实现,该方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)。

        let person = {
            name: "Nicholas",
            friends: ["zhangsan"]
        };
        let p1 = Object.create(person);
        p1.name = "Greg";
        p1.friends.push("lisi");
        let p2 = Object.create(person);
        p2.name = "Linda";
        p2.friends.push("wangwu");
        console.log(person.friends); // ['zhangsan', 'lisi', 'wangwu']

Object.create(person)实际上返回一个新对象,该新对象的原型是person。
优点:适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。
缺点:属性中包含引用值时始终会在相关对象间共享,跟使用原型模式是一样的。

5.寄生式继承

思想:封装一个继承过程的函数,该函数接收一个对象,复制该对象的副本(继承),对副本对象进行扩展,最后返回副本对象

        function createAnother(oriObj) {
            let clone = Object.create(oriObj); // 通过调用函数创建一个新对象
            clone.sayHi = function() { // 扩展一个方法    
                console.log("hi");
            };
            return clone; // 返回这个对象
        }
        let person = {
            name: "Nicholas",
            friends: ["zhangsan", "lisi"]
        };
        let p1 = createAnother(person);
        p1.sayHi(); // "hi"

代码中Object.create()函数不是寄生式继承所必需的,任何返回新对象的函数都可以在这里使用。
优点:适合主要关注对象,而不在乎类型和构造函数的场景。
缺点:扩展的方法不能复用。

5.寄生式组合继承

思路:不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。其实就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。

        function SuperType(name) {
            this.name = name;
            this.colors = ["red", "blue", "green"];
        }
        SuperType.prototype.sayName = function() {
            console.log(this.name);
        };

        function SubType(name, age) {
            // 盗用构造函数
            SuperType.call(this, name);
            this.age = age;
        }
        // 原型链继承
        SubType.prototype = Object.create(SuperType.prototype);
        SubType.prototype.constructor = SubType;
        SubType.prototype.sayAge = function() {
            console.log(this.age);
        };
        let instance1 = new SubType("zhangsan", 20);
        instance1.colors.push("black");
        console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
        instance1.sayName(); // "zhangsan"; 
        instance1.sayAge(); // 20
        let instance2 = new SubType("lisi", 22);
        console.log(instance2.colors); // ['red', 'blue', 'green']
        instance2.sayName(); // "lisi"; 
        instance2.sayAge(); // 22

优点:解决了组合式继承的缺点,即避免了在子类的原型对象上创建不必要的属性的问题。