原型链的问题:
- 子类(或者子构造函数)无法向父类传递参数(或父类构造函数)
- 父类属性子类共享(可能因一个子实例修改了父类的引用属性,而导致其他子实例共享的属性也被修改)
思维导图
盗用构造函数
也叫经典继承:在子类构造函数中调用父级构造函数并向其父级传递参数,使用 apply 或者 call 方法指定新的上下文(this 改变)
优点: 可以向父类传递参数;解决了原型链共享父类属性
缺点: 不能访问父类原型上定义的方法
function Parent(name) {
this.name = name;
this.friends = ["f1", "f2"];
}
Parent.prototype.say = function () {
log("parent_say");
};
function Son(name, other) {
// 盗用构造函数的精髓, 指定 this 调用父类构造函数【可以向父类构造函数传参数】
Parent.call(this, name);
this.other = other;
}
const son1 = new Son("son1", "other_son1");
const son2 = new Son("son2", "other_son2");
son1.friends.push("son1_friend");
log(son1.friends); // log: [ 'f1', 'f2', 'son1_friend' ]
// 盗用构造函数继承避免了原型链继承 共享父类属性造成的问题
log(son2.friends); // log: [ 'f1', 'f2' ]
// 盗用构造函数不能访问父类的原型方法
log(son2.say); // log: undefined
组合继承
综合原型链和盗用构造函数,将两者优点集中在一起; 既可以通过子类构造函数向父类构造函数传递参数,又可以访问父级原型上的方法,实现了共享方法
function Parent(name) {
this.name = name;
this.friends = ["f1", "f2"];
}
Parent.prototype.say = function () {
log("parent_pro_say");
};
function Son(name, other) {
// 调用了父类构造函数
Parent.call(this, name);
this.other = other;
}
// 又调用了一次父类构造函数
Son.prototype = new Parent();
Son.prototype.run = function () {
log("can_run_fast");
};
const son1 = new Son("son1", "son_other_1");
const son2 = new Son("son2", "son_other_2");
log(son1.friends); // log: [ 'f1', 'f2' ]
son1.friends.push("son1_new_friend");
log(son1.friends); // log: [ 'f1', 'f2', 'son1_new_friend' ]
son1.say(); // log: parent_pro_say
son1.run(); // log: can_run_fast
log(son2.friends); // log: [ 'f1', 'f2' ]; son1 和 son2 的属性独立