本文将从最简单的例子开始,从零讲解在 JavaScript 中如何实现继承。

小例子

现在有个需求,需要实现 Cat 继承 Animal ,构造函数如下:

function Animal(name){this.name = name
}function Cat(name){this.name = name
}
复制代码

注:如对继承相关的 prototype、constructor、__proto__、new 等内容不太熟悉,可以先查看这篇文章:理性分析 JavaScript 中的原型

继承

在实现这个需求之前,我们先谈谈继承的意义。继承本质上为了提高代码的复用性。

对于 JavaScript 来说,继承有两个要点:

  1. 复用父构造函数中的代码
  2. 复用父原型中的代码

下面的内容将围绕这两个要点展开。

第一版代码

复用父构造函数中的代码,我们可以考虑调用父构造函数并将 this 绑定到子构造函数。

复用父原型中的代码,我们只需改变原型链即可。将子构造函数的原型对象的 __proto__ 属性指向父构造函数的原型对象。

第一版代码如下:

function Animal(name){this.name = name
}function Cat(name){Animal.call(this,name)
}Cat.prototype.__proto__ = Animal.prototype
复制代码

检验一下是否继承成功:我们在 Animal 的原型对象上添加 eat 函数。使用 Cat 构造函数生成一个名为 'Tom' 的实例对象 cat 。代码如下:

function Animal(name){this.name = name
}function Cat(name){Animal.call(this,name)
}Cat.prototype.__proto__ = Animal.prototype// 添加 eat 函数
Animal.prototype.eat = function(){console.log('eat')
}var cat = new Cat('Tom')
// 查看 name 属性是否成功挂载到 cat 对象上
console.log(cat.name) // Tom
// 查看是否能访问到 eat 函数
cat.eat() // eat
// 查看 Animal.prototype 是否位于原型链上
console.log(cat instanceof Animal) // true
// 查看 Cat.prototype 是否位于原型链上
console.log(cat instanceof Cat) //true
复制代码

经检验,成功复用父构造函数中的代码,并复用父原型对象中的代码,原型链正常。

图示

弊端

__proto__ 属性虽然可以很方便地改变原型链,但是 __proto__ 直到 ES6 才添加到规范中,存在兼容性问题,并且直接使用 __proto__ 来改变原型链非常消耗性能。所以 __proto__ 属性来实现继承并不可取。

第二版代码

针对 __proto__ 属性的弊端,我们考虑使用 new 操作符来替代直接使用 __proto__ 属性来改变原型链。

我们知道实例对象中的 __proto__ 属性指向构造函数的 prototype 属性的。这样我们 Animal 的实例对象赋值给 Cat.prototype 。不就也实现了Cat.prototype.__proto__ = Animal.prototype 语句的功能了吗?

代码如下:

function Animal(name){this.name = name
}function Cat(name){Animal.call(this,name)
}Cat.prototype = new Animal()
Cat.prototype.constructor = Cat
复制代码

使用这套方案有个问题,就是在将实例对象赋值给 Cat.prototype 的时候,将 Cat.prototype 原有的 constructor 属性覆盖了。实例对象的 constructor 属性向上查询得到的是构造函数 Animal 。所以我们需要矫正一下 Cat.prototype 的 constructor 属性,将其设置为构造函数 Cat 。

图示

优点

兼容性比较好,并且实现较为简单。

弊端

使用 new 操作符带来的弊端是,执行 new 操作符的时候,会执行一次构造函数将构造函数中的属性绑定到这个实例对象。这样就多执行了一次构造函数,将原本属于 Animal 实例对象的属性混到 prototype 中了。

第三版代码

考虑到第二版的弊端,我们使用一个空构造函数来作为中介函数,这样就不会将构造函数中的属性混到 prototype 中,并且减少了多执行一次构造函数带来的性能损耗。

代码如下:

function Animal(name){this.name = name
}function Cat(name){Animal.call(this,name)
}
function Func(){}
Func.prototype = Animal.prototypeCat.prototype = new Func()
Cat.prototype.constructor = Cat
复制代码

图示

ES6

使用 ES6 就方便多了。可以使用 extends 关键字实现继承, 复用父原型中的代码。使用 super 关键字来复用父构造函数中的代码。

代码如下:

class Animal {constructor(name){this.name = name}eat(){console.log('eat')}
}
class Cat extends Animal{constructor(name){super(name)}
}let cat = new Cat('Tom')
console.log(cat.name) // Tom
cat.eat() // eat
复制代码

相关知识点

  • 理性分析 JavaScript 中的 this
  • 理性分析 JavaScript 中的原型

参考书籍

  • 《JavaScript高级程序设计(第3版)》
  • 《Java核心技术 卷Ⅰ(第9版)》

手把手教你如何实现继承相关推荐

  1. 简单有趣的 NLP 教程:手把手教你用 PyTorch 辨别自然语言(附代码)

     简单有趣的 NLP 教程:手把手教你用 PyTorch 辨别自然语言(附代码) 雷锋网(公众号:雷锋网)按:本文作者甄冉冉,原载于作者个人博客,雷锋网已获授权. 最近在学pyTorch的实际应用 ...

  2. css布局方式_手把手教你CSS Flex布局「真香」

    作者:EcbJS 转发链接:https://blog.csdn.net/EcbJS/article/details/106466757 前言   之前做项目,关于布局方面没怎么深入研究过,好多页面都是 ...

  3. 手把手教我班小姐姐学java之多态

    文章目录 多态 动态绑定 声明类型 实际类型 工作机制 对象转换和Instanceof操作符 概念: 隐式转换(向上转型.自动类型转换) 显式转换( 向下转型.强制类型转换) instanceof关键 ...

  4. 怎么批量在数字里加入网页_手把手教你爬取天堂网1920*1080大图片(批量下载)——理论篇

    /1 前言/ 平时我们要下载图片,要要一个一个点击下载是不是觉得很麻烦?那有没有更加简便的方法呢?答案是肯定的,这里我们以天堂网为例,批量下载天堂网的图片. /2 项目准备工作/ 首先 我们第一步我们 ...

  5. 手把手教你写一个spring IOC容器

    本文分享自华为云社区<手把手教你写一个spring IOC容器>,原文作者:技术火炬手. spring框架的基础核心和起点毫无疑问就是IOC,IOC作为spring容器提供的核心技术,成功 ...

  6. python新手入门代码-新手必看:手把手教你入门 Python

    原标题:新手必看:手把手教你入门 Python 本文为 AI 研习社编译的技术博客,原标题 : Learning Python: From Zero to Hero 翻译 |永恒如新的日常校对 | 酱 ...

  7. iOS逆向-手把手教你写支付宝蚂蚁森林收集能量助手

    iOS逆向-手把手教你写支付宝蚂蚁森林收集能量助手 前言 发现iOS支付宝逆向的分析并不多,蚂蚁森林基于H5应用 套着UIWebView 基本也没这类JS和原生交互分析的帖子,就拿此练手吧 作技术分享 ...

  8. 手把手教你iPhone 3G手机软件开发

    手把手教你iPhone 3G手机软件开发 "iPhone 是一款革命性的.不可思议的产品,比市场上其它任何移动电话整整领先了五年,"苹果公司首席执行官史蒂夫·乔布斯如是说,&quo ...

  9. 手把手教你通过SpringBoot实现邮箱注册码验证

    文章目录 一.前言 二.创建SpringBoot工程项目 2.1 JWT认证 2.2 集成mybatis-plus实现用户的增删改查 2.3 编写Email工具类实现邮件的发送 2.4 验证码邮件发送 ...

最新文章

  1. winform程序打包EXE三种方式
  2. LINUX服务器开启防火墙并屏蔽恶意IP
  3. Java 到底是值传递还是引用传递
  4. 01、DFT-全面了解如何测试一颗芯片
  5. mybatis-启动源码分析
  6. 三大主流软件负载均衡器对比(LVS VS Nginx VS Haproxy)
  7. 我眼中的分布式系统可观测性
  8. 华为设备IS-IS路由控制原理与实验
  9. 【SpringBoot_ANNOTATIONS】组件注册 05 @Lazy 懒加载
  10. Vue引入Froala-Editor富文本编辑器
  11. 在 WindowMobile 上的模拟LED 显示屏插件(转)
  12. [LeetCode] Maxium Subarray
  13. 大O记法-BigO notation
  14. 银河帝国----基地边缘
  15. oracle rman delete backupset,RMAN Crosscheck后delete obsolete遇到RMAN-06091的解决
  16. 原生 js html 开发成桌面应用 以及打包
  17. Power BI——建模
  18. requirejs html缓存问题,requirejs 缓存问题
  19. Microsoft visual Studio2017 中番茄visual assist 破解
  20. 718保时捷spyder_【图】保时捷发布718 Spyder/Cayman GT4官图_汽车之家

热门文章

  1. 内存区划分;内存分配;堆、栈概念分析;动态内存管理数据结构及程序样例;核心态与用户态...
  2. C++函数中返回引用和返回值的区别
  3. JS字符串与二进制的相互转化
  4. bzoj 36733674 可持久化并查集加强版(可持久化线段树+启发式合并)
  5. POJ 2323 贪心
  6. sqlserver2008r2安装
  7. poj 2051 Argus(优先队列)
  8. jenkins 通过批处理自动构建 非标准项目
  9. iPhone/Mac Objective-C内存管理教程和原理剖析(二)口诀与范式转
  10. url.getinputsteam 获取不完整_年度营销方案合集,活动策划,规划推广,全内容完整套用告别加班...