1、原型链继承
基本思想:让一个引用类型继承另一个引用类型的属性和方法;
构造函数、原型和实例的关系:
每个构造函数都有一个prototype属性,该属性指向该函数的原型对象;原型对象包含一个不可枚举的属性constructor,该属性指向它们的构造函数;当调用构造函数创建一个新实例之后,该实例内部包含一个内部指针proto ,指向该构造函数的原型对象。
//创建一个构造函数
function SubType(){ }
//构造函数存在prototype属性指向原型对象
SubType.prototype = SubType Prototype;
//实例化对象
var instance = new SubType();
//实例对象存在内部指针_proto_指向该构造函数的原型对象
instance._proto_ = SubType.prototype;
//原型对象存在constructor属性指向构造函数
SubType.prototype.constructor = SubType();
原型链继承的例子
function Father(){
this.colors = ['red','yellow','blue'];
this.age = 18;
this.name = "lee";
}
Father.prototype.money = 1000000;
Father.prototype.house = 4;
function Son(){
}
//父类型实例化后代对象赋值给子类型的原型属性,重写了子类型的原型对象,
//new Father()会将Father构造里的信息和原型里的信息都交给Son
Son.prototype = new Father();
//实例化对象
var son = new Son();
var son2 = new Son();
//实例son改变引用类型colors
son.colors.push('black');
console.log(son.age);//18
console.log(son.money);//1000000
//引用类型值的原型属性被所有实例共享,如下,改变了son的colors,son1的colors也被改变
console.log(son.colors);//["red", "yellow", "blue", "black"]
console.log(son2.colors);//["red", "yellow", "blue", "black"]
问题:
(1)、引用类型值的原型属性被共享
(2)、过多的继承了没有用的属性
(3)、不能向父类型传递参数
2、借用构造函数
function Father(age,name){
this.colors = ['red','yellow','blue'];
this.age = age;
this.name = name;
this.say=function(){
console.log(this.name);
}
}
Father.prototype.money = 100000;
function Son(age,name){
Father.call(this,age,name);//通过对象冒充
}
var son = new Son(18,"lee");
var son2 = new Son(29,"ooo");
son.colors.push('black');
console.log(son.age); //18
console.log(son.money); //undefined,继承不到原型里的信息
console.log(son2.name); //ooo
console.log(son.colors); //["red", "yellow", "blue", "black"]
console.log(son2.colors); //["red", "yellow", "blue"]
son.say(); //"lee"
问题:
(1)、只能继承到构造里的信息,继承不到原型里的信息;
(2)、方法放在构造函数中定义,每次实例都会分配一个内存地址,没有实现共享,无法实现复用;
3、组合继承
基本思想:将原型链和构造函数的技术组合到一起,发挥二者之长;使用原型链实现对原型属性和方法的继承,使用借用构造函数实现对实例属性的继承;
构造函数内放不需要被共享的属性,原型中放要被共享的属性和方法;
//放在构造函数里的信息不被共享
function Father(){
this.class = ['语文','数学','英语'];
}
Father.prototype.grade = 2016;//原型中的属性共享
Father.prototype.sayName = function(){//方法放在原型中,保证多次实例化只有一块内存地址,实现方法的复用
console.log(this.name);
}
function Son(name,age){//借用构造函数
Father.call(this);
this.age = age;
this.name = name;
}
Son.prototype = new Father();//原型链
var son = new Son("lee",18);
var son2 = new Son("ooo",19);
son.class.push('地理');
console.log("年级:"+son.grade+"----姓名:"+son.name+"----年龄:"+son.age+"----课程:"+son.class);
//年级:2016----姓名:lee----年龄:18----课程:语文,数学,英语,地理
console.log("年级:"+son2.grade+"----姓名:"+son2.name+"----年龄:"+son.age+"----课程:"+son2.class);
//年级:2016----姓名:ooo----年龄:18----课程:语文,数学,英语
问题:调用了两次父类型构造函数;
3-1、组合继承的优化1
上面的组合继承提到存在的问题是调用了两次父类型构造函数,所以我们进行优化,把原型链继承那一步改成Son.prototype = Father.prototype;
这样这里就没有调用构造函数,但也完成了继承。
//放在构造函数里的信息不被共享
function Father(age){
this.colors = ['red','yellow','blue'];
this.age = 18;
this.name = "lee";
}
Father.prototype.money = 1000000;
Father.prototype.house = 4;
function Son(){
Father.call(this);
}
Son.prototype = Father.prototype;//更改的地方
//实例化对象
var son = new Son();
var son2 = new Son();
var father = new Father();
Son.prototype.money = 1000;//子类重写该原型属性
son.colors.push('#ccc');
console.log(son.age);//18
console.log(son.colors,son2.colors);//["red", "yellow", "blue", "#ccc"] ,["red", "yellow", "blue"]
console.log(son.money);//1000
console.log(son2.money);//1000,一起都被更改
console.log(father.money);//1000 ,父类的原型对象也被更改
问题:从一开始就存在的问题,Son.prototype = Father.prototype;
或者Son.prototype = new Father();
会重写Son的原型对象,所以Son就没有constructor
这个属性,所以会向原型链上寻找,最终Son.prototype.constructor
指向Father
这个构造函数;
console.log(Son.prototype.constructor === Father ); //true
console.log(Father.prototype.constructor === Father ); //true
解决办法是改变constructor
指向;下面的组合继承优化2。
3-2、组合继承的优化2
在改变constructor指向之前,还要思考一个问题,由于Son.prototype = Father.prototype;
,即他俩指向同一个地址,所以,在你改变Son.prototype
的constructor
指向时,也会影响到Father.prototype
的constructor
指向;同时在Son在重写了父类原型的值时,父类也受到影响;
Son.prototype = Father.prototype;
Son.prototype.constructor = Son; //改变`Son.prototype`的`constructor` 指向
Father.prototype.money = 1000000;//父类定义一个原型属性
Son.prototype.money = 1000;//子类重写该原型属性
console.log(father.money);//1000 ,父类的该原型属性也被更改
console.log(Son.prototype.constructor === Son );//true
console.log(Father.prototype.constructor === Father ); //false
console.log(Father.prototype.constructor === Son ); //true
由运行结果可以看到,Father.prototype
的constructor
指向也被改变为Son
。
所以解决这个问题时,在实现继承那一步就不能再写成Son.prototype = Father.prototype;
,要改成Son.prototype = Object.create(Father.prototype);
,这里的Object.create(Father.prototype)
是创建了一个以Father.prototype
为原型的对象。
Son.prototype = Object.create(Father.prototype);
可以理解为:
var obj = Object.create(Father.prototype); //创建一个对象,这个对象的原型是Father.prototype
Son.prototype = obj;//这个对象同时又是Son的原型
//所以这里形成了一个原型链,但又同时把Son.prototype和Father.prototype隔离开来
下面再来改变Son.prototype
的constructor
指向
Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor = Son;
console.log(Son.prototype.constructor === Son ); //true
console.log(Father.prototype.constructor === Father ); //true
这里就实现了既改变了Son.prototype
的constructor
指向,又不影响Father.prototype
的constructor
指向的功能。
完整的实现代码
function Father(age){
this.colors = ['red','yellow','blue'];
this.age = 18;
this.name = "lee";
}
Father.prototype.money = 1000000;
Father.prototype.house = 4;
function Son(){
Father.call(this);
}
//Father.prototype只把Father原型里的信息交给Son
//Son.prototype会重写原型链
Son.prototype = Object.create(Father.prototype);
//Son.prototype = Father.prototype;
Son.prototype.constructor = Son;
console.log(Son.prototype.constructor === Son );//true,已改变
console.log(Father.prototype.constructor === Father );//true,不受影响没被改变
//实例化对象
var son = new Son();
var son2 = new Son();
var father = new Father();
Son.prototype.money = 1000;//子类重写该原型属性
son.colors.push('#ccc');
console.log(son.age);//18
console.log(son.colors,son2.colors);//(4) ["red", "yellow", "blue", "#ccc"] (3) ["red", "yellow", "blue"]
console.log(son.money);//1000
console.log(son2.money);//1000,一起都被更改
console.log(father.money);//100000 父类的不受影响
有些情况是只需要继承部分,所以存在下面两种方法;
下面的两种方法都是只继承原型里的属性和方法,不继承构造函数里的。
4、共享原型继承
function Father(age){
this.colors = ['red','yellow','blue'];
this.age = 18;
this.name = "lee";
}
Father.prototype.money = 1000000;
Father.prototype.house = 4;
function Son(){
}
//Father.prototype只把Father原型里的信息交给Son
Son.prototype = Father.prototype;
//实例化对象
var son = new Son();
var son2 = new Son();
var father = new Father();
Son.prototype.money = 1000;//子类重写该原型属性
console.log(son.age);//undefined
console.log(son.colors);//undefined
console.log(son.money);//1000
console.log(son2.money);//1000,一起都被更改
console.log(father.money);//1000 父类也受到影响
缺点:
(1)、只继承原型里的信息,不继承构造函数里的信息;
(2)、若子类型更改,Son.prototype.money = 1000,
其父类型和其他子类型也一起被更改,因为它们共享一个内存地址;
5、圣杯模式
可以继承已有原型对象的成员(主要是公共方法和属性),同时可以根据自己的需求修改原型对象,而这个修改对已有实例不产生影响;
//圣杯模式
function Father(name,age){
this.name = name;
this.age = aeg;
}
Father.prototype.money = 100000;
function Son(name,age){
this.name = name;
this.age = age;
}
Son.prototype.money = 4000;//子类型修改原型属性
var father = new Father("Bob",45);
var son = new Son("John",18);//实例化子类型对象,具有参数
var son1 = new Son();//实例化子类型对象,没有参数
//定义一个函数,参数是两个构造函数
function inherit(Target,Origin){
function Lk(){}//创建一个临时构造函数,多加一个中间层
Lk.prototype = Origin.prototype;//共享原型,临时构造函数的原型为Origin的原型,只继承Origin原型里的信息;
Target.prototype = new Lk();//使Target的原型为Lk的实例化对象,继承Lk构造里和原型里的信息,Target修改原型修改的是Lk对象,调用的是Origin
Target.prototype.constructor = Target;//由于Target原型重写,导致失去默认的constructor属性,这是使Target.prototype的constructor重新指向Target构造函数;
Target.prototype.ancetor = Origin.prototype;//target最终继承自Origin
}
//或者写成下面这种样式,同上面一样的效果
/*
var inherit = (function(){
function Lk(){}
return function(Target,Origin){
Lk.prototype = Origin.prototype;
Target.prototype = new Lk();
Target.prototype.constructor = Target;
Target.prototype.ancetor = Origin;
};
})();
*/
inherit(Son,Father);
console.log("这是儿子,姓名:"+son.name+" 年龄:"+son.age+" 存款:"+son.money);
//这是儿子,姓名:John 年龄:18 存款:4000;子类型这里修改成功
console.log("这是父亲,姓名:"+father.name+" 年龄:"+father.age+" 存款:"+father.money);
//这是父亲,姓名:Bob 年龄:45 存款:100000;可以看到,子类型修改的原型属性对父类型没有产生影响
console.log("这是小儿子,姓名:"+son1.name+" 年龄:"+son1.age);
//这是小儿子,姓名:undefined 年龄:undefined;父类型构造里的信息没有继承
上面的inherit函数可以利用Object.create()
方法改写成
function inherit(Target,Origin){
Target.prototype = Object.create(Origin.prototype);
Target.prototype.constructor = Target;//由于Target原型重写,导致失去默认的constructor属性,这是使Target.prototype的constructor重新指向Target构造函数;
Target.prototype.ancetor = Origin.prototype;//target最终继承自Origin
}
问题:这种方法只继承原型里的信息,不继承构造里的信息;例如上例中son1没有赋值,就无法继承父类型构造里的信息;
6,寄生组合式继承
基本思想:借用构造函数和圣杯模式的结合(我自己理解的。。。);通过借用构造函数来继承属性,通过原型链的混成模式来继承方法;不必为了指定子类型的原型而调用父类型的构造函数,我们所需要的无非就是父类型原型的一个副本而已;
这个就是上面3-2提到的组合继承优化方法2;
function Teacher(grade){
this.grade = 2016;
}
Teacher.prototype.class = 2;
function Student(grade,name,age){
Teacher.call(this,grade);//借用构造函数
this.name = name;
this.age = age;
}
Student.prototype.class = 8;
var teacher = new Teacher();
var student = new Student(2016,"Bob",17);
var student1 = new Student();
//三种inherit方法
/*
function inherit(Target,Origin){
function Lk(){}
Lk.prototype = Origin.prototype;
Target.prototype = new Lk();
Target.prototype.constructor = Target;
Target.prototype.ancetor = Origin;
}*/
/*
var inherit = (function(){
function Lk(){}
return function(Target,Origin){
Lk.prototype = Origin.prototype;
Target.prototype = new Lk();
Target.prototype.constructor = Target;
Target.prototype.ancetor = Origin;
};
})();*/
function inherit(Target,Origin){
Target.prototype = Object.create(Origin.prototype);
Target.prototype.constructor = Target;//由于Target原型重写,导致失去默认的constructor属性,这是使Target.prototype的constructor重新指向Target构造函数;
Target.prototype.ancetor = Origin.prototype;//target最终继承自Origin
}
inherit(Student,Teacher);//这一步替换了组合继承中的Student.prototype = new Teacher();少调用了一次构造函数;
console.log("这是老师,年级:"+teacher.grade+" 老师带几门课:"+teacher.class+" 分别是:"+teacher.cource);
//这是老师,年级:2016 老师带几门课:2 分别是:语文,数学
console.log("这是学生1,年级:"+student.grade+" 姓名:"+student.name+" 年龄:"+student.age+" 学生有几门课:"+student.class+" 必修课是:"+teacher.cource);
//这是学生1,年级:2016 姓名:Bob 年龄:17 学生有几门课:8 必修课是:语文,数学
console.log("这是学生2,年级:"+student1.grade+" 学生有几门课:"+student1.class+" 必修课是:"+teacher.cource);
//这是学生2,年级:2016 学生有几门课:8 必修课是:语文,数学
这种方法可以继承父类型构造函数里的信息,同时可以继承原型里的信息,并且在不影响父类型原型信息的情况下修改子类型原型信息。(恩,我觉得是这样,以后有新的想法再改)