首页 > 代码库 > 面向对象与原型5---继承
面向对象与原型5---继承
1.用原型链实现继承 最普通 但是没有办法传参,没有办法共享方法
继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继
承:一个是接口实现,一个是继承。而 ECMAScript 只支持继承,不支持接口实现,而实现
继承的方式依靠原型链完成。
原型链是由原型加对象构造之间的关系结构形成的像一个链条一样
在 JavaScript 里,被继承的函数称为超类型(父类,基类也行,其他语言叫法),继承的
函数称为子类型(子类,派生类)。
//继承,通过原型链实现
function Box() { //Box 构造 被继承的函数叫做超类型(父类)
this.name = ‘Lee‘;
}
function Desk() { //Desk 构造 继承的函数称为子类型(子类,派生类)。
this.age = 100;
}
var desk = new Desk();
alert(desk.age); //100
alert(desk.name); //undefined
//通过原型链继承,超类型实例化后的对象实例,赋值给子类型的原型属性即可
//new Box()会将Box构造里的信息和原型里的信息都交给Desk
Desk.prototype = new Box(); //Desk 继承了 Box,通过原型,形成链条 超类型new出来的东西交给子类型的原型
var desk = new Desk();
alert(desk.age);
alert(desk.name); // Lee 得到被继承的属性
function Table() { //Table 构造
this.level = ‘AAAAA‘;
}
Table.prototype = new Desk(); //继续原型链继承
var table = new Table();
alert(table.name); //Lee 继承了 Box 和 Desk
Desk的原型得到的是Box的构造加原型里的信息(图中水平第一个和第二个框),Table的原型又得到了Desk的构造和原型里的信息,所以最终的 Table得到了上面所有的信息。
如果要实例化 table,那么 Desk 实例中有 age=100,原型中增加相同的属性 age=200,
最后结果是多少呢?
Desk.prototype.age = 200; //实例和原型中均包含 age
alert(table.age); //输出100 就近原则 实例里面有就返回,没有就去查找原型
从属关系
以上原型链继承还缺少一环,那就是 Obejct,所有的构造函数都继承自 Obejct。而
继承 Object 是自动完成的,并不需要程序员手动继承。
//子类型从属于自己或者他的超类型
alert(desk instanceof Desk); //true
alert(desk instanceof Box); //true
alert(desk instanceof Object); //true
继承也有之前问题,比如字面量重写原型会中断关系,使用引用类型的原型时,并且子类型还无法给超类型传递参数。
2.第二种继承方法——借用构造函数继承 即对象冒充继承 解决了传参的问题 但是没有解决共享的方法
为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或
者成为对象冒充(伪造对象、经典继承)的技术来解决这两种问题。
//使用对象冒充
function Box(name,age) {
this.name = name;
this.age = age;
}
Box.prototype.family=‘家庭‘;
function Desk(name,age) {
Box.call(this, name,age); //this代表了Desk本身的类 对象冒充,给超类型传参 因此 Desk就可以冒充Box了 对象冒充只能继承构造里的信息,继承不了原型里的信息
}
var desk = new Desk(‘Lee‘,100);
alert(desk.name); //输出 Lee 说明Desk冒充了Box
alert(desk.family); //undefined 原型里的信息继承不过来
//引用类型放到构造函数里就不会共享
function Box(name,age) {
this.name = name;
this.age = age;
this.family = [‘哥哥‘,‘姐姐‘,‘妹妹‘];
}
function Desk(name,age){
Box.call(this,name,age);
}
var desk = new Desk(‘Lee‘,100);
desk.family.push(‘弟弟‘);
alert(desk.family); //输出 哥哥 姐姐 妹妹 弟弟
var desk2 = new Desk(‘Lee‘,100);
alert(desk2.family); //输出 哥哥 姐姐 妹妹 引用类型放到构造函数里就不会共享
(参看面向对象与原型4---原型)
function Box(name,age) {
this.name = name;
this.age = age;
this.family = [‘哥哥‘,‘姐姐‘,‘妹妹‘];
this.run = function(){
}
}
function Desk(name,age){
Box.call(this,name,age);
}
var desk = new Desk(‘Lee‘,100);
var desk2 = new Desk(‘Lee‘,100);
//构造函数里的方法,放在构造里,每次实例化都会分配一个内存地址,浪费,所以最好放在原型里,保证多次实例化只有一个地址。
function Box(name,age) {
this.name = name;
this.age = age;
this.family = [‘哥哥‘,‘姐姐‘,‘妹妹‘];
}
Box.prototype.run = function(){
return this.name+this.age;
}
function Desk(name,age){
Box.call(this,name,age);
}
var desk = new Desk(‘Lee‘,100);
var desk2 = new Desk(‘Lee‘,100);
以上两次实例化保证了一个地址
但是
alert(desk.run()); //not a function 因为通过对象冒充继承不到原型里的东西
3.组合模式实现继承(结合前两种)----最常用
对象冒充 结合 原型链 解决传参和方法共享的问题
借用构造函数虽然解决了刚才两种问题,第一个:引用保持独立。第二个:可以传参。但没有原型,复用则无从谈起。所以,我们需
要原型链+借用构造函数的模式,这种模式成为组合继承。
function Box(name,age) {
this.name = name;
this.age = age;
this.family = [‘哥哥‘,‘姐姐‘,‘妹妹‘];
}
Box.prototype.run = function(){
return this.name+this.age;
}
function Desk(name,age){
Box.call(this,name,age); //对象冒充只继承构造里的
}
Desk.prototype = new Box(); //原型链继承 只继承原型里的
4.原型式继承
还有一种继承模式叫做:原型式继承;这种继承借助原型并基于已有的对象创建新对象,
同时还不必因此创建自定义类型。
//临时中转函数
function obj(o){ //o表示将要传递进入的一个对象实例
function F(){} //F构造是一个临时新建的对象,用来存储传递进来的对象
F.prototype=o; //将o对象实例赋值给F构造的原型
return new F(); // 最后返回这个得到传递过来对象的对象实例
}
var box={ //这是字面量的声明方式,相当于 var box=new Box();
name:‘Lee‘,
age:100,
family:[‘哥哥‘,‘姐姐‘,‘妹妹‘]
}
var box1=obj(box); //box1就等于new出来的F
alert(box1.name); //Lee
box1.family.push(‘弟弟‘);
alert(box1.family); //哥哥姐姐妹妹弟弟
var box2=obj(box);
alert(box2.family); //哥哥姐姐妹妹弟弟 引用类型的属性共享了 因为把box全部放到F.prototype里了
标题说的原型式继承实际上是F继承了box F.prototype=o 其实就相当于Desk.prototype= new Box()
这种方法其实就是原型链继承 意思是一样的 只不过是改写了方式
5.寄生式继承 =原型式+工厂模式 ,目的是为了封装创建对象的过程
//临时中转函数
function obj(o){ //o表示将要传递进入的一个对象实例
function F(){} //F构造是一个临时新建的对象,用来存储传递进来的对象
F.prototype=o; //将o对象实例赋值给F构造的原型
return new F(); // 最后返回这个得到传递过来对象的对象实例
}
//寄生函数
function create(o){
var f=obj(o);
f.run = function(){
return this.name;
}
return f;
}
var box={ //这是字面量的声明方式,相当于 var box=new Box();
name:‘Lee‘,
age:100,
family:[‘哥哥‘,‘姐姐‘,‘妹妹‘]
}
var box1=create(box);
alert(box1.name);
alert(box1.run());//Lee
组合式继承是 JavaScript 最常用的继承模式;但,组合式继承也有一点小问题,就是超
类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的
内部。
function Box(name) {
this.name = name;
this.arr = [‘哥哥‘,‘妹妹‘,‘父母‘];
}
Box.prototype.run = function () {
return this.name;
};
function Desk(name, age) {
Box.call(this, name); //第二次调用 Box
this.age = age;
}
Desk.prototype = new Box(); //第一次调用 Box
以上代码是之前的组合继承,那么寄生组合继承,解决了两次调用的问题。
6.寄生组合继承
//临时中转函数
function obj(o){
function F(){}
F.prototype=o;
return new F();
}
//寄生函数
function create(box,desk){
var f=obj(box.prototype); //调用obj函数
f.constructor= desk; //调整原型构造指针
desk.prototype =f;
}
function Box(){
this.name=name;
this.age=age;
}
Box.prototype.run=function(){
return this.name+this.age; //方法放在原型 让每次实例化的时候 地址保持一致
}
function Desk(name,age){
Box.call(this,name,age);
}
//通过寄生组合继承来实现继承
create(Box,Desk); //这句话用来替代Desk.prototype=new Box();
var desk = new Desk(‘Lee‘,100);
alert(desk.run()); //Lee100