掰开揉碎系列——继承的前世今生

前言

今天跟各位朋友分享一下如何一步步推到出最优秀的继承方案,在阅读本文前,最好先弄懂原型原型链的关系,后续我也会写相关的文章,那么废话少说,我们开始

原型链继承方法

可以通过原型链,修改子类构造函数的原型对象为父级构造函数的一个实例,从而实现继承

javascript 复制代码
// 定义父级构造函数
function Animal() {
    this.name = '小动物'
    this.age = '2岁了'
}

// 往父级原型上添加方法
Animal.prototype.running = function() {
    console.log(this.name + '在快乐地跑步');
}

// 定义子级构造函数
function Rabbit() {
    this.color = '白色'
}

// 关键点------------ 将子构造函数的prototype属性指向Animal的一个实例
Rabbit.prototype = new Animal()

// 在子级的原型上添加方法
Rabbit.prototype.eating = function() {
    console.log(this.name + '在开心地吃东西');
} 

const rabbit = new Rabbit()
rabbit.eating() //小动物在开心地吃东西
rabbit.running() //小动物在快乐地跑步

画一个内存图方便大家理解

缺点

一.原型中的引用值会在所有的实例中共享,一旦被某一个实例改变了之后,下一个实例拿到的的引用值就不再是原来的数值,而是上一个实例改变后的值

javascript 复制代码
function Animal() {
  this.name = "小动物"
  this.friends = ["tiger", "dog"]
}

Animal.prototype.running = function() {
  console.log(this.name + "在快乐地跑步~")
}

function Rabbit() {
  this.color = '白色'
}

Rabbit.prototype = new Animal()

Rabbit.prototype.eating = function() {
  console.log(this.name + "在快乐地吃东西")
}

var rabbit1 = new Rabbit() 
var rabbit2 = new Rabbit()

rabbit1.friends.push("cat")
console.log(rabbit2.friends) // [ 'tiger', 'dog', 'cat' ]

二.子类不能给父类的构造函数传参

盗用构造函数

解决了包含引用值导致的继承问题

做法:通过apply() call()方法在新创建的对象上执行构造函数

javascript 复制代码
function Animal(name,friend) {
  this.name = name
  this.friends = friend
}

Animal.prototype.running = function() {
  console.log(this.name + "在快乐地跑步~")
}

function Rabbit(name,friend,color) {
  Animal.call(this,name,friend)
  this.color = color
}

Rabbit.prototype.eating = function() {
  console.log(this.name + "在快乐地吃东西")
}

var rabbit1 = new Rabbit('兔子一号',['kobe']) 
var rabbit2 = new Rabbit('兔子二号',['james'])

rabbit1.friends.push("curry")
console.log(rabbit2.friends) // [ 'james' ]

缺点:

只能继承父类的属性,不能继承父类的方法

组合继承

思路:使用原型链继承原型上的属性和方法,而盗用构造函数继承实例的属性

javascript 复制代码
function Animal(name,friend) {
  this.name = name
  this.friends = friend
}

Animal.prototype.running = function() {
  console.log(this.name + "在快乐地跑步~")
}

function Rabbit(name,friend,color) {
  Animal.call(this,name,friend) // 第二次调用父类函数
  this.color = color
}

Rabbit.prototype = new Animal() // 第一次调用父类函数

Rabbit.prototype.eating = function() {
  console.log(this.name + "在快乐地吃东西")
}

var rabbit1 = new Rabbit('兔子一号',['kobe']) 
var rabbit2 = new Rabbit('兔子二号',['james'])

rabbit1.friends.push("curry")
console.log(rabbit2.friends) // [ 'james' ]

组合继承是JavaScript中使用最多的继承模式之一,虽然有一些小瑕疵,但基本不影响使用

那么组合继承有哪些小瑕疵呢?

1.无论在什么情况下都会调用两次父类函数,第一次是在创建子类原型的时候,第二次是在子类构造函数内部

2.会储存着两份父类属性,第一份在rabbit本身,第二份在他的__proto__里面

接下来我们进行一系列优化

原型式继承函数

我们回顾一下实现继承的目的:重复利用另外一个对象的属性和方法

javascript 复制代码
function object(obj) {
    function fn() {}
    fn.prototype = obj
    return new fn()
}

const Animal = {
  name: "lebi",
  age: 18
}

const rabbit = obj(Animal)

上面代码进行了达到了什么目的呢?
把子类rabbit的对象原型指向了Animal对象

事实上,ES6之后还实现了一个Object.create()方法,同样也能实现相同的效果

javascript 复制代码
const Animal = {
  name: "lebi",
  age: 18
}

const rabbit = Object.create(Animal)

console.log(rabbit.__proto__) // { name: 'lebi', age: 18 }

寄生式继承

思路:寄生式继承的思路是结合原型类继承和工厂模式的一种方式

即创建一个封装继承过程的函数, 该函数在内部以某种方式来增强对象,最后再将这个对象返回;

javascript 复制代码
function object(obj) {
    function fn() {}
    fn.prototype = obj
    return new fn()
}

function createAnimal(obj) {
  const newObj = object(obj)
  newObj.running = function() {
    console.log(this.name + "running")
  }
  return newObj
}

const Animal = {
  name: "lebi",
  age: 18
}

const rabbit = createAnimal(Animal)
rabbit.running()

缺点:

一.引用类型数据还是会共享
二.寄生式继承还存在函数不能复用的问题,每一次调用createAnimal,都会创建出一个新的函数出来

寄生式组合继承

在前面提到的组合继承是存在以下两个问题

1.多次调用父类函数

2.父类的属性会有两份,一份在原型中,一份在子类实例里

而实际上,我们是可以使用寄生继承的办法来解决以上两个问题:

首先我们要明白,组合继承已经使用call方法,将父级的属性复制一份给了子类的实例,所以我们是不需要在将其再次拷贝进原型里面,接着我们可以使用刚才的寄生思想来对原型的属性方法进行继承
寄生组合的核心代码如下

javascript 复制代码
function object(obj) {
    function fn() {}
    fn.prototype = obj
    return new fn()
}



// 继承方法
function inhreitPrototype(son,dad) {
    object(dad.prototype)
    Object.defineProperty(son.prototype,'constructor',{
        configurable:true,
        enumerable:false,
        writable:true,
        value:son
    })
}

完整的代码如下

javascript 复制代码
function object(Object) {
    function fn() {}
    fn.prototype = Object
    return new fn()
}

function inhreitPrototype(son,dad) {
    son.prototype = object(dad.prototype)
    Object.defineProperty(son.prototype,'constructor',{
        configurable:true,
        enumerable:false,
        writable:true,
        value:son
    })
}

function Person(name,age,height) {
    this.name = name
    this.age = age
    this.height = height
}

Person.prototype.eating = function() {
    console.log(this.name + '在吃东西~');
}
Person.prototype.running = function() {
    console.log(this.name + '在跑步~');
}

function Student(name,age,height,grade,sno) {
    // 继承属性
    Person.call(this,name,age,height)
    this.grade = grade
    this.sno = sno
}

// 继承方法
/* 
    把子类的prototype改成一个原型对象是父类原型对象的对象
*/
inhreitPrototype(Student,Person)

const stu1 = new Student('孙沛涵',18,1.88,'大二',3122001621)
stu1.eating()
stu1.running()
console.log(stu1);
console.log(stu1.name,stu1.age,stu1.height,stu1.grade,stu1.sno);

以上是我借鉴自《JavaScript高级程序设计》继承一篇,结合自己理解整理出来的。希望对大家有帮助

相关推荐
jktantan20 分钟前
(二)Vue 3 的 attrs 不是响应式:el-form 扩展属性同步的三种方案
前端·javascript·vue.js·element-plus
前端 贾公子40 分钟前
第 5 章:Codex 从可运行程序到可交付工具
java·前端·数据库
用户360055579001 小时前
Kestrel_VLLM - 26 · 发射空档三步曲:事件计时 → nsys → 判别实验
前端
echome8881 小时前
JavaScript async/await 错误处理:别让 Promise 静默失败
开发语言·前端·javascript·promise
PedroQue991 小时前
v0.2.0 架构升级:class + reactive 重塑状态管理
前端·uni-app
鶴哥只手遮天1 小时前
Angular 22 源码级深度解析-第 02 章 编译链路总览:从模板字符串到 `ɵɵ` 指令
前端·开源·angular.js
尘世中一位迷途小书童1 小时前
别再堆万字 Prompt:用 Agent Skills 给团队写一个「前端 Review」Skill
前端·ai编程·cursor
aixingpan1 小时前
aixingpan.cn API开发文档:api_docs_bichart_common2接口指南
前端·php
super_Omni1 小时前
text/PDF/Word → 解析成纯文本 → 分词 → 词表匹配 → 加权打分 → 低于阈值
前端·chrome
阶进遐2 小时前
浏览器工具自制指南·对话导出(3):消息池设计
前端