原理:让子类的原型等于父类的实例
function Animal() {
this.name = 'dog'
}
Animal.prototype.getName = function() {
console.log(this.name)
}
function Dog() {
}
Dog.prototype = new Animal()
const dog = new Dog()
console.log(dog.getName()) // 'dog'
优点:
继承了父类的所有属性和方法
缺点:
function Animal() {
this.name = ['dog']
}
function Dog() {
}
Dog.prototype = new Animal()
const dog1 = new Dog()
dog1.name.push('rabbit')
console.log(dog1.name) // ['dog', 'rabbit']
const dog2 = new Dog()
console.log(dog2.name) // ['dog', 'rabbit']
原理:在子类的构造函数中,通过 apply() 或 call() 的形式,调用父类构造函数。
function Animal() {
this.name = ['dog']
}
function Dog() {
Animal.call(this)
}
const dog1 = new Dog()
dog1.name.push('rabbit')
console.log(dog1.name) // ['dog', 'rabbit']
const dog2 = new Dog()
console.log(dog2.name) // ['dog']
优点(解决了原型链继承产生的问题):
缺点:
...
Animal.prototype.age = '111'
...
dog1.age // undefined
...
上面介绍的原型链继承和经典继承都有各自的缺点。
所以,我们考虑能否将这二者结合到一起,即在继承过程中,既可以避免引用类型属性共享,又能够复用一些属性和方法。
双剑合璧、天下无敌。
function Animal(name) {
this.name = name
}
Animal.prototype.getName = function() {
console.log(this.name)
}
function Dog(name) {
Animal.call(this, name)
}
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog
const dog = new Dog('dog')
dog.getName() // dog
优点:
缺点:
调用了两次父类构造函数
原理:Object.create 的模拟实现,将传入的对象作为创建的对象的原型。
function Animal(Obj) {
function F() {}
F.prototype = Obj
return new F()
}
const animal = {
name: 'dog',
fere: ['rabbit']
}
const dog1 = Animal(animal)
const dog2 = Animal(animal)
dog1.name = 'dog1'
console.log(dog2.name) // 'dog'
dog1.fere.push('cat')
console.log(dog2.fere) // ['dog', 'cat']
dog1.name值改变了,为什么dog2.name值未变,因为dog1不是改变的原型上面的name,而是给dog1加了name属性并赋值
dog1.name = 'dog1'
dog1.name// dog1
Object.getPrototypeOf(dog1).name // dog
缺点:
和原型链继承一样,引用类型的属性值都被共享
原理:原型式继承外面套一层壳子,在函数内部增强对象,最后返回对象
function Animal(Obj) {
function F() {}
F.prototype = Obj
return new F()
}
function create(obj) {
let Obj = Animal(obj)
Obj.getName = function() {
console.log(this.name)
}
return Obj
}
const animal = {
name: 'dog'
}
const dog1 = create(animal)
dog1.getName() //dog
缺点:
和经典继承一样,每次创建实例都会创建一遍父类构造函数中的方法
原理:寄生和组合结合
function Animal(name) {
this.name = name
}
Animal.prototype.getName = function() {
console.log(this.name)
}
function Dog(name) {
Animal.call(this, name)
}
function create(Obj) {
function F() {}
F.prototype = Obj
return new F()
}
function inheritPrototype(Child, Parent) {
let prototype = object(Parent.prototype); // 创建对象
prototype.constructor = Child; // 增强对象
Child.prototype = prototype; // 赋值对象
}
inheritPrototype(Dog, Animal)
优点:
修复了组合继承中调用两次构造函数的问题
class是ES6的新的语法糖,虽然是新语法,但背后的逻辑依旧是使用的原型链。
使用extends关键字,就可以继承任何拥有[[Construct]]和原型的对象。
// 继承类
class Animal {}
class Dog extends Animal {}
const dog = new Dog()
console.log(dog instanceof Dog) // true
console.log(dog instanceof Animal) // true
// 继承构造函数
function Animal1 {}
class Dog1 extends Animal1 {}
const dog1 = new Dog1()
console.log(dog1 instanceof Dog1) // true
console.log(dog1 instanceof Animal1) // true
派生类的方法可以使用super关键字引用他们的原型。这个关键字只能在派生类中使用,而且仅限于类构造函数、实例方法和静态方法内部。
在类构造函数中使用super可以调用父类构造函数。
class Animal {
constructor() {
this.name = 'dog'
}
}
class Dog extends Animal {
constructor() {
console.log(this); // Uncaught ReferenceError
super(); // 相当于super.constructor()
console.log(this instanceof Animal); // true
console.log(this); // Dog { name: 'dog' }
}
}
new Dog()
注意:不能在调用super之前引用this,否则会抛出错误ReferenceError
在静态方法中可以通过super调用继承的类上定义的静态方法
class Animal {
static getName() {
console.log('dog')
}
}
class Dog extends Animal {
static getName() {
super.getName()
}
}
Dog.getName() // 'dog'
注意:
class Animal {
constructor() {
super() // Uncaught SyntaxError: 'super' keyword unexpected here
}
}
class Animal {}
class Dog extends Animal {
constructor() {
console.log(super) // Uncaught SyntaxError: 'super' keyword unexpected here
}
}
class Animal {}
class Dog extends Animal {
constructor() {
super()
console.log(this instanceof Animal) // true
}
}
new Dog()
class Animal {
constructor(name) {
this.name = name
}
}
class Dog extends Animal {
constructor(name) {
super(name)
}
}
new Dog('dog') // Dog {name: 'dog'}
class Animal {}
class Dog extends Animal {
constructor() {
super()
}
}
class Cat extends Animal {
constructor() {
return {}
}
}
class Rabbit extends Animal {
constructor() {
}
}
new Dog() // Dog {}
new Cat() // {}
new Rabbit() // Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
有时我们需要一个可供其他类继承,但自身不会被实例化的类。new.target保存通过new关键字调用的类或函数。通过在实例化时检测new.target是不是抽象基类,可以阻止对抽象基类的实例化:
class Animal {
constructor() {
if(new.target === Animal) {
throw new Error('Animal cannot be directly instantiated');
}
}
}
class Dog extends Animal {}
new Dog() // class Dog {}
new Animal() // class Animal {}
// Error: Animal cannot be directly instantiated
另外,还可以通过在抽象基类构造函数中进行检查,可以要求派生类必须定义某个方法
class Animal {
constructor() {
if(new.target === Animal) {
throw new Error('Animal cannot be directly instantiated');
}
if(!this.getName) {
throw new Error('Inheriting class must define getName');
}
}
}
class Dog extends Animal {
getName() {}
}
class Cat extends Animal {}
new Dog()
new Cat() // Error: Inheriting class must define getName
class reverseString extends String {
// 字符串翻转
reverse() {
return this.split('').reverse().join('')
}
}
let a = new reverseString('12345')
console.log(a instanceof String) // true
console.log(a instanceof reverseString) // true
console.log(a); // 12345
console.log(a.reverse()) // 54321