面试官:JS 有哪些继承的方法?

429 阅读4分钟

引言

在面滴滴出行的时候,面试官问了我这样一个问题,当时回答得不怎么好,表达得可能不是那么全面,于是我总结了一下

1.原型链继承

下面我们来看一段代码


function Parent() {

        this.name = '宇哥'
   }

       Parent.prototype.age = 18

        Child.prototype = new Parent()

        function Child() {


        }
        
        let child1 = new Child()
        
        console.log(child1.name)     // 宇哥
        console.log(child1.age)      //18

   

在这里,我们有一个父类 Parent,一个子类 Child,我们在父类的原型上添加了一个 key 为age,value为18 的一个属性,我们通过原型链继承的方式来实现 子类Child继承 父类Parent

第一个打印的结果是 '宇哥'

第二个打印的是18

说明成功的实现了继承

但是面试官可能会问:这种继承的缺点是什么?


  function Parent() {

            this.arr= [1,2,3,4]
                
        }

        Parent.prototype.age = 18


        Child.prototype = new Parent()

        function Child() {

            

        }
        let child1 = new Child()
        let child2 = new Child()
        
         child1.arr.push(5)

        console.log(child1.arr)     //[1,2,3,4,5]
        console.log(child2.arr)     //[1,2,3,4,5]


在这里,我们创建了两个实例对象child1child2,我们修改了实例对象child1上的属性(在数组里面插入了一个数字5),

我们发现两个打印都是[1,2,3,4,5],说明我们改变第一个实例对象的时候,第二个实例对象受到了影响

这就是缺点!

用原型链实现继承,当要继承父类的属性是引用类型的时候,会导致属性共享

2.构造函数继承

同样看这段代码


    function Parent() {

                this.name = '宇哥'
            }

            Parent.prototype.age = 18

            function Child() {

              Parent.call(this)
                   
            }
            let child1 = new Child()

            console.log(child1.name)     // 宇哥
            console.log(child1.age)      // undefined

在这里我们将Child()的this指向Parent的this

这个时候,子类可以访问到父类中的属性name

但是不能访问到父类prototype(原型上的属性)age,这就是它的缺点

用构造函数来实现继承的时,子类不能访问到父类prototype(原型上的属性)

3.组合继承(把原型链继承和构造函数结合一下)




function Parent() {

                this.arr=[1,2,3,4]
            }

            Parent.prototype.age = 18

           
               Child.prototype=new Parent()

            function Child() {

              Parent.call(this)
                   
            }

            let child1 = new Child()
            let child2 =new Child()

             child1.arr.push(5)

            console.log(child1.arr)     // [1,2,3,4,5]
            console.log(child2.arr)      // [1,2,3,4]
            console.log(child1.age)      //  18
           
    

当我们改变 child1 上的属性时,child2不会受到影响

子类可以访问到父类prototype 上的属性

用组合继承的方法,我们就解决了上面两个问题,一个是属性共享,另外一个是不能访问到父类prototype上的属性

但是组合继承也有缺点

组合继承需要调用两次父类的构造函数,一次是在子类的构造函数中调用 Parent.call (this),一次是通过 Child.prototype = new Parent () 实现原型继承。

这样做既增加了调用次数,也可能导致父类构造函数中的逻辑被执行多次。

总结来说,组合继承是一种常用的继承方式,它既能够继承父类的属性和方法,又能够拥有自身独有的属性和方法。

但它的缺点是在创建子类实例时会重复调用父类的构造函数,可能导致内存占用过大,并且需要额外处理父类构造函数中的逻辑

4.寄生组合继承



    function Parent() {
                    this.arr = [1, 2, 3, 4];
                }

                Parent.prototype.age = 18;

                function Child() {
                    Parent.call(this); // 继承 Parent 的实例属性
                }

                // 创建一个继承自 Parent 原型的新对象,但不立即执行 Parent 构造函数
                Child.prototype = Object.create(Parent.prototype);


                let child1 = new Child();
                let child2 = new Child();

                child1.arr.push(5);

                console.log(child1.arr); // 输出: [1, 2, 3, 4, 5]
                console.log(child2.arr); // 输出: [1, 2, 3, 4]
                console.log(child1.age); // 输出: 18
  • 避免构造函数的重复调用:与直接将 Child.prototype 设置为 new Parent() 不同,Object.create(Parent.prototype) 只是设置原型关系,而不会立即调用 Parent 构造函数。这样可以避免不必要的初始化操作。

5.ES6 里面的class


  class Parent {
                constructor() {
                    this.name = '宇哥';
                }

                getAge() {
                    return 18;
                }
            }

            class Child extends Parent {
                constructor() {
                    super(); // 调用父类的构造函数
                }
            }

            let child1 = new Child();

            console.log(child1.name); // 输出: 宇哥
            console.log(child1.getAge()); // 输出: 18
  1. 定义父类 Parent

    • 使用 class 关键字定义一个名为 Parent 的类。
    • 在 Parent 类的构造函数中初始化 name 属性。
    • 在 Parent 类中定义一个方法 getAge,返回年龄 18。这里使用方法而不是直接在原型上定义属性,是为了更好地展示如何继承方法。
  2. 定义子类 Child

    • 使用 class 关键字定义一个名为 Child 的类。
    • 使用 extends 关键字指定 Child 继承自 Parent
    • 在 Child 类的构造函数中调用 super(),这是调用父类构造函数的语法,确保父类的构造函数被执行,从而初始化 name 属性。
  3. 创建 Child 实例

    • 使用 new 关键字创建 Child 的实例 child1
    • 访问 child1 的 name 属性和 getAge 方法。

一般情况一下,在JS 当中,我们都是通过class 来实现继承的,因为这种方法简单,而且考虑到ES5一些继承的缺点