浅谈JavaScript的几种继承实现方式
当前需求: 实现 Student 继承自 Person
如果手动实现继承效果, Person和Student分别写自己的属性和方法, 两个构造函数之间没有任何关联
- 代码编写繁琐
- 可维护性低
构造函数Person
- function Person(name, age, height, address) {
- this.age = age
- this.height = height
- this.address = address
- }
- Person.prototype.running = function() {
- console.log(“running~”)
- }
- Person.prototype.eating = function() {
- console.log(“eating~”)
- }
构造函数Student
- function Student(name, age, height, address, sno, score) {
- this.age = age
- this.height = height
- this.address = address
- this.sno = sno
- this.score = score
- }
- Student.prototype.running = function() {
- console.log(“running~”)
- }
- Student.prototype.eating = function() {
- console.log(“eating~”)
- }
- Student.prototype.studying = function() {
- console.log(“studying~”)
- }
内存图
希望满足的条件功能
Student构造函数满足以下条件
- 能够重写继承的方法, 但不修改Person原型上的方法.
- 能够增加方法, 但不会影响Person原型上的方法.
Student构建出的实例对象满足以下条件
- 有name, age, height, address属性, 并且扩充sno和score. 作为自己独立的属性.
- 继承running, eating方法, 和Person实例对象的方法有相同的引用.
利用原形链实现方法的继承
方式1: 子类原型指向父类原型
- function Student(age, height, address, sno, score) {
- this.age = age
- this.height = height
- this.address = address
- this.sno = sno
- this.score = score
- }
- + Stuednt.prototype = Person.prototype
内存图
缺点
父类和子类共享通一个原型对象, 修改了任意一个, 另外一个也被修改
方式2 子类原型指向父类实例对象
- function Student(sno, score) {
- this.sno = sno
- this.score = score
- }
- + var p = new Person()
- + Student.prototype = p
内存图
缺点
- 属性放在了原型上, 无法通过打印查看.
- 创建的多个实例对象, 继承的属性不互相独立, 一个实例对象修改属性影响其他的实例对象
- 要new一个实例, 怪怪怪怪怪怪
借用构造函数继承
方式3 组合继承
- function Person(name, age, height, address) {
- this.name = name
- this.age = age
- this.height = height
- this.address = address
- }
- Person.prototype.running = …
- function Student(age, height, address, sno, score) {
- + Person.call(this, age, height, address)
- this.sno = sno
- this.score = score
- }
- Student.prototype = new Person()
内存图
优点
解决之前的硬性问题, 实例对象属性独立, 属性放在对象内而不是原型上.
缺点
- 调用两次父类的构造方法, 性能浪费
Student.prototype = new Person()
第一次Person.call(this)
第二次
- 调用两次构造方法, 导致子类创建的实例对象上, 保留了两份父类的属性
- 一份在实例对象的
__proto__
上, new时产生的 - 一份在实例对象上, 通过借用构造方法call得到
- 一份在实例对象的
寄生式继承
思路
属性的继承已经解决, 通过Person.call(this)
解决.
方法的继承未解决, 需要找到 Student.prototype = new Person()
的替代方案
思路1
- var obj = {}
- obj.__proto__ = Person.prototype
- Student.prototype = obj
- // __proto__为浏览器增加的属性, 解决浏览器兼容性问题可以改为
- var obj = {}
- Object.setPrototypeOf(obj, Person.prototype)
- Student.prototype = obj
思路2
兼容所有浏览器 解决老版本浏览器不支持setPrototypeOf
- function F() {}
- F.prototype = Person.prototype
- Student.prototype = new F()
思路3
Object.create()
传入一个对象作为参数, 并返回一个对象, 返回的对象的原型为传入对象
- var obj = Object.create(Person.prototype)
- Student.prototype = obj
最终 方式4: 寄生组合式继承
- // 工具函数
- // 创建对象的过程
- function createObject(proto) {
- function F() {}
- F.prototype = proto
- return new F()
- }
- // 将Subtype和Supertype联系在一起
- // 寄生式函数
- function inherit(Subtype, Supertype) {
- Subtype.prototype = createObject(Supertype.prototype)
- Object.defineProperty(Subtype.prototype, “constructor”, {
- enumerable: false,
- configurable: true,
- writable: true,
- value: Subtype
- })
- }
- function Student(age, height, sno, score) {
- Person.call(this, age, height)
- this.sno = sno
- this.score = score
- }
- + inherit(Student, Person)
- // 使用方法
- Student.prototype.studying = function() {
- console.log(“studying”)
- }
使用Person.call
实现属性的继承
使用inherit
实现方法的继承
createObject
使Student.prototype指向Person的prototype, 但中间多一个构造函数F(), 解决方式1 的问题Object.defineProperty
实现Student.prototype
的constructor
属性指回Student构造函数.内存图
附: 扩充createObject
最初的设计思想, 是为了实现对象的继承, 所以有了以下的代码
createObject只能够做到构造一个有原型的空对象, 现在想要让构造的对象也有属性
- createInfo(proto, age, height) {
- const newObj = this.createObject(proto)
- newObj.age = age
- newObj.height = height
- return newObj
- }
到此这篇关于浅谈JavaScript的几种继承实现方式的文章就介绍到这了,更多相关JavaScrip 继承内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!
发表评论