类的继承

ES5的继承

首先我们创建一个简单的父类

1
2
3
4
5
6
function Person(name){
this.name = name
}
Person.prototype.say = function(){
return 'hi'
}

继承父类
首先有一点需要说明,constructor属于不可枚举的属性

1
2
3
4
5
constructor:
configurable: true
enumerable: false // 不可枚举
value: ƒ Student(name, age)
writable: true
  • 组合继承 最常用的继承
1
2
3
4
5
6
7
8
9
10
11
12
13
function Student(name, age){
//拓展一个实例属性
Person.call(this, name); //其实很简单直接,就是调用一遍父类的this.x = x
this.age = age
}
//将子类的原型对象指向Person的实例
Student.prototype = new Person();
//将子类的原型对象的构造函数正确指回
Student.prototype.constructor = Student;
//新增一个实例方法
Student.prototype.sayAge = function(){
return this.age
};
  • 寄生组合继承 完美的继承

JavaScript高程里的写法,需要借助两个辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function inheritPrototype(subType, superType) {
let prototype = object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 赋值对象
}
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
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);
};

下边是网上的变种(倒是能用)

第一种方式,感觉不太好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Student(name, age){
Person.call(this, name);
this.age = age
}
// Object.assign 无法复制继承属性及不可枚举的数据 以及get set函数
Student.prototype = Object.assign(Object.create(Person.prototype), Student.prototype)
// 所以需要重新赋值constructor,但是用这种方法constructor属性就变为可枚举属性了
Student.prototype.constructor = Student;
Student.prototype.sayAge = function(){
return this.age
};

const student = new Student('lucy',30)
console.log(student);

第二种方式,可以解决枚举属性的问题

1
2
3
4
5
6
7
8
Student.prototype = Object.create(Person.prototype, {
constructor: {
value: Student,
enumerable: false,
writable: true,
configurable: true
}
})

第三种方式是我自己想的,结合Object.getOwnPropertyDescriptors()
该函数的参数是Student.prototype,第一次写的时候忘了写.prototype

1
2
3
4
Student.prototype = Object.create(
Person.prototype,
Object.getOwnPropertyDescriptors(Student.prototype)
)

下图是控制台输出,左边是组合继承,右边是寄生组合继承
控制台输出


我们为什么要把子类的prototype指向父类的实例而不是父类的prototype
如果我们把子类的原型对象直接指向父类的原型对象,扩展子类会影响到父类

将子类的原型对象直接指向父类的原型对象,给子类添加额外的原型方法

1
2
3
4
5
6
7
8
9
10
11
12
function Student(name, age){
Person.call(this, name);
this.age = age
}
Student.prototype = Person.prototype;
Student.prototype.constructor = Student;

Student.prototype.sayAge = function(){
return this.age
};
console.log(Person.prototype);
console.log(Student.prototype);

控制台结果
我们可以看到父类的prototype已经被子类改变了,这明显不符合我们的目的

ES5继承的帮助记忆

子类的prototype必须是某个对象(这个对象的__proto__必须指向父类的prototype)

满足这个条件的某个对象:

  • 父类的实例 组合继承
  • Object.create(父类.prototype) 寄生组合继承

满足条件后子类的实例可以通过两个__proto__查找,访问到父类原型对象,即

控制台输出

ES6的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
constructor(name){
this.name = name //实例属性
}
say(){ //原型方法
return 'hi'
}
}
class Student extends Person{
constructor(name, age){
super(name)
this.age = age
}
}
const student = new Student('lucy',30)
console.log(student);

控制台输出与ES5寄生组合继承一致

控制台结果