一,原型链继承
核心: 将父类的实例作为子类的原型
将构造函数的原型设置为另一个构造函数的实例对象,这样就可以继承另一个原型对象的所有属性和方法,可以继续往上,最终形成原型链
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
//定义子类
function Cat(){
}
//把子类的原型对象变成父类的实例对象,因为父类的实例对象必然继承了父类及其原型对象上的属性和方法
Cat.prototype = new Animal();
//给子类的原型上定义属性,并不会影响父类
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true Animal的原型对象在cat的原型链上
console.log(cat instanceof Cat); //true Cat的原型对象在cat的原型链上
具体的图示:
也就是舍弃自身原本的原型对象,找了父类的一个实例做原型对象,从而继承那个父类的属性和方法以及原型链。
通俗点说,就是为了继承那点财产(父类的属性和方法),认贼子作父!!
值得注意的是,构造函数是Function的实例,但是改写的toString()方法是定义在Function.prototype上的,不在构造函数的实例对象的原型链上。也就是说构造函数的实例只能顺着原型链找到一个toString方法,就是定义在Object.prototype上的toString,它是用来精准判断数据类型的。
console.log(Cat.__proto__.hasOwnProperty('toString')) //true 也就是toString定义在Functiong.prototype上
console.log(Animal.prototype.hasOwnProperty('toString')) //false 也就是Cat.prototype上没有toString
console.log(cat.toString()) //[object Object] //所以实例对象,只能访问到最末端的判断数据的toString方法
原型链继承存在的问题:
1,父类的实例会变成子类的原型对象,其中的属性会被所有子类的实例对象所共享,若是一个改变了,全部子类实例对象都会被影响
2,没有办法在不影响所有子类实例对象的情况下,给超类型的构造函数传递参数
二,构造函数继承
用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
//继承SuperType
SuperType.call(this);
}
var instance1 = new SubType();
//new的时候,创建新的实例对象,并将this指向它,于是 SuperType.call(this)的this是instance1
//然后call,再改变this指向,从而获取父类的color属性,并且它是直接重新创建一份(深拷贝)
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
优点:
解决了1中,子类实例共享父类引用属性的问题
缺点:
实例并不是父类的实例,只是子类的实例
只能继承父类的实例属性和方法,不能继承原型属性/方法
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
三,组合继承
将原型链和借用构造函数的技术组合到一块。使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。
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;
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JS中最常用的继承模式。
四,原型式继承
不自定义类型的情况下,临时创建一个构造函数,借助已有的对象作为临时构造函数的原型,然后在此基础实例化对象,并返回。
function object(o){
function F(){
}
F.prototype = o;
return new F();
}
本质上是object()对传入其中的对象执行了一次浅复制
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
原型的引用类型属性会在各实例之间共享。
五,寄生组合继承
组合继承是JS中最常用的继承模式,但其实它也有不足,组合继承无论什么情况下都会调用两次超类型的构造函数,并且创建的每个实例中都要屏蔽超类型对象的所有实例属性。
寄生组合式继承就解决了上述问题,被认为是最理想的继承范式。
function object(o) {
function F(){
}
F.prototype = o;
return new F();
}
function inheritPrototype(superType, subType) {
var prototype = object(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(SuperType, SubType); // 这一句,替代了组合继承中的SubType.prototype = new SuperType()
SubType.prototype.sayAge = function() {
alert(this.age);
};
1.在inheritPrototype函数中用到了原型式继承中的object()方法,将超类型的原型指定为一个临时的空构造函数的原型,并返回构造函数的实例。
2.此时由于构造函数内部为空(不像SuperType里面有实例属性),所以返回的实例也不会自带实例属性,这很重要!因为后面用它作为SubType的原型时,就不会产生无用的原型属性了,借调构造函数也就不用进行所谓的“重写”了。
3.然后为这个对象重新指定constructor为SubType,并将其赋值给SubType的原型。这样,就达到了将超类型构造函数的实例作为子类型原型的目的,同时没有一些从SuperType继承过来的无用原型属性。