JavaScript虽然是面向对象的程序设计语言,但与其他面向对象的语言有很大的不同,JavaScript没有真正的类,是基于原型的面向对象语言,用函数作为类的构造函数,通过复制构造函数的方式来模拟继承。
ES6加入了class
关键字来处理对象,但依旧是基于原型来实现的。JavaScript类的语法更像是一种语法糖。
继承的不同实现方式
继承是面向对象语言的核心特性之一,JavaScript使用原型实现继承。
继承最佳效果就是在不影响父类对象的实现下,使子类对象具有父类对象的特征,同时可以在不影响父类对象行为的情况下,扩展子类对象独有的特性。
如果希望对象的属性具有默认值,同时在运行时能修改这些默认值,可以在对象的原型中设置这些属性,而不是在构造函数中设置。一般情况下,静态属性写在构造函数中,动态属性和方法写在构造函数原型上。
原型链继承
实现方法:将父类的实例作为子类的原型
js
function Employee() {
this.name = '苏白'
}
Employee.prototype.sayName = function () {
console.log(this.name)
}
function Manager() {
this.reports = []
}
Manager.prototype = new Employee() // 核心
const clh = new Manager()
clh.sayName() // 苏白
当JavaScript执行new Manager()式,会先创造clh对象,并将这个对象中的[[prototype]]指向Manager.prototype,然后将该对象作为this的值传递给Manager()构造函数。
使用原型链继承创建子类时,不能向父类构造函数中传递参数
借用构造函数继承
实现:call()或者apply()复制父类的实例属性和方法给子类
js
function Employee() {
this.name = '苏白'
}
Employee.prototype.sayName = function () {
console.log(this.name)
}
function Manager() {
Employee.call(this) // 核心
this.reports = []
}
const clh = new Manager()
console.log(clh.name) // 苏白
// clh.sayName() // Uncaught TypeError: clh.sayName is not a function
借用构造函数继承完全没有用到原型,但可以向父类构造函数中传递参数
原型式继承
JavaScript中,空对象是整个原型继承体系的基础。
已有一个对象,在其基础上创建新对象,然后对返回的新对象进行适当修改,这样即使不定义构造函数也可以通过原型实现对象之间的信息共享
JS
function object(o) {
function F() {}
F.prototype = o
return new F()
}
const person = {
name: '苏白',
friends: ['崔洛恒']
}
const person1 = object(person)
person1.name = '邱文'
person1.friends.push('叶深')
console.log(person1.friends)
console.log(Object.getPrototypeOf(person1))
原型式继承的实现基于原型链,可以使用instanceof判断对象是否为某个类或其子类的实例
类式继承
使用Object.create()
可以实现类式继承,现有的对象提供新创建的对象原型。
这种继承方式是一种单继承,JavaScript的不同版本都支持。
js
// 父类
function Shape() {
this.x = 0
this.y = 0
}
// 父类的方法
Shape.prototype.move = function (x, y) {
this.x += x
this.y += y
console.log('Shape moved')
}
// 子类
function Rectangle() {
// 借用构造函数
Shape.call(this)
}
// 子类继承父类
Rectangle.prototype = Object.create(Shape.prototype)
// 重新指定构造函数
Rectangle.prototype.constructor = Rectangle
const rect = new Rectangle()
console.log(rect instanceof Rectangle) // true
console.log(rect instanceof Shape) // true
想要继承多个对象,可以使用
Object.assign()
寄生式组合继承
通过借用构造函数来继承属性,通过原型链形式继承方法。
本质:使用寄生式继承来继承超类型的原型,然后将结构指定给子类型的原型
寄生式继承主要创建一个封装基础过程的函数,在函数内部以某种方式来增强对象,最后返回对象
js
// 寄生式组合继承实现函数
function inheritPrototype(subType, superType) {
// 创建父类原型副本
let prototype = Object.create(superType.prototype)
// 修正子类原型构造函数
prototype.constructor = subType
// 将子类的原型替换成父类原型副本
subType.prototype = prototype
}
// 父类构造函数
function SuperType(name) {
this.name = name
this.colors = ['red', 'green', 'blue']
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
// 子类构造函数
function SubType(name, age) {
// 构造函数式继承------在子类构造函数中执行父类构造函数
SuperType.call(this, name)
this.age = age
}
// 对父类原型复制,不会两次调用父类的构造函数
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function () {
console.log(this.age)
}
const instance = new SubType('javascript', new Date().getFullYear() - 1995)
console.log(`${instance.name}已经${instance.age}岁了`)
// 指向SubType 若没有修正原型的构造函数,会指向父类构造函数
console.log(instance.constructor)
寄生式组合继承是实现继承比较理想的一种方式
类的继承
JavaScript中使用关键字class定义类,使用extends关键字可以继承任何拥有[[constructor]]和原型的对象。
也就是说,extends不仅可以用来继承类,也可以继承普通的构造函数。
语法格式:
javascript
class ChildClass extends ParentClass {}
父类可以是普通类、内置对象,也可以扩展null,但是新对象的原型不会继承Object.prototype()。
子类可以通过super关键字引用它们的原型(super只能在子类中使用,并且仅限于构造函数、原型方法和静态方法内部。)
在super()之前不能引用this,否则实例化时会报错。子类并没有自己的this对象,需要继承父类的this对象获取。
javascript
class Animal {
constructor(name) {
this.name = name
}
running() {
console.log(`${this.name}跑了`)
}
}
class Dog extends Animal {
constructor(name, age) {
super(name)
this.age = age
}
eating(){
console.log(`${this.name}今年${this.age}岁,能啃大骨头`)
}
}
const dog = new Dog('小白',1)
dog.running()
dog.eating()