1. 原型链继承

function Parent1() {this.name = "Parent1"this.son = [1]
}
// 需要继承的子类
function Child1() {this.age = 18
}
// 修改Child1的原型对象
Child1.prototype = new Parent1()  // 用new创建一个父类的匿名实例对象// Child1的实例对象可以获取到自己构造函数的原型对象(即new Parent1())的属性和方法了
const c1 = new Child1()
console.log(c1.name)   // Parent1
console.log(c1.__proto__)  // Parent1 { name: 'Parent1', son: [ 1 ] }

不过这样会存在一个潜在问题:

// 再创建一个子类的实例对象
const c2 = new Child1()
c2.son.push(2)
console.log(c1.son, c2.son)  //[ 1, 2 ] [ 1, 2 ]

可以看到修改c2son属性,c1的son属性也跟着变了。这是因为两个实例使用的是同一个原型对象,内存空间是共享的。

当然如果换种方式修改是不会影响其他实例对象的:

c2.son = [1, 2]
conosle.log(c1.son, c2.son)  //[1], [1,2]

2. 构造函数继承

function Parent2() {this.name = "parent2"this.son = [1]
}function Child2() {// 执行Parent构造函数,借用call修改this指向为子类Child2Parent2.call(this)this.age = 18
}
const c1 = new Child2()
console.log(c1.name)  // Parent2// 不会有原型链继承的问题了
const c2 = new Child2()
c2.son.push(2)
console.log(c1.son, c2.son)  // [1], [1, 2]

因为每次调用构造函数就会开辟一块新的内存空间,所以是不会存在原型链继承共用内存空间的问题。

但是又会出现新的问题:

Parent2.prototype.getName = function() {return this.name
}
console.log(c2.getName())  // TypeError: c2.getName is not a function

可以看到,构造函数继承中,子类无法继承父类原型上的方法

3. 组合继承

function Parent3() {this.name = "parent3"this.son = [1]Parent3.prototype.getName = function() {return this.name}
}function Child3() {// 这一步让子类拥有了父类的实例属性和方法Parent3.call(this)   // 第二次调用Parent3this.age = 18
}// 手动挂上构造器,同时指回自己的构造函数
// 这两步让子类拥有了父类原型的属性和方法
Child3.prototype = new Parent3()   // 第一次调用 Parent3()
Child3.prototype.constructor = Child3  // 这一步不写似乎也可以,但是为避免意料之外的其他问题,最好还是加上const c1 = new Child3()
const c2 = new Child3()
c1.son.push(2)
console.log(c1.son, c2.son)  // [ 1, 2 ] [ 1 ]  不会互相影响
console.log(c1.getName(), c2.getName())  // parent3 parent3

结合上述两种继承方式的优缺点,可以轻松的写出这种组合继承的方法。

思路很简单:用构造函数继承实例属性和方法,用原型链继承原型属性和方法

这种方式是最JS中最常用的继承方式,但是仍然存在问题:

缺点也很直观:一次继承需要调用两次父类,这就造成了额外的性能开销(多构造了一次),如果父类的共有属性和方法极多,那么会大大降低执行效率。

4. 原型式继承

Object.create(proto [,descriptors]) —— 利用给定的 proto 作为 [[Prototype]] 和可选的属性描述来创建一个空对象。

可以用如下代码表述其原理(来自第二篇参考文章,红宝书中的一段引用):

function object(o) {function F() {}; //临时构造函数F.prototype = o; //传入对象o作为临时构造函数的原型对象return new F(); //返回临时构造对象实例
}
const parent4 = {name: "parent4",friends: ["p1", "p2", "p3"],getName: function() {return this.name;}};const s1 = Object.create(parent4);const s2 = Object.create(parent4);s1.name = "tom";s1.friends.push("jerry");s2.friends.push("lucy");console.log(s1.name); // tomconsole.log(s1.name === s1.getName()); // trueconsole.log(s2.name); // parent4console.log(s1.friends); // ["p1", "p2", "p3","jerry","lucy"]console.log(s2.friends); // ["p1", "p2", "p3","jerry","lucy"]

根据我个人的研究,也可以用其他方式取代 Object.create()

//(代码接上)
// 根据上面的原理代码,我们可以知道,Object.create()本质上最终修改的是对象的 [[prototype]] 隐藏属性,这个属性可以用__proto__获取到:
console.log(s1.__proto__ === parent4)  // true//那么直接使用__proto__也是可以的:
const s3 = {}
s3.__proto__ = parent4//或者使用Object.setPrototypeOf():
const s4 = {}
Object.setPrototypeOf(s4,parent4)

这样只是为了方便理解,这种继承方式肯定是推荐使用Object.create()的。关于这三种方法可以参考下这篇文章:原型方法,没有 proto 的对象

这种继承方式的缺点也很明显,因为Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能。

5. 寄生式继承

寄生式继承也没啥好说的,只是在上面继承的基础上做了点优化(因此缺点也是一样的),可以自主添加一些方法以增强功能:

const parent5 = {name: "parent5",friends: ["p1", "p2", "p3"],getName: function() {return this.name}
}// 封装一个继承的方法
function inherit(original) {let inherit = Object.create(original);// 需要额外添加的方法inherit.getFriends = function() {return this.friends}return inherit
}const person5 = clone(parent5)console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // ["p1", "p2", "p3"]

6. 寄生组合式继承

寄生组合继承利用寄继承的核心方法:Object.create() 优化了组合继承会额外执行一次父类构造函数的问题:

// 整段代码还是组合继承的代码:
function Parent3() {this.name = "parent3"this.son = [1]Parent3.prototype.getName = function() {return this.name}// 可以加上代码测试:// console.log(this)  // 组合式继承会多打印一次this,这个this是父类构造函数即Parent3// 而寄生组合式继承只会在创建实例对象时执行一次,打印对应的this即C1
}
function Child3() {Parent3.call(this)this.age = 18
}// Child3.prototype = new Parent3()
// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
Child3.prototype = Object.create(Parent3.prototype)   // !!!核心(主要就是替换了这一句代码)
Child3.prototype.constructor = Child3const c1 = new Child3()
console.log(c1.son)
console.log(c1.getName())

它利用 Object.create() 创建了父类构造函数原型的(副本)浅复制,并将其赋值给子类构造函数的原型,这样子类的实例对象就可以访问到父类的原型属性和方法,同时它不会额外调用一次父类。

同样的,根据在原型式继承里的探究,这句核心代码也可以用如下代码代替(当然,仅仅作一个探讨):

Child3.prototype.__proto__ = Parent3.prototype
//或者
Object.setPrototypeOf(Child3.prototype, Parent3.prototype)

寄生组合继承模式是目前最优的继承方式,其实ES6的extends的语法糖本质上也正与这种继承方式基本类似。

以上六种继承方式的简单总结:

7. ES6 - extends

直接一段代码结束吧:

class Car {constructor(width,speed) {this.width = widththis.speed = speed}
}
class Truck extends Car {constructor(width,speed){super(width,speed)this.width = 7  // 覆盖this.Container = true //重写}
}
const truck = new Truck(6,40)
console.log(truck);  // Truck { width: 7, speed: 40, Container: true }

参考文章:

面试官:Javascript如何实现继承?

寄生组合式继承

原型方法,没有 proto 的对象

JavaScript六种继承方式的递进推演相关推荐

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

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

  2. JavaScript 常见的六种继承方式

    方式一.原型链继承 这种方式关键在于:子类型的原型为父类型的一个实例对象. -------------------------------------------------------------- ...

  3. JavaScript对象继承方式

    一.对象冒充 其原理如下:构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式).因为构造函数只是一个函数,所以可使 Parent 构造函数 成为 Children 的方法, ...

  4. JavaScript中6种常见的继承方式

    为什么需要继承? 在实际编码的过程中,如果有很多类似的方法都存放于构造函数中,这样会导致内存的浪费(内存泄漏),在这种情况下我们就需要用到继承. 继承是什么? 所谓继承就是通过某种方式让一个对象可以访 ...

  5. Javascript中的几种继承方式比较

    原文地址 开篇 从'严格'意义上说,javascript并不是一门真正的面向对象语言.这种说法原因一般都是觉得javascript作为一门弱类型语言与类似java或c#之类的强型语言的继承方式有很大的 ...

  6. 前端进击的巨人(七):走进面向对象,原型与原型链,继承方式

    "面向对象" 是以 "对象" 为中心的编程思想,它的思维方式是构造. "面向对象" 编程的三大特点:"封装.继承.多态" ...

  7. Js理解之路:Js常见的6中继承方式

    目录 一.JS 实现继承的几种方式 第一种:原型链继承 二.构造函数继承(借助call方法) 三.组合继承(原型链继承+构造函数继承) 第四种:原型式继承(借助Object.create) 第五种:寄 ...

  8. 探究JS常见的6种继承方式

    先看以下百科对(面向对象的继承)的解释! 通过以上精炼实用的解释,我们可以了解到继承的基本作用和功能!即可以使得子类具有父类的属性和方法或者重新定义.追加属性和方法等. 广告:帮忙点击>> ...

  9. js常见的的6种继承方式

    继承是面向对象的,继承可以帮助我们更好的复用以前的代码,缩短开发周期,提高开发效率:继承也常用在前端工程技术库的底层搭建上,在整个js的学习中尤为重要 常见的继承方式有以下的六种 一.原型链继承 原型 ...

最新文章

  1. Antd-Design List渲染列中Button 点击事件 传递参数
  2. JVM最佳学习笔记一---Java内存区域与内存溢出异常
  3. PYTHON线程知识再研习F---队列同步Queue
  4. 浮层java_css保持浮层水平垂直居中的四种方法
  5. sequelize常见操作使用方法
  6. Linux Shell编程笔记10 Shell数组的补充
  7. iOS开发 - 动画实践系列
  8. SQLServer IP不能登录问题解决
  9. 数据库DB2性能优化高级进阶
  10. 逆向CrackMe-01写注册机
  11. 社会学转计算机博士,科学网—记我国社会计算学科第一位博士 - 王帅的博文
  12. Mac上有哪些软件可以代替Office或Word的软件?
  13. Window10系统启动问题——无法启动问题
  14. Ubuntu只读文件系统修复方法
  15. C语言(二)BMP图像 文本数据保存为图像
  16. 废旧三元锂电池回收提镍钴锰
  17. Android对外置sd卡的权限问题(上)
  18. U盘内容被病毒隐藏的解决办法(亲测可用)
  19. hypersion oracle_Oracle收购Hyperion(海波龙)带来的思考与机遇
  20. 关系的基本概念及其性质

热门文章

  1. (王道408考研操作系统)第二章进程管理-第一节5:线程概念和多线程模型
  2. LeetCode 37 解数独
  3. Centos下载离线包、安装离线包、删除现有包、是否安装(rpm)
  4. phpcms网站搬家至服务器
  5. RT-Thread 学习笔记(四)——添加RTGUI组件
  6. 附9 elasticsearch-curator + Linux定时任务
  7. C#开发微信门户及应用(32)--微信支付接入和API封装使用
  8. 《软件工程概论》第四章核心内容
  9. 是时候好好去学门脚本语言了
  10. J2EE数据库后台开发步骤(附HQL使用教程)