JavaScript六种继承方式的递进推演
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 ]
可以看到修改c2
的son
属性,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六种继承方式的递进推演相关推荐
- jQuery-源码阅读,JavaScript原生继承方式与jQuery中的继承
JavaScript中继承方法有以下几种: 1.原型链继承: function Book (name,author){this.name=name;this.author=author;}Book.p ...
- JavaScript 常见的六种继承方式
方式一.原型链继承 这种方式关键在于:子类型的原型为父类型的一个实例对象. -------------------------------------------------------------- ...
- JavaScript对象继承方式
一.对象冒充 其原理如下:构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式).因为构造函数只是一个函数,所以可使 Parent 构造函数 成为 Children 的方法, ...
- JavaScript中6种常见的继承方式
为什么需要继承? 在实际编码的过程中,如果有很多类似的方法都存放于构造函数中,这样会导致内存的浪费(内存泄漏),在这种情况下我们就需要用到继承. 继承是什么? 所谓继承就是通过某种方式让一个对象可以访 ...
- Javascript中的几种继承方式比较
原文地址 开篇 从'严格'意义上说,javascript并不是一门真正的面向对象语言.这种说法原因一般都是觉得javascript作为一门弱类型语言与类似java或c#之类的强型语言的继承方式有很大的 ...
- 前端进击的巨人(七):走进面向对象,原型与原型链,继承方式
"面向对象" 是以 "对象" 为中心的编程思想,它的思维方式是构造. "面向对象" 编程的三大特点:"封装.继承.多态" ...
- Js理解之路:Js常见的6中继承方式
目录 一.JS 实现继承的几种方式 第一种:原型链继承 二.构造函数继承(借助call方法) 三.组合继承(原型链继承+构造函数继承) 第四种:原型式继承(借助Object.create) 第五种:寄 ...
- 探究JS常见的6种继承方式
先看以下百科对(面向对象的继承)的解释! 通过以上精炼实用的解释,我们可以了解到继承的基本作用和功能!即可以使得子类具有父类的属性和方法或者重新定义.追加属性和方法等. 广告:帮忙点击>> ...
- js常见的的6种继承方式
继承是面向对象的,继承可以帮助我们更好的复用以前的代码,缩短开发周期,提高开发效率:继承也常用在前端工程技术库的底层搭建上,在整个js的学习中尤为重要 常见的继承方式有以下的六种 一.原型链继承 原型 ...
最新文章
- Antd-Design List渲染列中Button 点击事件 传递参数
- JVM最佳学习笔记一---Java内存区域与内存溢出异常
- PYTHON线程知识再研习F---队列同步Queue
- 浮层java_css保持浮层水平垂直居中的四种方法
- sequelize常见操作使用方法
- Linux Shell编程笔记10 Shell数组的补充
- iOS开发 - 动画实践系列
- SQLServer IP不能登录问题解决
- 数据库DB2性能优化高级进阶
- 逆向CrackMe-01写注册机
- 社会学转计算机博士,科学网—记我国社会计算学科第一位博士 - 王帅的博文
- Mac上有哪些软件可以代替Office或Word的软件?
- Window10系统启动问题——无法启动问题
- Ubuntu只读文件系统修复方法
- C语言(二)BMP图像 文本数据保存为图像
- 废旧三元锂电池回收提镍钴锰
- Android对外置sd卡的权限问题(上)
- U盘内容被病毒隐藏的解决办法(亲测可用)
- hypersion oracle_Oracle收购Hyperion(海波龙)带来的思考与机遇
- 关系的基本概念及其性质