JavaScript面向对象—继承的实现

前言

面向对象的三大特性:封装、继承和多态。上一篇我们简单的了解了封装的过程,也就是把对象的属性和方法封装到一个函数中,这一篇讲一下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的原型链也是有尽头的,这个尽头就是Object的原型。

2.Object的原型

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

(1)打印Object的原型

console.log(Object.prototype)
  • 在node环境中:

  • 在浏览器中:

(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

内存表现:

缺点

  • 从内存表现图中就可以看出,当打印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 }

内存表现:

缺点:

  • 在实现继承的过程中,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 }

内存表现:

总结:

  • 多个地方用到了继承,可以将上面的核心代码赋值在一个函数里面,如果不想用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的实现原理就在这;

JavaScript面向对象—继承的实现的更多相关文章

  1. javaScript面向对象继承方法经典实现

    转自原文javaScript面向对象继承方法经典实现 JavaScript的出现已经将近20多年了,但是对这个预言的褒贬还是众说纷纭.很多人都说JavaScript不能算是面向对象的变成语言.但是Ja ...

  2. JavaScript面向对象继承方法

    JavaScript的出现已经将近20多年了,但是对这个预言的褒贬还是众说纷纭.很多人都说JavaScript不能算是面向对象的变成语言.但是JavaScript的类型非常松散,也没有编译器.这样一来 ...

  3. JavaScript 面向对象继承详解

    题记 由于js不像java那样是完全面向对象的语言,js是基于对象的,它没有类的概念.所以,要想实现继承,一般都是基于原型链的方式: 一.继承初探 大多数JavaScript的实现用 __proto_ ...

  4. Javascript 面向对象-继承

    JavaScript虽然不是面向对象的语言,但是我们通过构造可以让其支持面向对象,从而实现继承.重写等面向对象的特性.具体代码如下: //创建类Person function Person(age,n ...

  5. javascript面向对象——继承

    javascript和其他语言相比,它没有真正意义上的继承,也不能从一个父类extends,要实现它的继承可以通过其他方式来实现: 步骤:1.继承父类的属性 2.继承父类的原型 下面就以一个拖拽为例子 ...

  6. JavaScript面向对象--继承 (超简单易懂,小白专属)

    一.继承的概念 子类共享父类的数据和方法的行为,就叫继承. 二.E55如何实现继承?探索JavaScript继承的本质 2.1构造函数之间的"复制粘贴" 第一条路是通过构造函数来继 ...

  7. JavaScript 面向对象继承的实现

    <script type="text/javascript"> function Animal () { this.species="Animal" ...

  8. javascript面向对象继承和原型

    一.理解什么是对象:任何东西都可以是对象,对象就是一组无序属性的集合 对象具有属性和方法1.1 属性的类型属性内部又定义了两种属性:数据属性和访问器属性 (1)数据属性:有4个描述的行为 Config ...

  9. Javascript面向对象特性实现封装、继承、接口详细案例——进级高手篇

    Javascript面向对象特性实现(封装.继承.接口) Javascript作为弱类型语言,和Java.php等服务端脚本语言相比,拥有极强的灵活性.对于小型的web需求,在编写javascript ...

随机推荐

  1. 【免杀技术】Tomcat内存马-Filter

    Tomcat内存马-Filter型 什么是内存马?为什么要有内存马?什么又是Filter型内存马?这些问题在此就不做赘述 Filter加载流程分析 tomcat启动后正常情况下对于Filter的处理过 ...

  2. .NET6: 开发基于WPF的摩登三维工业软件

    MS Office和VisualStudio一直引领着桌面应用的时尚潮流,大型的工业软件一般都会紧跟潮流,搭配着Ribbon和DockPanel风格的界面.本文将介绍WPF下两个轻量级的Ribbon和 ...

  3. 「SHOI2006」有色图

    首先发现这题虽然是边的置换,但是是由点的置换所造成的,并且发现点置换对应的所有边置换和置换操作构成置换群. 由于颜色可以全选,那么根据 Polya 定理,答案为: \[|X / G| = \frac{ ...

  4. Argo 安装和 workflow 实例配置文件解析

    一.Argo 安装配置 1.1 Argo 安装 $ kubectl create ns argo $ kubectl apply -n argo -f https://raw.githubuserco ...

  5. Tomcat临时目录及java.io.tmpdir对应的目录

    最近客户现场的技术支持接连反馈了一个问题:导入数据的时候,上传的excel会在服务器上生成一个临时文件,而这个临时文件都在  tomcat 的安装目录下,如果上传次数比较多的话,就会导致tomcat安 ...

  6. 如何生成Java文档注释(Java Doc Comments)

    在我们的Java SDK中已经提供了javadoc工具来生成我们的文档. 所以我们可以手动调用javadoc工具来生成文档,或者通过IDE生成.当然IDE也是调用javadoc,不过更快更省事. 注释 ...

  7. linux计划任务之cron

    目录 cron计划任务之用户级 cron计划任务之系统级 cron计划任务之用户级 1.安装crond centos7 执行命令: # yum install -y crontabs /bin/sys ...

  8. 编写PHP扩展

    转载请注明来源:https://www.cnblogs.com/hookjc/ PHP 5.2 环境的扩展(PHP Extension) 需求:比如开发一个叫做 heiyeluren  的扩展,扩展里 ...

  9. matlab构建栅格地图绘图思路

    matlab构建栅格地图绘图思路 近来因研究需要,调研并思考了栅格地图的生成方法,暂时总结以备不时之需. 栅格的建立最需要注意栅格粒度的问题,即根据需要调整栅格的边长,目前有两种思路一种是固定栅格边长 ...

  10. 02网络编程( socket套接字+TCP粘包 )

    目录 02 网络编程 一.socket套接字编程 二.简易代码模板 2.1 服务端 2.2 客户端 三.通信循环及代码优化 四.黏包现象 五.struct模块 六.简易版本报头 七.上传文件数据 * ...