说一下js的继承方式

423 阅读3分钟

借用构造函数继承

function Person (name) {
    this.love = ['apple','orange']
    this.name = name
    this.age = 18
}
function People (name) {
    Person.call(this, name) // 改变this指向,这样people的实例就可以继承person上的属性
}
let p = new People('xl')
p.love.push('banana')
p.age = 19
let p1 = new Person('xw')
console.log(p.love, p1.love) // ['apple','orange','banana], ['apple','orange']
console.log(p.age, p1.age) // 19, 18
console.log(p.name, p1.name) // 'xl', 'xw'

优点:

  • 引用类型的属性不会全局污染,每份实例独享
  • 可以向父类中传参

缺点:

  • 方法在构造函数中定义,每次新建实例都会调用一次改变this指向的方法

原型链继承

function Parent () {
    this.names = ['xw','xl','xs']
}
function Child () {}
Child.prototype = new Parent() // 上一篇new操作符了解到它的内部操作实际是把构造函数的prototype赋值给了一个新对象的隐式原型__proto__,所以相当于把Parent的原型对象赋值给了Child
Parent.prototype.sayName = function () {console.log(this.names)}
let c = new Child()
c.names.push('xz')
let c1 = new Child()
console.log(c.sayName(), c1.sayName()) // ["xw", "xl", "xs", "xz"], ["xw", "xl", "xs", "xz"]

问题:

  • 新建child实例时不能传参给父类
  • 引用类型的属性会让所有实例共享

组合继承

function Parent (name) {
    this.name = name
    this.car = ['奔驰', '宝马']
}
function Child (name) {
    Parent.call(this, name)
    this.age = 18
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
let c = new Child('xl')
c.car.push('奥迪')
let c1 = new Child('xw')
c.age = 19
console.log(c.name, c.age, c.car) // 'xl', 19, ['奔驰', '宝马', '奥迪']
console.log(c1.name, c1.age, c1.car) // 'xw', 18, ['奔驰', '宝马']

这种继承方法结合了原型继承和借用构造函数继承的优点,会经常使用到

原型式继承

实际上是模拟了Object.create()方法

function createObj (o) {
    function F () {}
    F.prototype = o
    return new F()
}
var person = {
    name: 'kevin',
    friends: ['daisy', 'kelly']
}
var person1 = createObj(person);
var person2 = createObj(person);

person1.name = 'person1';
console.log(person2.name); // kevin

person1.firends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"]

缺点:

  • 和原型链继承一样,引用类型的属性会让所有实例共享

注意:修改person1.name的值,person2.name的值并未发生改变,并不是因为person1person2有独立的 name 值,而是因为person1.name = 'person1',给person1添加了 name 值,并非修改了原型上的 name 值。

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。

function createObj (o) {
    var clone = Object.create(o);
    clone.sayName = function () {
        console.log('hi');
    }
    return clone;
}

缺点:

  • 和构造函数继承一样,每次创建实例都会调用一次方法

寄生组合式继承

组合继承的缺点在于调用了两次构造函数,第一次在 Child.prototype = new Parent()。第二次在const c = new Child(),构造函数中Person.call(this)处调用。如何避免重复调用,可以不执行Child.prototype = new Parent(),而是间接让Child.prototype指向Parent.prototype

function Parent (name) {
    this.name = name
    this.car = ['奔驰', '宝马']
}
function Child (name) {
    Parent.call(this, name)
    this.age = 18
}
let F = function () {}
F.prototype = Parent.prototype
Child.prototype = new F()
let c = new Child('xl')
c.car.push('奥迪')
let c1 = new Child('xw')
console.log(c.car, c1.car) //  ["奔驰", "宝马", "奥迪"] (2) ["奔驰", "宝马"]

封装一下

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}
function prototype(child, parent) {
    var prototype = object(parent.prototype);
    prototype.constructor = child;
    child.prototype = prototype;
}
// 当我们使用的时候:
prototype(Child, Parent);

引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是: 这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

es6类继承

extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法。在子类的constructor中必须声明super用于继承父类this,因为子类没有自己的this

class Rectangle {
    constructor (height,width) {
        this.width = width
        this.height = height
    }
    get area () {
        return this.calcArea()
    }
    calcArea () {
        return this.height * this.width
    }
}
class Squar extends Rectangle {
     constructor (length) {
        super(length,length)
        this.name = 'Squar'
    }
}
let s = new Squar(10)
s.area // 100
console.log(s.width, s.height) // 10, 10

结语

写的不好,有错误的地方还请各位大佬指正,感恩家人感恩🙏