首先我们要实现一个父类
function Animal(name) {
this.name = name || "Mike";//实例属性
this.love = function () {
console.log(this.name + "爱吃骨头")//实例方法
}
this.other = [];//实例引用属性
}
Animal.prototype.sleep = function (place) {
console.log(this.name + "在" + place + "睡觉")
}//原型方法
1.原型继承
核心:父类的实例充当子类的原型
function Dog() {
}
Dog.prototype = new Animal();
Dog.prototype.name = "Jack";
var dog1 = new Dog("twoHa");
var dog2 = new Dog("keJi");
console.log(dog1.name);//Jack
console.log(dog2.name);//Jack
dog1.love();//Jack爱吃骨头
dog2.love();//Jack爱吃骨头
dog1.sleep("床上");//Jack在床上睡觉
dog2.sleep("客厅");//Jack在客厅睡觉
优点:
-
实例是子类的实例,也是父类的实例
-
父类新增原型方法/原型属性,子类都能访问到
-
简单,易于实现
缺点:
-
要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
-
无法实现多继承
-
来自原型对象的引用属性是所有实例共享的,改变实例会污染父类
-
创建子类实例时,无法向父类构造函数传参
2.构造继承
核心:相当于copy了一份父类的实例属性和方法,注意父类的原型属性和方法是无法copy过来的
function Cat(name){
Animal.call(this);
this.name = name||"yingDuan"
}
var cat = new Cat();
console.log(cat.name);//yingDuan
console.log(cat.other);//[]
cat.sleep();//undefined
cat.love();//yingDuan爱吃骨头
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
var cat1 = new Cat("jiafei");
var cat2 = new Cat("tianYuan");
cat1.other.push('m');
console.log(cat1.name);//jiafei
console.log(cat2.name);//tianYuan
console.log(cat1.other);//["m"]
console.log(cat2.other);//[]
优点:
-
解决了1中,子类实例共享父类引用属性的问题
-
创建子类实例时,可以向父类传递参数
-
可以实现多继承(call多个父类对象)
缺点:
-
实例并不是父类的实例,只是子类的实例
-
只能继承父类的实例属性和方法,不能继承原型属性/方法
-
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
3.组合继承(最常用)
核心:相当于1,2的组合,通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现原型函数的复用
function Bear(name){
Animal.call(this);
this.name = name || 'Tom';
}
Bear.prototype = new Animal();
// Test Code
var bear1 = new Bear("bearBig");
var bear2 = new Bear("bearTwo");
bear1.other.push('a');
console.log(bear1.name);//bearBig
console.log(bear2.name);//bearTwo
console.log(bear1.sleep('森林里'));//bearBig在森林里睡觉
console.log(bear1.other);//['a']
console.log(bear2.other);//[]
console.log(bear1 instanceof Animal); // true
console.log(bear1 instanceof Bear); // true
优点:
- 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法,实现了函数的复用
- 可传参
- 实例既是父类的实例又是子类的实例
- 可以实现多继承
缺点:
- 调用了两次父类构造函数,生成了两份实例,多耗了一些内存(子类实例将子类原型上的那份屏蔽了)
4.寄生组合继承(最佳继承方式)
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承浪费内存的缺点
function medium(obj){//用来砍掉原型对象上多余的那份父类实例属性
var fn = function(){};
fn.prototype = obj.prototype;
return new fn();
}
function Pig(name){
Animal.call(this);
this.name = name||"peiQi"
}
var proto = medium(Animal);
proto.constructor = Pig; // 核心
Pig.prototype = proto;
var pig = new Pig("George");
console.log(pig.name);//George
pig.sleep();//George在undefined睡觉
console.log(pig instanceof Animal); // true
console.log(pig instanceof Pig); //true
优点:
- 完美
缺点:
- 使用麻烦,所以相较来说组合继承更常用,性价比高
5.实例继承
核心:在子类中new一个父类实例,可以为父类实例添加新的属性,然后将这个父类实例作为子类返回
function Panda(name){
var example = new Animal();
example.name =name ||"huanhuan";
return example;
}
var panda1 = new Panda("yingying");
var panda2 = Panda("nini");
panda1.other.push('x');
console.log(panda1.name);//yingying
console.log(panda2.name);//"nini
console.log(panda1.other);//['x']
console.log(panda2.other);//[]
panda1.sleep("鸟巢");//yingying在鸟巢睡觉
console.log(panda1 instanceof Animal); // true
console.log(panda1 instanceof Panda); // false
优点:
- 不限制调用方式,可以new 子类() 也可以直接子类()
缺点:
- 实例是父类的实例,不是子类的实例
- 不能实现多继承