一、原型链继承
function Person () {
// 属性
this.colors = ["red", "blue", "green"];
}
// 原型方法
Person.prototype.sayHello = function () {
console.log("hello word!");
}
function Student () { }
// 继承了 Parent
Student.prototype = new Person();
// 创建实例对象 child1
var student1 = new Student();
student1.sayHello(); // hello word!
student1.colors.push("yellow");
console.log(student1.colors); // ["red", "blue", "green", "yellow"]
// 创建实例对象 child2
var student2 = new Student();
console.log(student2.colors); // ["red", "blue", "green", "yellow"]
优点:
- 子类可以访问父类原型上的方法
- 简单,易于实现
缺点:
- 父类的属性子被所有实例共享;
- 创建子类实例时,不能向父类构造函数中传参数
二、借用构造函数(伪造对象或经典继承)
使用 apply() 和 call() 方法在新创建的对象上执行构造函数
function Person (name) {
this.name = name;
}
Person.prototype.sayName = function () {
console.log(this.name);
}
function Student (name) {
Person.call(this, name);
}
var student1 = new Student("Pitt");
console.log(student1.name); // Pitt
student1.sayName(); // 报错
优点:
- 子类构造函数可以向父类构造函数中传递参数
- 可以实现多继承(
call/apply多个父类对象)
缺点:
- 子类无法访问父类原型上的方法
三、组合继承(伪经典继承)
将原型链和借用构造函数的技术组合到一块,既避免了两者的缺陷,又发挥了它们的优点。是JavaScript中最常用的继承模式。
function Person (name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
Person.prototype.sayName = function () {
console.log(this.name);
}
function Student (name, age) {
// 继承属性
Person.call(this, name); //第二次调用 Person()
this.age = age;
}
// 继承方法
Student.prototype = new Person(); // 第一次调用 Person()
Student.prototype.constructor = Student;
Student.prototype.sayAge = function () {
console.log(this.age);
};
var student1 = new Student("Pitt", 18);
student1.colors.push("black");
console.log(student1.colors); //["red", "blue", "green", "black"]
student1.sayName(); // Pitt
student1.sayAge(); // 18
var student2 = new Student("Bob", 20);
console.log(student2.colors); //["red", "blue", "green"]
student2.sayName(); // Bob
student2.sayAge(); // 20
优点:
- 可以继承父类原型上的属性,可以传参,可复用。
- 每个新实例引入的构造函数属性是私有的。
缺点:
- 调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
四、寄生组合继承
前面说过,组合继承是 JavaScript 最常用的继承模式;不过,它也有自己的不足。我们再来看下组合继承的例子。
function Person (name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
Person.prototype.sayName = function () {
console.log(this.name);
}
function Student (name, age) {
Person.call(this, name); //第二次调用 Person()
this.age = age;
}
Student.prototype = new Person(); // 第一次调用 Person()
Student.prototype.constructor = Student;
Student.prototype.sayAge = function () {
console.log(this.age);
};
在第一次调用 Parent 构造函数时,Child.prototype 会得到两个属性:name 和 colors;它们都是 Parent 的实例属性,只不过现在位于 Child 的原型中。当调用 Child 构造函数时,又会调用一次 Parent 构造函数,这一次又在新对象上创建了实例属性 name 和 colors 。于是,这两个属性就屏蔽了原型中的两个同名属性。
下面来看下寄生组合式继承:
function Parent (name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.sayAge = function () {
console.log(this.age);
}
趋近完美的继承方式。
五、ES6继承
ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
class Person {
constructor(name) {
this.name = name;
}
// 方法写在constructor后面
sayName() {
console.log(this.name);
}
}
// 通过extends关键字实现继承
class Student extends Person {
constructor(name, age) {
// 子类必须在constructor方法中调用super方法
super(name);
this.age = age;
}
sayAge() {
console.log(this.age);
}
}
let student1 = new Student("Pitt", 18);
student1.sayName(); // Pitt
student1.sayAge(); // 18