首页 > 代码库 > 面向对象与原型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