1. 实例继承 (子对象 = new 父对象())
function Animal(name) {
this.name = name;
this.say = function () {
console.log("my name is ",this.name);
}
}
Animal.prototype.eat = function (food) {
console.log(this.name + '吃:' + food);
};
function Cat(name, age) {
var o = new Animal(name); //先创建子类型实例
o.age = age;
return o;
}
var cat = new Cat("jason",18);
cat.say()
特点:
不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果
缺点:
- 实例是父类的实例,不是子类的实例
- 不支持多继承
2. 拷贝继承( for in 父实例属性)
遍历所有属性,统一赋值给子对象
function Animal(name) {
this.name = name;
this.say = function () {
console.log("my name is ",this.name);
}
}
// 原型对象方法
Animal.prototype.eat = function (food) {
console.log(this.name + '吃:' + food);
};
function Cat(name, age) {
var animal = new Animal(name);
for (var p in animal) {
Cat.prototype[p] = animal[p];
}
this.age = age
}
var cat = new Cat("jason");
cat.say()
cat.eat("香蕉")
特点:
支持多继承
缺点:
- 效率较低,拷贝导致内存占用高
- 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
3. 原型链式继承 (子对象.prototype = new 父对象())
将父类的实例作为子类的原型
function Animal(name) {
this.name = name;
this.say = function () {
console.log("我叫"+this.name);
}
}
Animal.prototype.eat = function (food) {
console.log(this.name + '正在吃:' + food);
};
//写法1
function Cat() {}
Cat.prototype = new Animal();
Cat.prototype.construtor = Cat; //这里要修正构造函数的指向,不然就是Animal
var cat = new Cat();
cat.name="jason"
cat.say()
//写法2 Object.create 创建原型在Animal父对象实例的子空对象
var cat = Object.create(new Animal())
cat.name="jason"
cat.say()
Object.create() 底层实现
Object.create = function (obj) {
function F() {}
F.prototype = obj;
return new F();
};
特点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
缺点:
- 可以在Cat构造函数中,为Cat实例增加实例属性。如果要新增原型属性和方法,则必须放在new Animal()这样的语句之后执行。
- 无法实现多继承
- 来自原型对象的引用属性是所有实例共享的
- 创建子类实例时,无法向父类构造函数传参
4. 寄生式继承 (子对象.prototype = new 父对象() , 子对象 新增方法)
是原型是继承的扩展,在继承的对象上,新增原型属性与方法
function Animal(name) {
this.name = name;
this.say = function () {
console.log("我叫"+this.name);
}
}
Animal.prototype.eat = function (food) {
console.log(this.name + '正在吃:' + food);
};
var cat = Object.create(new Animal())
cat.name="jason"
cat.jump = function (food) {//寄生的地方,新增自己的方法
console.log(this.name + '跳起来' );
};
cat.say()
cat.jump()
5. 构造函数继承 (父构造函数.call(this))
通过使用call改变父构造函数的执行上下文的this为当前的对象实例,达到继承的效果
function Animal(name) {
this.name = name;
this.say = function () {
console.log("my name is ",this.name);
}
}
// 原型对象方法
Animal.prototype.eat = function (food) {
console.log(this.name + '吃:' + food);
};
function Cat(name, age) {
Animal.call(this, name);
//这里等价于 执行了
//cat.name = name;
//cat.say = function () {
// console.log("my name is ",cat.name);
// }
this.age = age;
}
var cat = new Cat("jason");
cat.say() //这里正常
cat.eat() //这里会报错 缺点 原型对象不能调用
特点:
- 解决了原型链继承中,子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call 多个父类对象)
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
6. 组合继承 (原型链+ 父构造函数call)
子对象.prototype = new 父对象 + 父构造函数.call(this))
function Animal(name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function () {
return this.name + ' 正在睡觉!';
}
}
// 原型方法
Animal.prototype.eat = function (food) {
return this.name + ' 正在吃: ' + food;
};
function Cat(name){
Animal.call(this);
this.name = name;
}
Cat.prototype = new Animal();
Cat.prototype.construtor = Cat; //这里要修正构造函数的指向,不然就是Animal
// Test Code
var cat = new Cat("jason");
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
特点:
- 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
缺点: 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
7. 寄生+组合继承(中间层Middle实例做原型链+ 父构造函数call)
- 通过新增中间层Middle实例
- 让自对象原型指向Middle实例, MIddle原型再指向父对象原型。
- 这个过程也叫原型式继承
解决了原型链继承时候,要先实例父类的问题,导致父对象属性被多次实例化。
function Animal(name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function () {
return this.name + ' 正在睡觉!';
}
}
Animal.prototype.eat = function (food) {
return this.name + ' 正在吃: ' + food;
};
function Cat(name) {
Animal.call(this);
this.name = name || 'Tom';
}
// 创建一个没有实例方法的类
var Middle = function () {};
Middle.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Middle();
Cat.prototype.construtor = Cat; //这里要修正构造函数的指向,不然就是 Middle
// 等价于下面这种情况
//inheritPrototype1(Cat,Animal)
//inheritPrototype2(Cat,Animal)
//inheritPrototype3(Cat,Animal)
//inheritPrototype4(Cat,Animal)
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
优化和封装middle指向方法
//实现1 把上面内容包装一下
function inheritPrototype1(sub, sup) {
var Middle = function() {}
Middle.prototype = sup.prototype;
sub.prototype = new Middle();
sub.prototype.construtor = sub; //这里要修正构造函数的指向
}
//实现2 用Object.create()简化
function inheritPrototype2(subType, superType) {
var MiddleObj = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本
subType.prototype = MiddleObj; // 指定对象,将新创建的对象赋值给子类的原型
subType.prototype.construtor = subType; //这里要修正构造函数的指向
}
//实现3 用Object.create()简化2
function inheritPrototype3(subType, superType) {
subType.prototype = Object.create(superType, { constructor: subType })
}
//实现4 直接修改原型链指向
function inheritPrototype4(subType, superType) {
subType.prototype.__proto__ = superType.prototype;
}
//实现5 使用setPrototypeOf直接修改原型链指向
function inheritPrototype5(subType, superType) {
Object.setPrototypeOf(subType.prototype, superType.prototype);
}
Object.create() 底层实现
Object.create = function (obj) {
function F() {}
F.prototype = obj;
return new F();
};
缺点:原型链上多加了一层,而且又使用的父函数call(this),逻辑太复杂
8. ES6 class extends继承
class Animal {
constructor(name) {
this.name = name || 'Animal';
this.sleep = function () {
return this.name + ' 正在睡觉!';
}
}
eat(food) {
return this.name + ' 正在吃: ' + food;
};
}
class Cat extends Animal {
constructor(name, age) {
super(name);
this.age = age; // 新增的子类属性
}
eat(food) {
const result = super.eat(food); // 通过 super 调用父类方法
return this.age + ' ' + result;
}
}
const cat = new Cat('miao', 3);
console.log(cat.name,cat.age)
console.log(cat.eat("香蕉"))