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

前言

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

原型链继承方法

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

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

相关推荐
小白学大数据几秒前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫
qq_390161779 分钟前
防抖函数--应用场景及示例
前端·javascript
3345543237 分钟前
element动态表头合并表格
开发语言·javascript·ecmascript
John.liu_Test39 分钟前
js下载excel示例demo
前端·javascript·excel
Yaml41 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事1 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶1 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo1 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v1 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript
知孤云出岫1 小时前
web 渗透学习指南——初学者防入狱篇
前端·网络安全·渗透·web