一、ES5实现继承
原型链的核心只需要记住三点:
- 每个
实例对象
都有__proto__属性,该属性指向其构造函数的原型对象
,在调用实例的方法和属性时,如果在实例对象上找不到,就会往原型对象上找。 - 构造函数的prototype属性也指向实例的原型对象
- 原型对象的constructor属性指向构造函数。
①原型链继承:
原型链继承的原理很简单,直接让子类的原型对象指向父类实例
,当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承。
// 父类
function Parent() {
this.name = '29'
}
// 父类的原型方法
Parent.prototype.getName = function() {
return this.name
}
// 子类
function Child() {
}
// 让子类的原型对象指向父类实例, 这样一来在Child实例中找不到的属性和方法就会到原型对象(父类实例)上寻找
Child.prototype = new Parent()
Child.prototype.constructor = Child // 根据原型链的规则,顺便绑定一下constructor, 这一步不影响继承, 只是在用到constructor时会需要
// 然后Child实例就能访问到父类及其原型上的name属性和getName()方法
const child = new Child()
child.name // '29'
child.getName() // '29'
原型继承的缺点:
由于所有Child实例的隐式原型都指向同一个Parent实例, 因此对某个Child实例的
父类引用类型变量
修改会影响所有的Child实例
在创建子类实例时无法向父类构造传参, 即没有实现super()的功能
// 示例:
function Parent() {
this.name = ['29kun']
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
// 测试
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name) // ['foo']
console.log(child2.name) // ['foo'] (预期是['29kun'], 对child1.name的修改引起了所有child实例的变化)
②构造函数继承:
构造函数继承,即在子类的构造函数中执行父类的构造函数
,并为其绑定子类的this
,让父类的构造函数把成员属性和方法都挂到子类的this上去,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参
function SuperType(name) {
this.name = name;
this.colors = ['pink', 'blue', 'green'];
}
function SubType(name) {
SuperType.call(this, name);
}
let instance1 = new SubType('Yvette');
instance1.colors.push('yellow');
console.log(instance1.colors);//['pink', 'blue', 'green', yellow]
let instance2 = new SubType('Jack');
console.log(instance2.colors); //['pink', 'blue', 'green']
构造函数继承的缺点:
方法都在构造函数中定义,方法无法复用。
③组合继承(原型链 + 借用构造函数)
使用原型链实现对原型属性和方法的继承
,通过借用构造函数来实现对实例属性的继承
,既通过在原型上定义方法来实现了函数复用,又保证了每个实例都有自己的属性
。
function SuperType(name) {
this.name = name;
this.colors = ['pink', 'blue', 'green'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
}
function SuberType(name, age) {
SuperType.call(this, name);
this.age = age;
}
SuberType.prototype = new SuperType();
SuberType.prototype.constructor = SuberType;
SuberType.prototype.sayAge = function () {
console.log(this.age);
}
let instance1 = new SuberType('Yvette', 20);
instance1.colors.push('yellow');
console.log(instance1.colors); //[ 'pink', 'blue', 'green', 'yellow' ]
instance1.sayName(); //Yvette
let instance2 = new SuberType('Jack', 22);
console.log(instance2.colors); //[ 'pink', 'blue', 'green' ]
instance2.sayName();//Jack
缺点:每次创建子类实例都执行了两次构造函数,又在新对象上创建了实例属性,
于是这两个属性就屏蔽了原型中的两个同名属性。
④寄生式继承:
与寄生构造函数和工厂模式类似,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象。
function createAnother(original){
var clone = Object.create(original); //通过调用函数创建一个新对象
clone.sayHi = function(){
//以某种方式来增强这个对象
alert("Hi");
};
return clone; //返回这个对象
}
var person = {
name: "Bob",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi();
缺点:
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而效率低下。
同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。

⑤寄生式组合继承:
为了解决构造函数被执行两次的问题, 我们将指向父类实例改为指向父类原型
, 减去一次构造函数的执行。即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法
function inheritPrototype(subType, superType){
var protoType = Object.create(superType.prototype); //创建对象
protoType.constructor = subType; //增强对象
subType.prototype = protoType; //指定对象
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance = new SubType("Bob", 18);
instance.sayName();
instance.sayAge();
通过inheritPrototype函数来减少一次对父类构造函数的调用,同时进行了父类方法的继承,避免在父类的原型上面创建不必要的、多余的属性,与其同时,原型链还能保持不变
。