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

前言

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

原型链继承方法

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

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高级程序设计》继承一篇,结合自己理解整理出来的。希望对大家有帮助

相关推荐
郭尘帅66622 分钟前
vue3基础学习(上) [简单标签] (vscode)
前端·vue.js·学习
njsgcs36 分钟前
opencascade.js stp vite webpack 调试笔记
开发语言·前端·javascript
T0uken1 小时前
【前端】:单 HTML 去除 Word 批注
前端·html·word
st紫月2 小时前
用vue和go实现登录加密
前端·vue.js·golang
岁岁岁平安2 小时前
Vue3学习(组合式API——计算属性computed详解)
前端·javascript·vue.js·学习·computed·计算属性
HWL56793 小时前
Express项目解决跨域问题
前端·后端·中间件·node.js·express
刺客-Andy3 小时前
React 第三十九节 React Router 中的 unstable_usePrompt Hook的详细用法及案例
前端·javascript·react.js
Go_going_3 小时前
【js基础笔记] - 包含es6 类的使用
前端·javascript·笔记
浩~~3 小时前
HTML5 浮动(Float)详解
前端·html·html5
AI大模型顾潇4 小时前
[特殊字符] 本地大模型编程实战(29):用大语言模型LLM查询图数据库NEO4J(2)
前端·数据库·人工智能·语言模型·自然语言处理·prompt·neo4j