前言

在阅读《Javascript高级程序设计》后的一些感想,顺便记录自己的学习,也希望能帮助到人,这就是我的初衷了。如文中有任何问题,也希望大家指正,以免误人子弟。

关于js的继承,我感觉更多的是一种探索得到的结果。因为像Java,C++ 等都是有明确的 class 定义类。但是在 Javascript 中的类和函数是无法区分的,我们似乎也只能定规范,比如构造函数模式,非严格规定:第一个字母大写的自定义函数就是构造函数。所以学继承似乎也是在阅览前辈们去提高代码复用,实现类效果的一个历史过程。

1. 原型链继承

基本思想: 利用原型让一个引用类型继承另一个引用类型的属性和方法

核心:原型链对象 变成 父类实例,子类就可以调用父类方法和属性。

function Parent() {
}
Parent.prototype.age = 18
Parent.prototype.getName = function () {return this.name
}function Child(name) {this.name = name
}
Child.prototype = new Parent()var child = new Child('leo')
// 这样子类就可以调用父类的属性和方法
console.log(child.getName())    // leo
console.log(child.age)          // 18
复制代码

优点: 实现简单。

缺点

  1. 引用类型值的原型属性会被所有实例共享。
  2. 不能向父类传递参数。

书中是说引用类型,而没说基本类型,我认为是因为往往定义在父类的值类型都是用来做全局的。

function Parent() {this.likeFood = ['水果', '鸡', '烤肉']
}
Parent.prototype.age = 18
Parent.prototype.getName = function () {return this.name
}function Child(name) {this.name = name
}
Child.prototype = new Parent()var chongqiChild = new Child('重庆孩子')
var guangdongChild = new Child('广东孩子')// 重庆孩子还喜欢吃花椒。。。
chongqiChild.likeFood.push('花椒')
console.log(chongqiChild.likeFood)      // ["水果", "鸡", "烤肉", "花椒"]
console.log(guangdongChild.likeFood)    // ["水果", "鸡", "烤肉", "花椒"]
复制代码

这时,会发现明明只是 重庆孩子 爱吃花椒,广东孩子 莫名奇妙得也变得爱吃了????这个共享是存在问题的,不科学的。(可能重庆孩子和广东孩子一起黑脸问号。。。)

至于第二个问题,其实也显而易见了,没有传递参数的途径。因此,第二种继承方式出来啦。

2. 借用构造函数继承

遗留问题

  1. 父类引用属性共享。
  2. 不能传参数到父类。

核心:子类构造函数内部调用父类构造函数,并传入 this指针。

// 2. 借用构造函数
function Parent(name) {this.name = namethis.likeFood = ["水果", "鸡", "烤肉"]
}
function Child(name) {Parent.call(this, name)
}
Parent.prototype.getName = function() {return this.name
}
var chongqingChild = new Child('重庆孩子')
var guangdongChild = new Child('广东孩子')
chongqingChild.likeFood.push('花椒')console.log(chongqingChild.likeFood)    //  ["水果", "鸡", "烤肉", "花椒"]
console.log(guangdongChild.likeFood)    //  ["水果", "鸡", "烤肉"]
console.log(chongqingChild.name)        //  "重庆孩子"
console.log(chongqingChild.getName())   //  Uncaught TypeError: chongqingChild.getName is not a function
复制代码

值得庆幸的是,这次只有我们 重庆孩子 喜欢吃花椒,广东孩子 没被标记爱吃花椒啦。并且,我们通过 call 方法将我们的参数也传入到了父类,解决了之前的遗留问题啦。

但是,原型链继承 是可以调用父类方法的,但是借用构造函数却不可以了,这是因为 当前子类的原型链并不指向父类了。因此,结合 第一,第二种继承方式,第三种继承方式应运而生啦。

3. 组合继承

核心: 前两者结合,进化更高级。

function Parent(name) {this.name = namethis.likeFood = ["水果", "鸡", "烤肉"]
}
function Child(name, age) {Parent.call(this, name)this.age = age
}
Parent.prototype.getName = function() {return this.name
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
Child.prototype.getAge = function() {return this.age
}var chongqingChild = new Child('重庆孩子', 18)
var guangdongChild = new Child('广东孩子', 19)
chongqingChild.likeFood.push('花椒')console.log(chongqingChild.likeFood)    // ["水果", "鸡", "烤肉", "花椒"]
console.log(guangdongChild.likeFood)    // ["水果", "鸡", "烤肉"]
console.log(chongqingChild.name)        // "重庆孩子"
console.log(chongqingChild.getName())   // "重庆孩子"
console.log(chongqingChild.getAge())    // 18
复制代码

这样:

  1. 原型引用类型传参共享问题
  2. 传参问题
  3. 调用父类问题都解决啦。
  • 三个问题都解决了,开心~这就是Javascript 的经典继承。

  • 但是有一个小缺点:在给 Child 原型赋值会执行一次Parent构造函数。所以,无论什么情况下都会调用两次父类构造函数

  • 但是,可能很多人会迷惑Child.prototype.constructor=Child,这句代码干了什么,这个就需要知道原型,原型链,构造函数之间的关系,我会下次写篇文章~


4. 原型式继承

这是在2006年一个叫 道格拉斯·克罗克福德 的人,介绍的一种方法,这种方法并没有使用严格意义上的构造函数

他的想法是 借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型

之前我有朋友没理解,我这里就对比的解释上面这两句话。

这之前的三种继承方式,我们都需要自己写自定义函数(例如,Parent和Child)。假如,现在已经有一个对象了,并且,我也只是想用你的属性,不想搞得那么麻烦的自定义很多函数。那怎么办呢?

核心: 我们需要创建一个临时的构造函数,并将作为父类的对象作为构造函数的原型,并返回一个新对象。

/*@function 实现继承 函数@param parent 充当父类的对象
*/
function realizeInheritance(parent) {// 临时函数function tempFunc() {}tempFunc.prototype = parentreturn new tempFunc()
}
复制代码

核心点说了,我们来尝试一下。

// 这个就是已有的对象
var baba = {name: "爸爸",likeFoods: ["水果", "鸡", "烤肉"]
}
/*var newChild = {} <==> baba  这两个对象建立关系就是这种继承的核心了。
*/
var child1 = realizeInheritance(baba)
var child2 = realizeInheritance(baba)
child1.likeFoods.push('花椒')
console.log(child1.likeFoods) //    ["水果", "鸡", "烤肉", "花椒"]
console.log(child2.likeFoods) //    ["水果", "鸡", "烤肉", "花椒"]
复制代码

我们可以发现,父类的属性对于子类来说都是共享的。所以,如果我们只是想一个对象和另一个对象保持一致,这将是不二之选。

当然,在 ES5 中,新增了个 Object.create(parentObject) 函数来更加便捷的实现上述继承

var baba = {name: "爸爸",likeFoods: ["水果", "鸡", "烤肉"]
}
var child1 = Object.create(baba)
var child2 = Object.create(baba)
child1.likeFoods.push('花椒')
console.log(child1.likeFoods) //    ["水果", "鸡", "烤肉", "花椒"]
console.log(child2.likeFoods) //    ["水果", "鸡", "烤肉", "花椒"]
复制代码

效果和上面相同~

5. 寄生式继承

这种继承是基于原型式继承,是同一个人想出来的,作者觉得,这样不能有子类的特有方法,似乎不妥。就用来一个种工厂模式的方式来给予子类一些独特的属性。

例如

function realizeInheritance(parent) {// 临时函数function tempFunc() {}tempFunc.prototype = parentreturn new tempFunc()
}
// Parasitic: 寄生的    inheritance: 继承    一个最简单的工厂函数。
function parasiticInheritance(object) {var clone = realizeInheritance(object)  // 这是用了原型式继承,但是只要是任何可以返回对象的方法都可以。clone.sayName = function() {console.log('我是'+this.name)}return clone
}
var baba = {name: "爸爸",likeFoods: ["水果", "鸡", "烤肉"]
}
var child = parasiticInheritance(baba)
child.name = '儿子'
child.sayName() // 我是儿子
复制代码

缺点:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率(每一个函数都是新的);这一点与构造函数继承类似。

6.寄生组合式继承

我们先回顾之前的 组合继承

function Parent(name) {this.name = namethis.likeFood = ["水果", "鸡", "烤肉"]
}
function Child(name, age) {Parent.call(this, name)         // 第二次调用this.age = age
}
Parent.prototype.getName = function() {return this.name
}
Child.prototype = new Parent()      // 第一次调用
Child.prototype.constructor = Child
Child.prototype.getAge = function() {return this.age
}
复制代码

这个两次调用的问题之前有提及过。过程大致:

  • 第一次调用,Child 的原型被赋值了 name 和 likeFood 属性
  • 第二次调用,注入this,会在Child 的实例对象上注入 name 和 likeFood 属性,这样就屏蔽了原型上的属性。

只要了问题,我们就来解决这个问题~

function Parent(name) {this.name = namethis.likeFood = ["水果", "鸡", "烤肉"]
}
function Child(name, age) {Parent.call(this, name)this.age = age
}
Parent.prototype.getName = function() {return this.name
}// Child.prototype = new Parent()  使用新方法解决
// Child.prototype.constructor = Child
inheritPrototype(Child, Parent)
function inheritPrototype(childFunc, parentFunc) {var prototype = realizeInheritance(parentFunc.prototype)   //创建对象,我们继续是用原型式继承的创建prototype.constructor = childFunc              //增强对象childFunc.prototype = prototype                //指定对象
}
function realizeInheritance(parent) {// 临时函数function tempFunc() {}tempFunc.prototype = parentreturn new tempFunc()
}Child.prototype.getAge = function() {return this.age
}var chongqingChild = new Child('重庆孩子', 18)
var guangdongChild = new Child('广东孩子', 19)
chongqingChild.likeFood.push('花椒')console.log(chongqingChild.likeFood)    // ["水果", "鸡", "烤肉", "花椒"]
console.log(guangdongChild.likeFood)    // ["水果", "鸡", "烤肉"]
console.log(chongqingChild.name)        // "重庆孩子"
console.log(chongqingChild.getName())   // "重庆孩子"
console.log(chongqingChild.getAge())    // 18
复制代码

这种方法的核心思想:

  • 首先,用一个空对象建立和父类关系。
  • 然后,再用这个空对象作为子类的原型对象。

这样,中间的对象就不存在new 构造函数的情况(这个对象本来就没有自定义的函数),这样就避免了执行构造函数,这就是高效率的体现。并且,在中间对象继承过程中,父类构造器也没有执行。所以,没有在子类原型上绑定属性。

这种继承方式也被开发人员普遍认为是引用类型最理想的继承范式。


7. class,extends

庆幸 ES6 中为我们提供了两个关键字可以很轻松的实现上述的继承方式:classextends

  • class 用来声明类,也就是js中充当类的自定义函数

  • extends 表示用哪个作为父类来继承

  • super 表示父类的构造器函数,可以向内部传参

class Person {constructor(name, age) {console.log('父类构造函数执行。')this.name = namethis.age = agethis.commonLikeFood = ["水果", "鸡", "烤肉"]}showInfo() {console.log('我是' + this.name + ', 我今年' + this.age + '岁了')}showLikeFood() {console.log('我是' + this.name + ', 我喜欢吃' + this.commonLikeFood)}
}class Child extends Person{constructor(name, age) {super(name, age)    // 像父类中传参,要写在第一行console.log('子类构造函数执行。')}
}let child1 = new Child('小明', 27)
let child2 = new Child('小红', 28)child1.showInfo()   // 我是小明, 我今年27岁了
child1.commonLikeFood.push('火锅')
child1.showLikeFood()   // 我是小明, 我喜欢吃水果,鸡,烤肉,火锅child2.showInfo()   // 我是小红, 我今年28岁了
child2.showLikeFood()   // 我是小红, 我喜欢吃水果,鸡,烤肉
复制代码

这样我们很轻松的实现了继承关系,也更加符合我们对类的定义,特别是和后端吻合度更高~

总结:

  • 模式(简述):

    • 工厂模式:创建中间对象,给中间对象赋添加属性和方法,再返回出去。
    • 构造函数模式:就是自定义函数,并用过 new 关键子创建实例对象。缺点也就是无法复用。
    • 原型模式: 使用 prototype 来规定哪一些属性和变量能被共享。
  • 继承
    • 原型链继承:

      • 优点:只调用一次父类构造函数,能复用原型链属性
      • 缺点:部分不想共享属性也被共享,无法传参。
    • 构造函数继承:
      • 优点:可以传参,同属性可以不被共享。
      • 缺点:无法使用原型链上的属性
    • 组合继承
      • 优点:可以传参,同属性可以不被共享,能使用原型链上的属性。
      • 缺点:父类构造函数被调用2次,子类原型有冗余属性。
    • 原型式继承:(用于对象与对象之间)
      • 优点:在对象与对象之间无需给每个对象单独创建自定义函数即可实现对象与对象的继承,无需调用构造函数。
      • 缺点:父类属性被完全共享。
    • 寄生式继承:
      • 优点:基于原型式继承仅仅可以为子类单独提供一些功能(属性),无需调用构造函数。
      • 缺点:父类属性被完全共享。
    • 寄生组合继承:
      • 优点:组合继承+寄生式继承,组合继承缺点在于调用两次父类构造函数,子类原型有冗余属性,寄生式继承的特性规避了这类情况,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。
    • class & extends:
      • 优点:个人认为是十分相似寄生组合继承,几乎可以说是寄生组合继承的语法糖。但还是有一点的区别,就是寄生组合继承是先创建子类实例对象,然后再对其增强;而ES6先将父类实例对象的属性和方法,通过调用super方法,添加到实例对象上,然后再用子类的构造函数,改变this指向。

转载于:https://juejin.im/post/5cc43911e51d45400f5d589a

Javascript 中继承汇总相关推荐

  1. JavaScript中继承的那些事

    引言 JS是一门面向对象的语言,但是在JS中没有引入类的概念,之前特别疑惑在JS中继承的机制到底是怎样的,一直学了JS的继承这块后才恍然大悟,遂记之. 假如现在有一个"人类"的构造 ...

  2. JavaScript中的继承入门

    正统的面相对象的语言都会提供extend之类的方法用于出来类的继承,但Javascript并不提供extend方法,在Javascript中使用继承需要用点技巧. Javascript中的实例的属性和 ...

  3. html5学习笔记---05.JavaScript 中的面向对象,继承和封装

    05.JavaScript 中的面向对象 a.创梦技术qq交流群:CreDream:251572072 a.JavaScript 是一种基于对象的语言   类:JavaScript 对象很抽象,所以下 ...

  4. jQuery-源码阅读,JavaScript原生继承方式与jQuery中的继承

    JavaScript中继承方法有以下几种: 1.原型链继承: function Book (name,author){this.name=name;this.author=author;}Book.p ...

  5. JavaScript中的load事件的作用_史上最全的web前端面试题汇总及答案JavaScript之二(二)...

    作者:樱桃小丸子儿 链接:https://www.jianshu.com/p/abadcc84e2a4 JavaScript JS的基本数据类型 number,string,boolean,objec ...

  6. JavaScript中的继承

    在JavaScript中没有Java中的exends关键字,只能通过其他的方式来实现继承关系. 1) 对象冒充 1 function Parent(username) 2 { 3 this.usern ...

  7. JavaScript中的原型(prototype)与继承

    在JavaScript中,原型是用来模仿其他「类」语言继承机制的基础.原型并不复杂,原型只是一个对象. 一.原型对象 1.1 什么是原型对象 每当我们创建了一个函数后,这个函数就拥有了一个protot ...

  8. JavaScript学习13 JavaScript中的继承

    JavaScript学习13 JavaScript中的继承 继承第一种方式:对象冒充 <script type="text/javascript">//继承第一种方式: ...

  9. javascript中的继承方式

    javascript中的继承方式有好几种. 下面分别举例供大家参考学习: 1.function parent() { this.x=1; } function child() { var instan ...

  10. 理解JavaScript中的原型继承(2)

    两年前在我学习JavaScript的时候我就写过两篇关于原型继承的博客: 理解JavaScript中原型继承 JavaScript中的原型继承 这两篇博客讲的都是原型的使用,其中一篇还有我学习时的错误 ...

最新文章

  1. Windows 比 Linux 好?我有 13 个反对理由
  2. ARP监控工具ARPalert
  3. Java提升篇——equals()方法和“==”运算符
  4. linux git指令不存在,一些常用的Git命令
  5. 【LeetCode】3月27日打卡-Day12
  6. 从零实现SpringBoot简易读写分离,也不难嘛!
  7. java redis pipeline,巧用 Redis pipeline 命令,解决真实的生产问题
  8. mysql封装增删改查_jdbc封装一行代码实现增删改查
  9. VC6编译64位程序
  10. Pycharm设置中文版
  11. 信息学奥赛一本通(C++版)
  12. excel职称计算机考试题怎么做,职称计算机考试EXCEL试题「附答案」
  13. 西工大第三届“探索·解密”趣味密码比赛-个人WriteUp
  14. 免费地图大战?阿里上将高德百度元帅百度地图
  15. 工厂不存在导致的采购订单无法修改报错处理
  16. Excel技能之查找筛选排序,同事竖起大拇指
  17. 关于光纤收发器的一些基本常识介绍
  18. 并发编程之深入理解十三:CompletionService CompletableFuture
  19. 【TWVRP】遗传算法求解带时间窗的含充电站车辆路径规划问题【含Matlab源码 1177期】
  20. Spring-02 IOC与DI

热门文章

  1. 【Nodejs篇三】Node js npm包管理工具
  2. HTTP 代理服务器技术选型之旅
  3. 【BZOJ】1176: [Balkan2007]Mokia(cdq分治)
  4. 幸福框架:可扩展的、动态的、万能的 编号生成器
  5. 戴尔软件部门第一弹 收购备份公司AppAssure
  6. stardict安装及其他
  7. c语言 实验6 数组实验报告,实验报告(实验六数组).doc
  8. DM9000驱动分析之发送
  9. Uboot下SPI FLASH的添加(SPI 控制器采用软件模拟的方式)
  10. Mmap的实现原理和应用