首先先来讲下 class,其实在 JS 中并不存在类,class 只是语法糖,本质还是函数。

想要继承,就必须要提供个父类(继承谁,提供继承的属性)

//父类
function Person(name){
  this.name = name
  this.sum = function(){
      alert(this.name)
  }
}
Person.prototype.age = 10 //给构造函数添加了原型属性

一、原型链继承

//原型链继承
function Per(){
    this.name = 'key'
}
Per.prototype = new Person()//主要
let per1 = new Per()
console.log(per1.age)//10
//instanceof 判断元素是否在另一个元素的原型链上
//per1 继承了Person的属性,返回true
console.log(per1 instanceof Person)//true

重点:让新实例的原型等于父类的实例。
特点:1、实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)
缺点:1、新实例无法向父类构造函数传参。
   2、继承单一。
   3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)

二、借用构造函数继承

// 借用构造函数继承
function Con(){
    Person.call(this,'jer)//重点
    this.age = 12
}
let con1 = new Con()
console.log(con1.name)//jer
console.log(con1.age)//12
console.log(con1 instanceof Person)//false

重点:用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
特点:1、只继承了父类构造函数的属性,没有继承父类原型的属性。
   2、解决了原型链继承缺点1、2、3。
   3、可以继承多个构造函数属性(call多个)。
   4、在子实例中可向父实例传参。
缺点:1、只能继承父类构造函数的属性。
   2、无法实现构造函数的复用。(每次用每次都要重新调用)
   3、每个新实例都有父类构造函数的副本,臃肿。

三、组合继承(组合原型链继承和借用构造函数继承)(常用)

//组合原型链和构造函数继承
function SubType(name){
    Person.call(this,name)//借用构造函数的模式
}
SubType.prototype = new Person()//原型链继承
let sub = new SubType('gar')
conlose.log(sub.name)//‘gar'继承了构造函数属性
conlose.log(sub.age)//10 ,继承了父类原型链的属性
function Parent(value) {
    this.val = value
}
Parent.prototype.getValue= function() {
    console.log(this.val)
}
function Child(value) {
    Parent.call(this, value)
}
Child.prototype = new Parent()
const child = new Child(1)
child.getValue() // 1
child instanceof
Parent // true

以上继承的方式核心是在子类的构造函数中通过 Parent.call(this) 继承父类的属性,然后改变子类的原型为 new Parent() 来继承父类的函数。这种继承方式优点在于构造函数可以传参,不会与父类引用属性共享,可以复用父类的函数,但是也存在一个缺点就是在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费。

重点:结合了两种模式的优点,传参和复用
特点:1、可以继承父类原型上的属性,可以传参,可复用。
   2、每个新实例引入的构造函数属性是私有的。
缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。

四、原型式继承

//先封装一个函数容器,用来输出对象和承载继承的原型
function content(obj){
   function F(){}
   F.prototype = obj//继承了传入的参数
   return new F()//返回函数对象
}
let sup = new Person()//拿到了父类的实例
let sup1 = content(sup)
console.log(sup1.age)//10 继承了父类函数的属性

重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
特点:类似于复制一个对象,用函数来包装。
缺点:1、所有实例都会继承原型上的属性。
   2、无法实现复用。(新实例属性都是后面添加的)

五、寄生式继承

这种继承方式对组合继承进行了优化,组合继承缺点在于继承父类函数时调用了构造函数,我们只需要优化掉这点就行了。

function content(obj){
   function F(){}
   F.prototype = obj//继承了传入的参数
   return new F()//返回函数对象
}
let sup = new Person()//拿到了父类的实例
//以上事原型式继承,给原型式继承在套个壳子传递参数
function subobject(obj){
    let sub = content(obj)
    sub.name = 'gar'
    retrun sub
}
let sup2 = subobject(sup)
//这个函数经过声明之后就成了可增添属性的对象
console.log(typeof subobject)//function
console.log(typeof sup2)//object
console.log(sup2.name)//‘gar’ 返回了个sun对象,继承了sub的属性

重点:就是给原型式继承外面套了个壳子。
优点:没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。
缺点:没用到原型,无法复用。

六、寄生组合式继承(常用)

这种继承方式对组合继承进行了优化,组合继承缺点在于继承父类函数时调用了构造函数,我们只需要优化掉这点就行了。
寄生:在函数内返回对象然后调用
组合:1、函数的原型等于另一个实例。2、在函数中用apply或者call引入另一个构造函数,可传参 

function content(obj){
   function F(){}
   F.prototype = obj//继承了传入的参数
   return new F()//返回函数对象
}
//content 就是F实例的另一个表示法
let con = content(Porson.prototype)
//con实例(F实例)的原型继承了父类函数的原型

//组合
function Sub(){
    Person.call(this)//这个继承了父类构造函数的属性
}//解决了组合式两次调用构造函数属性的缺点

//重点
Sub.prototype = con //继承了con实例
con.constructor = Sub//一定要修复实例
let sun1 = new Sub()
//Sub的实例就继承了构造函数属性,父类实例,con的函数属性
console.log(sub1.age)//10
function Parent(value) {
    this.val = value
}
Parent.prototype.getValue= function() {
    console.log(this.val)
}
function Child(value) {
    Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
    constructor: {
        value: Child,
        enumerable: false,
        writable: true,
        configurable: true
    }
})
const child = new Child(1)
child.getValue() // 1
child instanceof
Parent // true

以上继承实现的核心就是将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题,还能正确的找到子类的构造函数。

重点:修复了组合继承的问题

继承这些知识点与其说是对象的继承,更像是函数的功能用法,如何用函数做到复用,组合,这些和使用继承的思考是一样的。上述几个继承的方法都可以手动修复他们的缺点,但就是多了这个手动修复就变成了另一种继承模式。

这些继承模式的学习重点是学它们的思想,不然你会在coding书本上的例子的时候,会觉得明明可以直接继承为什么还要搞这么麻烦。就像原型式继承它用函数复制了内部对象的一个副本,这样不仅可以继承内部对象的属性,还能把函数(对象,来源内部对象的返回)随意调用,给它们添加属性,改个参数就可以改变原型对象,而这些新增的属性也不会相互影响。

扩展,ES6的class继承,Class 如何实现继承?Class 本质是什么?

Class 继承

class Parent {
    constructor(value) {
        this.val = value
    }
    getValue() {
        console.log(this.val)
    }
}
class Child extends Parent {
    constructor(value) {
        super(value)
        this.val = value
    }
}
let child = new Child(1)
child.getValue() // 1
child instanceof
Parent // true

class 实现继承的核心在于使用 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 super,因为这段代码可以看成 Parent.call(this, value)。

之前也说了在 JS 中并不存在类,class 的本质就是函数。

最后修改:2022 年 03 月 14 日
如果觉得我的文章对你有用,请随意赞赏