您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > javascript

JavaScript面向对象—继承的实现

时间:2022-03-14 09:57:18  来源:  作者:可爱的雯雯子

前言

面向对象的三大特性:封装、继承和多态。上一篇我们简单的了解了封装的过程,也就是把对象的属性和方法封装到一个函数中,这一篇讲一下JAVAScript中继承的实现,继承是面向对象中非常重要的特性,它可以帮助我们提高代码的复用性。继承主要的思想就是将重复的代码逻辑抽取到分类中,子类只需要通过继承分类,就可以使用分类中的方法,但是在实现JavaScript继承之前,需要先了解一个重要的知识点“原型链”。

1.JavaScript中的原型链

在上一篇JavaScript面向对象—对象的创建和操作中已经简单的了解过了JavaScript中对象的原型和函数的原型,当我们从一个对象上获取属性时,如果在当前对象自身没有找到该属性的话,就会去它原型上面获取,如果原型中也没有找到就会去它原型的原型上找,沿着这么一条线进行查找,那么这条线就是我们所说的原型链了。

示例代码:

const obj = {
  name: 'curry',
  age: 30
}

obj.__proto__ = {}
obj.__proto__.__proto__ = {}
obj.__proto__.__proto__.__proto__ = { height: 1.83 }

console.log(obj.height) // 1.83

对应的内存中的查找过程:

JavaScript面向对象—继承的实现

 

当通过原型链查找某个属性时,一直找不到的话会一直查找下去么?肯定是不会的,JavaScript的原型链也是有尽头的,这个尽头就是Object的原型。

2.Object的原型

事实上,不管是对象还是函数,它们原型链的尽头都是Object的原型,也称之为顶层原型,我们可以打印看看这个顶层原型长什么样。

(1)打印Object的原型

console.log(Object.prototype)
  • 在node环境中:
JavaScript面向对象—继承的实现

 

  • 在浏览器中:
JavaScript面向对象—继承的实现

 

(2)Object原型的特殊之处

  • 如果我们再次打印Object.prototype的原型,这个原型属性已经指向了null
  • console.log(Object.prototype.__proto__) // null
  • 并且在Object.prototype上有很多默认的属性和方法,像toString、hasOwnProperty等;

(3)上一篇中讲到当使用new操作符调用构造函数时,其对象的[[prototype]]会指向该构造函数的原型prototype,其实Object也是一个构造函数,因为我们可以使用new操作符来调用它,创建一个空对象。

  • 示例代码:
  • const obj = new Object() obj.name = 'curry' obj.age = 30 console.log(obj.__proto__ === Object.prototype) // true console.log(obj.__proto__) // [Object: null prototype] {} console.log(obj.__proto__.__proto__) // null
  • 内存表现:
  •  

(4)总结

  • 从Object的原型可以得出一个结论“原型链最顶层的原型对象就是Object的原型对象”,这也就是为什么所有的对象都可以调用toString方法了;
  • 从继承的角度来讲就是“Object是所有类的父类”;

3.JavaScript继承的实现方案

3.1.方案一:通过原型链实现继承

如果需要实现继承,那么就可以利用原型链来实现了。

  • 定义一个父类Person和子类Student;
  • 父类中存放公共的属性和方法供子类使用;
  • 核心:将父类的实例化对象赋值给子类的原型;
// 定义Person父类公共的属性
function Person() {
  this.name = 'curry'
  this.age = 30
}
// 定义Person父类的公共方法
Person.prototype.say = function() {
  console.log('I am ' + this.name)
}

// 定义Student子类特有的属性
function Student() {
  this.sno = 101111
}

// 实现继承的核心:将父类的实例化对象赋值给子类的原型
Student.prototype = new Person()

// 定义Student子类特有的方法
Student.prototype.studying = function() {
  console.log(this.name + ' studying')
}

// 实例化Student
const stu = new Student()
console.log(stu.name) // curry
console.log(stu.age) // 30
console.log(stu.sno) // 101111
stu.say() // I am curry
stu.studying() // curry studying

内存表现:

JavaScript面向对象—继承的实现

 

缺点

  • 从内存表现图中就可以看出,当打印stu对象时,name和age属性是看不到的,因为不会打印原型上的东西;
  • 当父类中的属性为引用类型时,子类的多个实例对象会共用这个引用类型,如果进行修改,会相互影响;
  • 在使用该方案实现继承时,属性都是写死的,不支持动态传入参数来定制化属性值;

3.2.方案二:借用构造函数实现继承

针对方案一的缺点,可以借用构造函数来进行优化。

  • 在子类中通过call调用父类,这样在实例化子类时,每个实例就可以创建自己单独属性了;
// 定义Person父类公共的属性
function Person(name, age) {
  this.name = name
  this.age = age
}
// 定义Person父类的公共方法
Person.prototype.say = function() {
  console.log('I am ' + this.name)
}

// 定义Student子类特有的属性
function Student(name, age, sno) {
  // 通过call调用Person父类,创建自己的name和age属性
  Person.call(this, name, age)
  this.sno = sno
}

// 实现继承的核心:将父类的实例化对象赋值给子类的原型
Student.prototype = new Person()

// 定义Student子类特有的方法
Student.prototype.studying = function() {
  console.log(this.name + ' studying')
}

// 实例化Student
const stu1 = new Student('curry', 30, 101111)
const stu2 = new Student('kobe', 24, 101112)
console.log(stu1) // Person { name: 'curry', age: 30, sno: 101111 }
console.log(stu2) // Person { name: 'kobe', age: 24, sno: 101112 }

内存表现:

JavaScript面向对象—继承的实现

 

缺点:

  • 在实现继承的过程中,Person构造函数被调用了两次,一次在new Person(),一次在Person.call();
  • 在Person的实例化对象上,也就是stu1和stu2的原型上,多出来了没有使用的属性name和age;

3.3.方案三:寄生组合式继承

通过上面两种方案,我们想实现继承的目的是重复利用另外一个对象的属性和方法,如果想解决方案二中的缺点,那么就要减少Person的调用次数,避免去执行new Person(),而解决的办法就是可以新增一个对象,让该对象的原型指向Person的原型即可。

(1)对象的原型式继承

将对象的原型指向构造函数的原型的过程就叫做对象的原型式继承,主要可以通过以下三种方式实现:

  • 封装一个函数,将传入的对象赋值给构造函数的原型,最后将构造函数的实例化对象返回;
  • function createObj(o) { // 定义一个Fn构造函数 function Fn() {} // 将传入的对象赋值给Fn的原型 Fn.prototype = o // 返回Fn的实例化对象 return new Fn() } const protoObj = { name: 'curry', age: 30 } const obj = createObj(protoObj) // 得到的obj对象的原型已经指向了protoObj console.log(obj.name) // curry console.log(obj.age) // 30 console.log(obj.__proto__ === protoObj) // true
  • 改变上面方法中的函数体实现,使用Object.setPrototypeOf()方法来实现,该方法设置一个指定的对象的原型到另一个对象或null;
  • function createObj(o) { // 定义一个空对象 const newObj = {} // 将传入的对象赋值给该空对象的原型 Object.setPrototypeOf(newObj, o) // 返回该空对象 return newObj }
  • 直接使用Object.create()方法,该方法可以创建一个新对象,使用现有的对象来提供新创建的对象的__proto__;
  • const protoObj = { name: 'curry', age: 30 } const obj = Object.create(protoObj) console.log(obj.name) // curry console.log(obj.age) // 30 console.log(obj.__proto__ === protoObj) // true

(2)寄生组合式继承的实现

寄生式继承就是将对象的原型式继承和工厂模式进行结合,即封装一个函数来实现继承的过程。而这样结合起来实现的继承,又可以称之为寄生组合式继承。下面就看看具体的实现过程吧。

  • 创建一个原型指向Person父类的对象,将其赋值给Student子类的原型;
  • 在上面的实现方案中,Student子类的实例对象的类型都是Person,可以通过重新定义constructor来优化;
// 定义Person父类公共的属性
function Person(name, age) {
  this.name = name
  this.age = age
}
// 定义Person父类的公共方法
Person.prototype.say = function() {
  console.log('I am ' + this.name)
}

// 定义Student子类特有的属性
function Student(name, age, sno) {
  // 通过call调用Person父类,创建自己的name和age属性
  Person.call(this, name, age)
  this.sno = sno
}

// 调用Object.create方法生成一个原型指向Person原型的对象,并将这个对象赋值给Student的原型
Student.prototype = Object.create(Person.prototype)
// 定义Student原型上constructor的值为Student
Object.defineProperty(Student.prototype, 'constructor', {
  configurable: true,
  enumerable: false,
  writable: true,
  value: Student
})

// 定义Student子类特有的方法
Student.prototype.studying = function() {
  console.log(this.name + ' studying')
}

// 实例化Student
const stu1 = new Student('curry', 30, 101111)
const stu2 = new Student('kobe', 24, 101112)
console.log(stu1) // Student { name: 'curry', age: 30, sno: 101111 }
console.log(stu2) // Student { name: 'kobe', age: 24, sno: 101112 }

内存表现:

JavaScript面向对象—继承的实现

 

总结:

  • 多个地方用到了继承,可以将上面的核心代码赋值在一个函数里面,如果不想用Object.create(),也可以使用上面封装的createObj函数;
  • function createObj(o) { function Fn() {} Fn.prototype = o return new Fn() } /** * @param {function} SubClass * @param {function} SuperClass */ function inherit(SubClass, SuperClass) { SubClass.prototype = createObj(SuperClass.prototype) Object.defineProperty(SubClass.prototype, 'constructor', { configurable: true, enumerable: false, writable: true, value: SubClass }) }
  • 寄生组合式实现继承的原理其实就是创建一个空对象用于存放子类原型上的方法,并且这个对象的原型指向父类的原型,在ES6中推出的class的实现原理就在这;

 

原文地址:
https://www.cnblogs.com/MomentYY/p/15999285.html



Tags:JavaScript   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
前言面向对象的三大特性:封装、继承和多态。上一篇我们简单的了解了封装的过程,也就是把对象的属性和方法封装到一个函数中,这一篇讲一下JavaScript中继承的实现,继承是面向对象...【详细内容】
2022-03-14  Tags: JavaScript  点击:(0)  评论:(0)  加入收藏
了解有关 JavaScript 箭头函数的所有信息。我们将向您展示如何使用 ES6 箭头语法,以及在代码中使用箭头函数时需要注意的一些常见错误。你会看到很多例子来说明它们是如何工...【详细内容】
2022-03-11  Tags: JavaScript  点击:(9)  评论:(0)  加入收藏
在变量中存储值是编程中的一个基本概念。变量的“范围”决定了它在整个程序中何时可用和不可用。理解 JavaScript 中的变量作用域是在语言中打下坚实基础的关键之一。本文将...【详细内容】
2022-03-10  Tags: JavaScript  点击:(13)  评论:(0)  加入收藏
本文将用JavaScript实现两个颇有技术含量的功能:压缩图片、加密图片。直入正题:一、压缩图片压缩原理:将图片读入canvas,并使用canvas的toDataURL方法将图片base64化,在此过程中,...【详细内容】
2022-03-02  Tags: JavaScript  点击:(21)  评论:(0)  加入收藏
简介nanoid是一个小巧、安全、URL友好、唯一的JS字符串ID生成器// 目前github的star是15.9Kgithub地址:https://github.com/ai/nanoid 特点 小巧,130 bytes (已压缩和 gzipped...【详细内容】
2022-02-24  Tags: JavaScript  点击:(27)  评论:(0)  加入收藏
Date对象是 JavaScript原生的时间库。它以1970年1月1日00:00:00作为时间的零点,可以表示的时间范围是前后各1亿天(单位为毫秒)。Date对象的创建:Date可以当作构造函数使用。对它...【详细内容】
2022-02-11  Tags: JavaScript  点击:(38)  评论:(0)  加入收藏
当谈到嵌入式开发时,您通常首先查看您在硬件级别尝试做什么。例如,如果您需要读取某种传感器并将公式应用于其值,以便您可以在某处显示结果,您可以查看哪些微控制器带有模数转换...【详细内容】
2022-02-11  Tags: JavaScript  点击:(36)  评论:(0)  加入收藏
预加载图片是提高用户体验的一个很好方法。图片预先加载到浏览器中,访问者便可顺利地在你的网站上冲浪,并享受到极快的加载速度。这对图片画廊及图片占据很大比例的网站来说十...【详细内容】
2022-01-04  Tags: JavaScript  点击:(46)  评论:(0)  加入收藏
说明面向对象的三个基本特征是:封装、继承、多态。call函数:预定义的JavaScript方法,用来调用所有者对象作为参数的方法。上节内容:面向对象,类、对象、属性、方法,静态属性和方法...【详细内容】
2022-01-04  Tags: JavaScript  点击:(50)  评论:(0)  加入收藏
JavaScript 的故事很长。在今天,JavaScript 的运行从移动设备到服务器端,无论您是计划在 2022 年学习或使用 JavaScript ,还是目前正在使用JavaScript进行开发,还是已经熟练掌握...【详细内容】
2021-12-30  Tags: JavaScript  点击:(57)  评论:(0)  加入收藏
▌简易百科推荐
前言面向对象的三大特性:封装、继承和多态。上一篇我们简单的了解了封装的过程,也就是把对象的属性和方法封装到一个函数中,这一篇讲一下JavaScript中继承的实现,继承是面向对象...【详细内容】
2022-03-14  可爱的雯雯子    Tags:JavaScript   点击:(0)  评论:(0)  加入收藏
了解有关 JavaScript 箭头函数的所有信息。我们将向您展示如何使用 ES6 箭头语法,以及在代码中使用箭头函数时需要注意的一些常见错误。你会看到很多例子来说明它们是如何工...【详细内容】
2022-03-11  走马Z    Tags:JavaScript   点击:(9)  评论:(0)  加入收藏
在变量中存储值是编程中的一个基本概念。变量的“范围”决定了它在整个程序中何时可用和不可用。理解 JavaScript 中的变量作用域是在语言中打下坚实基础的关键之一。本文将...【详细内容】
2022-03-10  走马Z    Tags:JavaScript   点击:(13)  评论:(0)  加入收藏
本文将用JavaScript实现两个颇有技术含量的功能:压缩图片、加密图片。直入正题:一、压缩图片压缩原理:将图片读入canvas,并使用canvas的toDataURL方法将图片base64化,在此过程中,...【详细内容】
2022-03-02  修丹道的程序猿    Tags:JavaScript   点击:(21)  评论:(0)  加入收藏
简介nanoid是一个小巧、安全、URL友好、唯一的JS字符串ID生成器// 目前github的star是15.9Kgithub地址:https://github.com/ai/nanoid 特点 小巧,130 bytes (已压缩和 gzipped...【详细内容】
2022-02-24  前端亦可行    Tags:JavaScript   点击:(27)  评论:(0)  加入收藏
Date对象是 JavaScript原生的时间库。它以1970年1月1日00:00:00作为时间的零点,可以表示的时间范围是前后各1亿天(单位为毫秒)。Date对象的创建:Date可以当作构造函数使用。对它...【详细内容】
2022-02-11  前端全家桶    Tags:内置对象   点击:(38)  评论:(0)  加入收藏
当谈到嵌入式开发时,您通常首先查看您在硬件级别尝试做什么。例如,如果您需要读取某种传感器并将公式应用于其值,以便您可以在某处显示结果,您可以查看哪些微控制器带有模数转换...【详细内容】
2022-02-11  粤嵌教育    Tags: JavaScript   点击:(36)  评论:(0)  加入收藏
api接口指的是应用程序编程接口(Application Programming Interface),是一些预先定义的函数,或指软件系统不同组成部分衔接的约定。用来提供应用程序与开发人员基于某软件或硬件...【详细内容】
2022-02-03  风度翩翩的番茄鸡蛋    Tags:Js   点击:(106)  评论:(0)  加入收藏
上文中咱们简单提到了JDK9中Flow接口中的静态内部类实现了响应式流的JAVA API,并且提供了一个一个Publisher的实现类SubmissionPublisher。本文将先梳理一下接口中具体的处理...【详细内容】
2022-02-03  蜜糖的代码注释    Tags:JDK   点击:(38)  评论:(0)  加入收藏
什么是闭包?相信很多人听了有点懵,然后去百度查了一下官方的定义“闭包就是能够读取其他函数内部变量的函数”发现感觉自己更懵了。关于闭包如果只看那官方的定义的话确实不是...【详细内容】
2022-01-21  编程小帅    Tags:JS   点击:(46)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条