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

前言

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

原型链继承方法

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

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

相关推荐
万物得其道者成4 分钟前
React Zustand状态管理库的使用
开发语言·javascript·ecmascript
小白小白从不日白5 分钟前
react hooks--useReducer
前端·javascript·react.js
下雪天的夏风18 分钟前
TS - tsconfig.json 和 tsconfig.node.json 的关系,如何在TS 中使用 JS 不报错
前端·javascript·typescript
diygwcom29 分钟前
electron-updater实现electron全量版本更新
前端·javascript·electron
volodyan32 分钟前
electron react离线使用monaco-editor
javascript·react.js·electron
^^为欢几何^^41 分钟前
lodash中_.difference如何过滤数组
javascript·数据结构·算法
Hello-Mr.Wang1 小时前
vue3中开发引导页的方法
开发语言·前端·javascript
程序员凡尘1 小时前
完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法,亲测有效!!!
前端·javascript·vue.js
编程零零七5 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
北岛寒沫6 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习