一、原型链
//父类声明
function Parent(){
this.name = 'papa'
}
//为父类添加共有方法
Parent.prototype.greet = function(str){
console.log("parent say: ",str)
}
//声明子类
function Child(){
this.name = 'child'
}
//继承父类
Child.prototype = new Parent()
//验证继承是否成功
console.log(Child.prototype)
let xiaoming = new Child()
xiaoming.greet("hello, I am xiaoming")
继承结果:
注意事项:
这样的继承方式,实现的实质是以Parent的一个实例来重写Child的prototype属性。
引用类型的原型属性被所有的继承者共享,如果在某个继承者的实例上修改了原型的属性,那么所有的继承者继承的属性都会被修改:
二、借用构造函数(伪造对象、经典继承)
//Parent declaration
function Parent(name){
this.name = name || 'hua'
this.parent = ['nainai','yeye']
}
//为父类添加共有方法
Parent.prototype.greet = function(str){
console.log("parent say: ",str)
}
//声明子类时实现继承
function Child(name){
Parent.apply(this,arguments)
}
//是否继承
let xiaoMing = new Child('xiaoming')
console.log(xiaoMing)
继承结果:
注意事项:
通过借用构造函数来实现继承,无法继承Parent构造函数之外定义的方法,对比上面两图,第一种可以继承greet方法,第二种没有继承。
三、组合继承
组合继承其实就是将原型链继承和借用构造函数继承组合在一起。
实现思路: 使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。
优点: 通过在原型上定义方法实现了函数复用,同时能够保证每个实例有自己的属性,避免了原型链和借用构造函数的缺陷。此时引用类型属性(比如parent)其实是通过apply复制到子类中的,所以不会发生共享。
缺点: 它调用了两次父类的构造函数。
//Parent declaration
function Parent(name){
this.name = name || 'hua'
this.parent = ['nainai','yeye']
}
//为父类添加共有方法
Parent.prototype.greet = function(str){
console.log("parent say: ",str)
}
//声明子类时借用构造函数
function Child(name){
Parent.apply(this,arguments)
}
//重写prototype
Child.prototype = new Parent()
//是否继承
let xiaoMing = new Child('xiaoming')
xiaoMing.parent.push('mingPa','mingMa')
let Hong = new Child('hong')
console.log(Hong.parent)
继承结果:
四、寄生组合式继承
寄生组合式继承优化的部分:在组合继承的基础上减少一次多余的调用父类的构造函数。
//Parent declaration
function Parent(name){
this.name = name || 'hua'
this.parent = ['nainai','yeye']
}
//为父类添加共有方法
Parent.prototype.greet = function(str){
console.log("parent say: ",str)
}
//声明子类时借用构造函数
function Child(name){
Parent.apply(this,arguments)
}
//划重点!!!
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
//是否继承
let xiaoMing = new Child('xiaoming')
xiaoMing.parent.push('mingPa','mingMa')
let Hong = new Child('hong')
console.log("xiao ming's parents: ",xiaoMing.parent)
console.log('hong\'s parents: ',Hong.parent)
划重点的两部分:
- 第一行:没有像原型链继承一样直接将父类Parent的一个实例赋值给Child.prototype,而是使用Object.create()进行一次浅拷贝,将父类原型上的方法拷贝后赋给Child.prototype,这样子类上就能拥有了父类的共有方法,而且少了一次调用父类的构造函数。
Object.create()的浅拷贝的作用类似下面的函数:
function create(obj) {
function F() {};
F.prototype = obj;
return new F();
}
第二行:
因为对Parent的原型进行了拷贝后赋给Child.prototype,因此Child.prototype上的constructor属性也被重写了,所以要修复这一个问题。
继承结果:
五、ES6 —— 通过extends关键字实现继承
class Parent {
constructor(name){
this.name = name
}
greet(str){
console.log('hello, ',str)
}
}
class Child extends Parent {
constructor(name){
super(name)
this.name = name
}
}
let ming = new Child('ming')
ming.greet('world')
console.log(ming.name)
注意事项:
- 子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。所以只有调用super之后,才可以使用this关键字,否则会报错。子类实例的构建,基于父类实例,只有super方法才能调用父类实例。
- 如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。
class ColorPoint extends Point {
}
// 等同于
class ColorPoint extends Point {
constructor(...args) {
super(...args);
}
}
继承结果:
PS:
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。