1.原型链继承
核心思想:子类的原型等于父类的一个实例。
function Parent() {
this.name = 'afeng';
}
Parent.prototype.getName = function() {
console.log(this.name);
}
function Child() {
}
// 这里是最关键的一步
Child.prototype = new Parent();
var child1 = new Child();
console.log(child1.getName()); // afeng
缺点:
1.引用类型的属性被所有的实例共享。这是因为子类的原型现在是父类的一个实例,如果父类的实例上存在引用类型的值,就会被共享。
2.在创建Child的实例时,不能向Parent传参。 解决办法:使用借用构造函数
2.借用构造函数(经典继承)
基本思想:在子类构造函数中,调用执行父类构造函数,窃取父类构造函数中的属性。
核心实现代码:Parent.call(this);
function Parent(name) {
this.name = name;
this.likes = ['coding', 'sport']
}
function Child(name) {
// 核心实现,通过构造函数窃取,现在每个子类实例上都有likes这个属性。
Parent.call(this, name);
}
var child1 = new Child('afeng');
child1.likes.push('game');
console.log(child1.likes);
var child2 = new Child('fang');
console.log(child2.likes);
优点:
1.避免了引用类型的属性被所有实例共享
2.可以在Child中向Parent传参
缺点:
1.方法都定义在构造函数中,每次创建实例都会创建一遍方法。
2.只能继承父类的实例属性和方法,不能继承原型属性和方法。 因为并没有改变子类的原型
3.组合继承(伪经典继承)
基本思想:原型链继承+借用构造函数
用原型链实现对原型属性和方法的继承,用借用构造函数实现实例属性的继承。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function() {
console.log(this.name);
}
function Child(name, age) {
// 借用构造函数
Parent.call(this, name);
this.age = age;
}
// 原型链继承
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child('Afeng', '18');
child1.colors.push('black');
console.log(child1.colors);
var child2 = new Child('yaFeng', '18');
console.log(child2.colors);
缺点:
1.两次调用父类构造函数,
2.并且子类实例和子类原型中都有一个colors属性
4.原型式继承
基本思想:
function createObj(obj) {
function F() {};
F.prototype = obj;
return new F();
}
其实就是ES5 Object.create()的模拟实现,将传入对象作为被创建对象的原型对象。
var person = {
name: 'afeng',
like: ['coding', 'game']
}
var person1 = createObj(person); // [ 'coding', 'game', 'sport' ]
var person2 = createObj(person); // [ 'coding', 'game', 'sport' ]
person1.like.push('sport');
console.log(person1.like);
console.log(person2.like);
// 只是在person1上添加了name属性。
person1.name = 'yafeng';
console.log(person1.name); // yafeng
console.log(person2.name); // afeng
缺点:引用类型值会共享。
5.寄生式继承
基本思想:基本思想:创建一个仅用于封装继承过程的函数,该函数在内部以某种形式增强对象,最后返回对象。
function createObj(obj) {
var clone = Object.create(obj);
clone.getName = function() {
console.log(this.name);
}
return clone;
}
var person = {
name: 'afeng',
like: ['coding', 'game']
};
// 传一个对象,作为返回对象的原型。
var afeng = createObj(person);
afeng.getName()
afeng.like.push('sport');
console.log(afeng.like); // [ 'coding', 'game', 'sport' ]
var mengfan = createObj(person);
console.log(fang.like); // [ 'coding', 'game', 'sport' ]
console.log(afeng.getName === fang.getName); // false. 方法被重复创建了
缺点:引用类型属性会被共享。
6.寄生组合式继承(伪类继承)
基本思想:主要是为了解决组合继承中两次调用父类构造函数。我们并不是非要父类的实例等于子类的原型,才能实现原型链的继承。 我们需要的是子类的原型和父类原型建立起关系。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function() {
console.log(this.name);
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
configurable: true,
enumerable: false,
value: Child,
writable: true
}
});
// 原型一定要先改变,然后再添加。
Child.prototype.getAge = function() {
console.log(this.age);
}
var child1 = new Child('afeng', 18);
var child2 = new Child('yafeng', 18);
child1.colors.push('pink');
console.log(child1.colors);
console.log(child2.colors);
这种方式的高效率体现在只调用一次Parent构造函数,并且因此避免了在Child.prototype上面创建不必要的,多余的属性。同时,原型链还能保持不变。
7.混入式继承
定义:一个对象在不改变原型对象的情况下得到另一个对象的属性被称为混入。
基本思想:
function minix(receiver, supplier) {
// 查看浏览器是否支持ES5
if(Object.getOwnPropertyDescriptor) {
// Object.keys获取到的是自有的可枚举属性
Object.keys(supplier).forEach(function(property) {
// 得到属性描述对象
var desc = Object.getOwnPropertyDescriptor(supplier, property);
// 设置给同名的接收者
Object.defineProperty(receiver, property, desc);
});
} else {
for(var property in supplier) {
if(supplier.hasOwnProperty(property)) {
receiver[property] = supplier[property];
}
}
}
return receiver;
}
这其实也是一种增强对象的手段。