一 子类的原型继承 ----- 类式继承
也就是将子类型的原型指向父类型的实例 (子类型.prototype = new 父类型)。
//声明父类
function SuperClass(){
this.supperValue = true;
};
//为父类添加共有方法
SuperClass.prototype.getSupperValue = function(){
return this.supperValue;
};
//声明子类
function SubClass(){
this.subValue = false;
};
//继承父类
SubClass.prototype = new SuperClass();
//为子类添加共有方法
SubClass.prototype.getSubValue = function(){
return this.subValue;
};
var instance1 = new SubClass();
console.log(instance1.getSupperValue()); //true
console.log(instance1.getSubValue()) //false
类式继承原理:
新创建的对象不仅可以访问父类原型上的属性和方法,也可以从父类构造函数中赋值属性和方法,将这个对象赋 值给子类的原型,那么这个子类的原型同样可以访问父类原型上的属性和方法与从构造函数中复制的属性和方法
但是类式继承有两个缺点:
1.由于子类通过其原型prototype对父类实例化,继承了父类,所以说父类中的共有属性要是引用类型,就会在子类中被所有实例共享,因此一个子类的实例更改子类原型从父类构造函数中继承来的共有属性就会直接影响其他子类
function SuperClass(){
this.animals = ['dog' , 'cat' , 'pig'];
};
function SubClass(){};
SubClass.prototype = new SuperClass();
var instance2 = new SubClass();
var instance3 = new SubClass();
console.log(instance3.animals); //["dog", "cat", "pig"]
instance2.animals.push('tiger');
console.log(instance3.animals); // ["dog", "cat", "pig", "tiger"];
2.由于子类实现的继承是靠原型prototype对父类的实例化实现的,因此创建父类的时候,是无法向父类传递函数的,因而在实例化父类的时候也无法对父类构造函数内的属性进行初始化
二 创建即继承 ----- 构造函数继承
在子类型的构造函数内调用父类型的构造函数
//声明父类
function SuperClass(num){
this.animals = ['dog' , 'cat' , 'pig'];
this.num = num;
};
//父类声明原型方法
SuperClass.prototype.showAnimals = function(){
console.log(this.animals);
};
//声明子类
function SubClass(num){
SuperClass.call(this , num);
};
var instance1 = new SubClass(11);
var instance2 = new SubClass(21);
instance1.animals.push('bird');
console.log(instance1.animals); // ["dog", "cat", "pig", "bird"]
console.log(instance2.animals); // ["dog", "cat", "pig"]
console.log(instance1.num); //1
console.log(instance2.num); //2
instance1.showAnimals(); //报错
由于call方法可以更改函数的作用环境,因此在子类中,对SuperClass调用这个方法就是将子类中的共有属性在父类中执行一遍,由于父类中是给this绑定的,因此子类自然就继承了父类的共有属性。
由于这种类型的继承没有涉及原型prototype,所以父类的原型就不会被子类所继承
三 将优点为我所用 ---- 组合继承
原型链继承方法,构造函数继承属性
类式继承是通过子类的prototype对父类实例化实现的,构造函数式继承是在子类的构造函数作用环境中执行一次父类的构造函数实现的,所以就有了组合继承。
//声明父类
function SuperClass(name){
this.animals = ['dog' , 'cat' , 'pig'];
this.name = name;
};
//父类声明原型方法
SuperClass.prototype.getName = function(){
console.log(this.name);
};
function SubClass(name,age){
//构造函数式继承父类name属性
SuperClass.call(this , name);
//子类中新增共有属性
this.age = age;
};
//类式继承 子类原型继承父类
SubClass.prototype = new SuperClass();
//子类原型方法
SubClass.prototype.getAge = function(){
console.log(this.age);
};
在子类构造函数中执行父类构造函数,在子类原型上实例化父类就是组合模式,融合了类式继承和构造函数继承的优点,过滤其缺点
var instance1 = new SubClass('QQ' , 3);
instance1.animals.push('tiger');
console.log(instance1.animals); // ["dog", "cat", "pig", "tiger"]
instance1.getName(); //QQ
instance1.getAge(); //3
var instance2 = new SubClass('CC' , 1);
console.log(instance2.animals); //["dog", "cat", "pig"]
instance2.getName(); //CC
instance2.getAge(); //1
看起来很完美,是吧?
但是我们在使用构造函数继承时执行了一遍父类的构造函数,而实现子类原型的类式继承时又调用了一遍构造函数,因此父类构造函数调用了两遍,还不是最完美的方法
四.原型式继承
function object(o){
function F(){}
F.prototype = o;
return new F();
}
在object函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。
本质上来说,object对传入其中的对象执行了一次浅复制。
var person = {
name: 'Jiang',
friends: ['Shelby', 'Court']
}
var anotherPerson = object(person)
console.log(anotherPerson.friends) // ['Shelby', 'Court'
这种模式要去你必须有一个对象作为另一个对象的基础。
在这个例子中,person作为另一个对象的基础,把person传入object中,该函数就会返回一个新的对象。
这个新对象将person作为原型,所以它的原型中就包含一个基本类型和一个引用类型。
所以意味着如果还有另外一个对象关联了person,anotherPerson修改数组friends的时候,也会体现在这个对象中。
Object.create()方法
ES5通过Object.create()方法规范了原型式继承,可以接受两个参数,一个是用作新对象原型的对象和一个可选的为新对象定义额外属性的对象,行为相同,基本用法和上面的object一样,除了object不能接受第二个参数以外
var person = {
name: 'Jiang',
friends: ['Shelby', 'Court']
}
var anotherPerson = Object.create(person)
console.log(anotherPerson.friends) // ['Shelby', 'Court']
寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数。
function createMethod(o){
var clone = Object.create(o);
clone.fn = function(){
console.log('hi')
}
return clone;
}
var person = {
name: 'Jiang'
}
var anotherPeson = createMethod(person)
anotherPeson.fn()
基于person返回了一个新对象anotherPeson,新对象不仅拥有了person的属性和方法,还有自己的sayHi方法。
在主要考虑对象而不是自定义类型和构造函数的情况下,这是一个有用的模式。
寄生组合式继承
在前面说的组合模式(原型链+构造函数)中,继承的时候需要调用两次父类构造函数。
父类
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
第一次在子类构造函数中
function SubType(name, job) {
// 继承属性
SuperType.call(this, name)
this.job = job
}
第二次将子类的原型指向父类的实例
// 继承方法
SubType.prototype = new SuperType()
当使用var instance = new SubType()
的时候,会产生两组name和color属性,一组在SubType实例上,一组在SubType原型上,只不过实例上的屏蔽了原型上的。使用寄生式组合模式,可以规避这个问题。
这种模式通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
基本思路:不必为了指定子类型的原型而调用父类的构造函数,我们需要的无非就是父类原型的一个副本。
本质上就是使用寄生式继承来继承父类的原型,在将结果指定给子类型的原型。
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype
}
该函数实现了寄生组合继承的最简单形式。
这个函数接受两个参数,一个子类,一个父类。
第一步创建父类原型的副本,第二步将创建的副本添加constructor属性,第三部将子类的原型指向这个副本。
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType(name, job) {
// 继承属性
SuperType.call(this, name)
this.job = job
}
// 继承
inheritPrototype(SubType, SuperType)
var instance = new SubType('Jiang', 'student')
instance.sayName()
补充:直接使用Object.create来实现,其实就是将上面封装的函数拆开,这样演示可以更容易理解。
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType(name, job) {
// 继承属性
SuperType.call(this, name)
this.job = job
}
// 继承
SubType.prototype = Object.create(SuperType.prototype)
// 修复constructor
SubType.prototype.constructor = SubType
var instance = new SubType('Jiang', 'student')
instance.sayName()
ES6新增了一个方法,Object.setPrototypeOf,可以直接创建关联,而且不用手动添加constructor属性。
// 继承
Object.setPrototypeOf(SubType.prototype, SuperType.prototype)
console.log(SubType.prototype.constructor === SubType) // true