侧边栏壁纸
博主头像
woku博主等级

成功的路上并不拥挤

  • 累计撰写 50 篇文章
  • 累计创建 13 个标签
  • 累计收到 3 条评论

面试中常问的JS继承

woku
2022-03-07 / 0 评论 / 2 点赞 / 211 阅读 / 6,600 字

原型链继承

子类的原型指向父类的原型

        function Parent() {
            this.tSkill = 'Java'
            this.major = ['计算机','HTML','CSS']
        }
        Parent.prototype.sayHi = function() {
            console.log('heiyg')
        }
        function Child() {
            this.major = ['Node','Vue']
        }
        Child.prototype = Parent.prototype
        Child.prototype.doWork = function() {
            console.log('I am doing something')
        }
        var c = new Child()
        c.sayHi()
        console.log(Child.prototype)
        console.log(Parent.prototype)

子类的原型指向了父类的原型,子类实例在寻找属性时,本身没有,就通过proto往上找
找到子类原型的时候,指向父类原型,那么就可以拿到父类原型上的属性和方法。

这种继承的问题:
1、只能继承父类原型上的属性,无法继承父类构造函数上的属性   eg: c.tSkill undefined
2、Child.prototype = Parent.prototype 是同一个地址引用,一旦改变了Child.prototype,那么Parent.prototype也会发生改变
eg: Child.prototype.a = 1 -> Parent.prototype.a = 1

子类的原型指向父类的实例

        function Parent(tSkill) {
            this.tSkill = tSkill
            this.major = ['计算机','HTML','CSS']
        }
        Parent.prototype.sayHi = function() {
            console.log('heiyg')
        }
        var parent = new Parent()
        function Child() {
            
        }
        Child.prototype = parent
        Child.prototype.doWork = function() {
            console.log('I am doing something')
        }
        var c = new Child('web')
        c.sayHi()
        // 可以继承父类构造函数中定义的属性
        console.log(c.tSkill) 
        console.log(Child.prototype)
        console.log(Parent.prototype)

继承父类的模板,又能继承父类的原型对象
实例对象是由构造函数Parent构造的,在执行构造函数的时候,肯定会有父类的模板
实例对象有__proto__指向Parent的prototype
😃这解决了上一个继承方案的第一个问题(无法继承父类构造函数属性)

再看看第二个问题: 更改子类的原型,父类的原型是不是也会变化?
在上面代码中,给Child.prototype增加了一个doWork方法,分别打印Child.prototype 和 Parent.prototype
image.png
增加属性并不会影响

那修改属性呢?
Child.prototype.sayHi = null
直接在实例上增加的这个属性,不会去找 Parent.prototype.sayHi
这种也不会影响。

继续看下面代码:

        function Parent(tSkill) {
            this.tSkill = tSkill
            this.major = ['计算机','HTML','CSS']
        }
        Parent.prototype.sayHi = function() {
            console.log('heiyg')
        }
        Parent.prototype.color = ['red','blue']
        var parent = new Parent()
        function Child() {
            
        }
        Child.prototype = parent
        Child.prototype.doWork = function() {
            console.log('I am doing something')
        }
        Child.prototype.color.push('pink')
        var c = new Child('web')
        console.log(Child.prototype)
        console.log(Parent.prototype)

注意这里的Child.prototype.color.push('pink') 和上面的Child.prototype.sayHi = null的区别

  • Child.prototype.color.push('pink'):
    1、Child.prototype.color.push('pink') :我要先找到color这个属性
    2、自身没有,就到Parent.prototype里面去找,发现了color,把地址拿过来
    3、看这个属性有没有push方法
    4、有push方法,就在拿过来的那个引用地址给他加一个pink
    5、Parent.prototype里面的color属性同步更改

这其实就是来自原型对象的所有属性都被共享了,操作子类的原型对象里的引用类型数据会导致父类原型对象中的属性变化

        function Parent(tSkill) {
            this.tSkill = tSkill
            this.major = ['计算机','HTML','CSS']
        }
        Parent.prototype.sayHi = function() {
            console.log('heiyg')
        }
        Parent.prototype.color = ['red','black']
        var parent = new Parent()
        function Child() {
            
        }
        Child.prototype = parent
        Child.prototype.doWork = function() {
            console.log('I am doing something')
        }
        var c = new Child('web')
        c.color.push('pink')
        var c2 = new Child()
        console.log(c2.color) // ['red', 'black', 'pink']

子类实例来修改原型对象中的引用类型数据,那么会影响所有子类创建的实例对象。

  • Child.prototype.sayHi = null:我不管你有没有,反正我就直接用=来覆盖你了,是给自己加。所以不会对父造成影响。

存在的问题

  1. 属性共享
  2. 无法实现多继承(已经指定了原型对象)
  3. 创建子类时,无法向父类构造函数传递参数(var c = new Child('web') 这个参数并没有传递给父类)

构造函数继承

通过call借用父类构造函数改变this指向实现继承

  • 可以向父类构造函数传递参数
       function Parent(tSkill) {
            this.tSkill = tSkill
            this.major = ['计算机','HTML','CSS']
        }
        Parent.prototype.sayHi = function() {
            console.log('heiyg')
        }
        function Child(tSkill) {
            Parent.call(this,tSkill)
        }
        var child1 = new Child('web')
        console.log(child1.tSkill)  // 'web'
  • 解决了原型链中子类共享父类引用对象的问题
        function Parent(tSkill) {
            this.tSkill = tSkill
            this.major = ['计算机','HTML','CSS']
        }
        Parent.prototype.sayHi = function() {
            console.log('heiyg')
        }
        function Child(tSkill) {
            Parent.call(this,tSkill)
        }
        var child1 = new Child('web')
        child1.major.push('Java')
        var child2 = new Child()
        console.log(child1.major)  // ['计算机', 'HTML', 'CSS', 'Java']
        console.log(child2.major)  // ['计算机', 'HTML', 'CSS']

存在的问题

  1. 只能继承父类构造函数中的属性和方法,不能继承父类原型上的属性和方法(使用child1.sayHi()会报错)
  2. 实例只是子类的实例,不是父类的实例
        console.log(child1 instanceof Child)    true
        console.log(child1 instanceof Parent)   false

a instanceof A:判断a对象的原型链上是否存在A的原型,或者说A的原型是否存在于a的原型链上
这种继承父类的原型和子类的原型都没有任何关系,所以肯定使用child1 instanceof Parent是false啦!

组合继承

原型链继承+构造函数继承

        function Parent(tSkill) {
            this.tSkill = tSkill
            this.major = ['计算机','HTML','CSS']
        }
        Parent.prototype.color = ['red','black']
        Parent.prototype.sayHi = function() {
            console.log('heiyg')
        }
        function Child(tSkill) {
            Parent.call(this,tSkill)
        }
        Child.prototype = new Parent()
        var parent1 = new Parent()
        var child1 = new Child('web')
        child1.color.push('pink')  // 改变Parent原型上的属性
        var child2 = new Child()
        console.log(child2.color)  // 同步发生变化

Child.prototype = new Parent() 原型链继承

Parent.call(this,tSkill) 构造函数继承

constructor

默认原型对象下会有这一个属性,意思是我这个对象是由哪个构造函数构造的

上面的代码中:Child.prototype = new Parent()是父类是一个实例,会使得child1.constructorparent1.constructor都是指向Parent构造函数

此时,我们要手动的将constructor进行调整过来

        function Parent(tSkill) {
            this.tSkill = tSkill
            this.major = ['计算机','HTML','CSS']
        }
        Parent.prototype.color = ['red','black']
        Parent.prototype.sayHi = function() {
            console.log('heiyg')
        }
        function Child(tSkill) {
            Parent.call(this,tSkill)
        }
        Child.prototype = new Parent()
        Child.prototype.constructor = Child
        var parent1 = new Parent()
        var child1 = new Child('web')
        console.log(parent1.constructor)
        console.log(child1.constructor)

存在的问题

我们虽然只调用了new Child()一次,但是在Parent中却需要调用2次

  • 第一次是原型链继承的时候,new Parent()
  • 第二次是构造继承的时候,Parent.call()调用的

而且打印child1会发现,同样的属性,自己身上有一份,原型对象上面也有一份。而原型对象上的属性根本不会用到,自己有的,肯定先拿自己的。这样增加了不必要的内存
image.png

寄生组合继承

组合继承的缺点中父类构造函数调用了2次。那就减少调用次数

Parent.call 或者 new Parent()中的一种换一个方式,不调用父类构造函数

  • Parent.call 执行父类构造函数,并且解决引用值共享,肯定需要用到
  • new Parent()这个其实是为了获取父类原型上的属性和方法。但并不要构造函数里面的属性方法

也就是说:我们需要一个干净的实例对象,这个对象要继承父类原型对象

Object.create(proto, propertiesObject)

参数1:需要指定的原型对象
参数2:可选参数,给新对象自身添加新属性以及描述器

        function Parent(tSkill) {
            this.tSkill = tSkill
            this.major = ['计算机','HTML','CSS']
        }
        Parent.prototype.sayHi = function() {
            console.log('heiyg')
        }
        function Child(tSkill) {
            Parent.call(this,tSkill)
        }
        Child.prototype = Object.create(Parent.prototype)
        Child.prototype.constructor = Child
        var parent1 = new Parent()
        var child1 = new Child()

ES6中的class继承

extends和super

        class Parent {
            constructor() {
                this.name = 'parent'
            }
        }
        class Child extends Parent {
            constructor() {
                super()
            }
        }
        var c = new Child()
        console.log(c)

也可以省略constructor

        class Parent {
            constructor() {
                this.name = 'parent'
            }
        }
        class Child extends Parent {
        }
        var c = new Child()
        console.log(c)

注意:constructor和super要不都不写,要写的话,写了constructor就一定要写super

super的不同用法

super可以当做函数来调用

代表着父类构造函数  (只能在constructor中使用super())

        class Parent {
            constructor() {
                this.name = 'parent'
            }
        }
        class Child extends Parent {
            getName() {
                super()   // 直接报错  Uncaught SyntaxError: 'super' keyword unexpected here
            }
        }
        var c = new Child()
        console.log(c)

super可以当做对象来使用

  • 在子类的普通函数中super对象指向父类的原型对象

  • 在子类的静态方法中super对象指向父类

       class Parent {
            constructor(name) {
                this.name = name
            }
            getName() {
                console.log(this.name)
            }
        }
        Parent.prototype.name = 'protoName'
        Parent.prototype.getSex = function() {
            console.log('boy')
        }
        Parent.getColors = function() {
            console.log(['blue'])   
        }
        class Child extends Parent {
            constructor(name) {
                super(name)  // 父类构造函数
                super.getName() // 普通函数中,super指向父类原型对象
            }
            instanceOf() {
                super.getSex()  // 普通函数中,super指向父类原型对象
            }
            static staticFn() {
                super.getColors() // 静态方法中,super指向父类
            }
        }
        var c1 = new Child('c1')
        console.dir(Child)
        c1.instanceOf()
        Child.staticFn()

super当成对象调用父类方法时this的指向

普通方法中,super当做对象时候指向的是父类的原型对象,super.getName()里面的this是指向父类原型对象吗?

ES6规定,通过super调用父类的方法时,super会绑定子类的this。
super.getName()里面的this是指向子类,打印的自然是子类的name.

巩固一下class继承的知识吧!

carbon 1.png

2

评论区