1. 原型链继承
子类型的原型为父类型的一个实例对象。
function Person(name, age) {
//这里的是实例(私有)属性/方法
this.name = name;
this.age = age;
this.play = [1, 2, 3];
this.setName = function () {};
}
Person.prototype.setAge = function () {};
//这里的是原型(公有)属性/方法
function Student(price) {
this.price = price;
this.setScore = function () {};
}
//子类的原型指向父类的实例,这样就可以顺着原型链共享父类的方法了。并且为子类添加原型方法的时候,不会影响父类。
Student.prototype = new Person();
var s1 = new Student(1500);
var s2 = new Student(1400);
console.log(s1, s2);
子类的实例就可以通过__proto__访问到Student.prototype也就是Person的实例,这样就可以访问到父类的实例属性和方法;然后再通过Person实例的__proto__指向父类的prototype就可以获得到父类原型上的方法。这样就做到了将父类的实例 、原型属性和方法都当作子类的原型属性和方法。
特点:
- 父类新增原型属性 /方法,子类都能访问到
- 简单,易于实现
缺点:
- 创建子类实例时,无法向父类构造函数传参
- 继承后的所有属性都在原型上,被所有子实例共享。(如果父类的实例属性中有引用类型的属性,那么它被子类继承的时候会作为原型属性。这样子类1操作这个属性的时候,就会影响到子类2)
- 要想为子类中添加新的方法,一定要在替换原型的语句(Student.prototype = new Person())之后。
- 无法实现多继承
2. 借用构造函数继承
在子类型构造函数中用call()调用父类型构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.setName = function () {};
}
Person.prototype.setAge = function () {};
function Student(name, age, price) {
Person.call(this, name, age);
this.price = price;
}
var s1 = new Student('Tom', 20, 1500);
特点:
- 创建子类实例时,可以向父类的构造函数传递参数
- 子类实例不再共享父类私有属性中的引用属性,因为这些属性不在子类的原型上
- 可以实现多继承(call多个父类对象)
缺点:
- 只能继承父类的实例属性/方法,不能继承原型属性/方法
- 实例并不是父类的实例,只是子类的实例
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
3. 原型链+借用构造函数组合继承
调用父类构造函数,继承父类的实例属性并保留了传参的优点;然后将父类实例作为子类原型,继承了原型属性。
function Person(name, age) {
this.name = name;
this.age = age;
this.setName = function () {};
}
Person.prototype.setAge = function () {};
function Student(name, age, price) {
Person.call(this, name, age); //父类实例属性以这次为准
this.price = price;
this.setScore = function () {};
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
Student.prototype.sayHellop = function () {};
var s1 = new Student('Tom', 20, 1500);
console.log(s1);
console.log(s1.constructor);
融合了原型链继承和构造函数继承的优点。
但无论什么情况下都会调用两次构造函数。一次在创建子类型原型的时候,另一次实在子类型构造函数的内部。父类型的实例属性被重写了。
优点:
- 可以继承实例属性/方法,也可以继承原型属性/方法
- 可传参
- 不存在引用属性共享问题
- 函数可复用
缺点:
- 调用了两次父类构造函数,生成了两份实例
4. 组合继承优化
改变了原型链继承中,替换原型时创建对象的方法 。使用Student.prototype = Object.create(Person.prototype),来继承父原型对象的属性/方法。
构造函数继承不变。
实例和原型属性/方法都可以继承,可传参,还避免了调用两次父类构造函数。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setAge = function () {};
function Student(name, age, price) {
Person.call(this, name, age);
this.price = price;
this.setScore = function () {};
}
Student.prototype = Object.create(Person.prototype); //核心代码
Student.prototype.constructor = Student; //核心代码
var s1 = new Student('Tom', 20, 1500);
5. ES6中class的继承
ES6中引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的静态方法。
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.call(this));ES6的继承,实质是先将父类实例对象的属性和方法加到this上面,然后再用子类的构造函数修改this。
class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的。
//基于ES6 class的继承
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
showName() {
console.log('父类方法');
console.log(this.name);
}
}
class Student extends Person {
constructor(name, age, salary) {
super(name, age); //通过super()调用父类的构造方法,继承实例属性
this.salary = salary;
}
personShowName() {
return super.showName(); //通过super引用父类原型对象上的方法,继承原型方法
}
studentShowName() {
console.log('子类方法');
console.log(this.name, this.salary);
}
}
let s1 = new Student('baba', 38, 1000);
s1.personShowName();
s1.studentShowName();
可以使用super继承父类的实例方法,原型方法,静态方法
super关键字
super既可以当作函数使用,也可以当作对象使用。
1. super当作函数调用时,代表父类的构造函数。子类的构造函数中,必须使用super()调用一次父类的构造函数。实现实例属性/方法继承。
2. super当作对象时,在普通方法中指向父类的原型对象;在静态方法中,指向父类 。
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
printB() {
super.print(); //super指向父类的原型对象
}
}
let b = new B();
b.printB(); //2
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
static printB() {
super.print(); //super指向父类
}
}
B.x = 3;
B.printB(); //3
类的__proto__属性和prototype属性
- 子类的__proto__属性,表示构造函数的继承,总是指向父类
- 子类的prototype属性的__proto__的属性,表示方法的继承,总是指向父类的prototype属性
代码:
class A{
}
class B extends A{
}
B.__proto__ === A //true
B.prototype.__proto__ === A.prototype //true
实例的__proto__属性
子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。
class A {}
class B extends A {}
let a = new A();
let b = new B();
b.__proto__.__proto__ === a.__proto__; //true