JavaScript继承的实现
继承是面向对象的,使用这种方式我们可以更好地复用以前的开发代码,缩短开发周期、提高开发效率。
一、JS实现继承的几种方式
第一种:原型链继承
原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针。
function Parent() {
this.name = 'parent'
this.arr = [1, 2]
}
function Child() {
this.name = 'child'
}
Child.prototype = new Parent()
const child1 = new Child()
console.log(child1.name, child1.arr)
// 父类的方法和属性都能够访问,但其实有潜在问题
const child2 = new Child()
child2.arr.push(3)
console.log(child2.arr, child1.arr)
// 因为两个实例使用的是同一个原型对象,它们的内存空间是共享的,当一个发生变化的时候,另外一个也随之进行了变化,这就是使用原型链继承方式的一个缺点
第二种:构造函数继承(借助 call)
function Parent() {
this.name = 'parent'
}
Parent.prototype.getName = function () {
return this.name
}
function Child() {
Parent.call(this)
this.type = 'child'
}
let child1 = new Child()
console.log(child1)
// console.log(child1.getName())
// 构造函数实现继承的优点,使父类的引用属性不会被共享,优化了第一种继承方式的弊端
// 不过,只能继承父类的实例属性和方法,不能继承原型属性或方法
第三种:组合继承(前两种方式)
function Parent() {
this.name = 'parent'
this.arr = [1, 2]
}
Parent.prototype.getName = function () {
return this.name
}
function Child() {
// 第二次调用 Parent()
Parent.call(this)
this.type = 'child'
}
// 第一次调用 Parent()
Child.prototype = new Parent()
// 手动挂上构造器,指向自己的构造函数
Child.prototype.constructor = Child
let child1 = new Child()
let child2 = new Child()
child1.arr.push(3)
console.log(child1.arr, child2.arr)
// 不互相影响
console.log(child1.getName())
console.log(child2.getName())
// 正常输出'parent'
// Parent执行了两次,第一次是改变Child的prototype的时候,第二次是通过call方法调用Parent的时候,那么Parent多构造一次就多进行了一次性能开销
第四种:原型式继承
ES5的Object.create方法,可以接收两个参数:
- 用作新对象原型的对象
- 为新对象定义额外属性的对象(可选参数)
let parent = {
name: 'parent',
friends: ['p1', 'p2', 'p3'],
getName: function () {
return this.name
},
}
let parent1 = Object.create(parent)
parent1.name = 'tom'
parent1.friends.push('jerry')
let parent2 = Object.create(parent)
parent2.friends.push('lucy')
console.log(parent1.name)
console.log(parent1.name === parent1.getName())
console.log(parent2.name)
console.log(parent1.friends)
console.log(parent2.friends)
// 多个实例的引用类型属性指向相同的内存,存在篡改的可能
第五种:寄生式继承
使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承
let parent = {
name: 'parent',
friends: ['p1', 'p2', 'p3'],
getName: function () {
return this.name
},
}
function clone(original) {
let clone = Object.create(original)
clone.getFriends = function () {
return this.friends
}
return clone
}
let parent1 = clone(parent)
console.log(parent1.getName())
console.log(parent1.getFriends())
第六种:寄生组合式继承
结合第四种中提及的继承方式,解决普通对象的继承问题的Object.create方法,我们在前面这几种继承方式的优缺点基础上进行改造,得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式
function clone(parent, child) {
// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
child.prototype = Object.create(parent.prototype)
child.prototype.constructor = child
}
function Parent() {
this.name = 'parent'
this.arr = [1, 2, 3]
}
Parent.prototype.getName = function () {
return this.name
}
function Child() {
Parent.call(this)
this.friends = 'child'
}
clone(Parent, Child)
Child.prototype.getFriends = function () {
return this.friends
}
let parent1 = new Child()
console.log(parent1)
console.log(parent1.getFriends())
console.log(parent1.getName())
ES6 的 extends 关键字实现逻辑
我们可以利用ES6里的extends的语法题,使用关键词很容易直接实现JS的基础,但是想深入了解extends语法糖是怎么实现的,就得深入研究extends的底层逻辑
- 因为浏览器的兼容性问题,如果遇到不支持ES6的浏览器,那么就得利用babel这个编译工具,将ES6的代码编译成ES5,让一些不支持新语法的浏览器也能运行
// class Person {
// constructor(name) {
// this.name = name
// }
// // 原型方法
// // 即 Person.prototype.getName = function() {}
// // 下面可以简写为 getName() {...}
// getName = function () {
// console.log('Perosn', this.name)
// }
// }
// class Gamer extends Person {
// constructor(name, age) {
// // 子类中存在构造函数,则需要在使用“this”之前首先调用 super()
// super(name)
// this.age = age
// }
// }
// const asuna = new Gamer('Asuna', 20)
// asuna.getName()
function _possibleConstructorReturn(self, call) {
// ...
return call && (typeof call === 'object' || typeof call === 'function')
? call
: self
}
function _inherits(subClass, superClass) {
// 这里可以看到
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true,
},
})
if (superClass)
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass)
}
var Parent = function Parent() {
// 验证是否是Parent构造出来的this
classCallCheck(this, Parent)
}
var Child = (function () {
_inherits(Child, Parent)
function Child() {
_classCallCheck(this, Child)
return _possibleConstructorReturn(
this,
(Child.proto || Object.getPrototypeOf(Child)).apply(this, arguments)
)
}
return Child
})(Parent)