js中面向对象

js中没有定义类,但却有function与对象,结合类的思想,可以通过他们来模仿类。
类有封装、继承的特点,那么先从这两点开始来看js中如何实现的。

封装

大家都知道如何创建一个对象:
var obj=new Object();

obj.name="myobject";

obj.sayName=function(){

return this.name;

};
这有一个问题:当要创建大量这种对象时,会出现大批的重复代码。可以通过工厂模式来解决:
function createPerson(name){

var o =new Object();

o.name=name;

o.sayName=function(){

return this.name;

};

return o;

}
调用:var person1=createPerson("xiaoqi");
这个虽然可以解决以上的问题,但无法识别对象类型。
构造函数模式:
function Person(name){

this.name=name;

this.sayName=function(){

return this.name;

};

}
调用:
var person1=new Person("xiaoqi"); 
var person2=new Person("xiaosan"); 
缺点:每创建一个实例都要创建一个方法,内存中会出现重复的相同功能的重复方法,无必要的浪费了内存
解决方法:
function Person(name){

this.name=name;

this.sayName=sayName;


function sayName(){

return this.name;

}
调用:
var person1=new Person("xiaoqi"); 
var person2=new Person("xiaosan"); 
 这样person1与person2的sayName属性保存的只是sayName的内存地址,而不用重新创建新的方法。
缺点:
1.定义很多方法,就要定义很多全局函数
2.函数全局作用域名不副实,全局作用域中定义的函数实际上只能被某个对象调用
3.没有丝毫封装性可言 
要解决上面的问题引进了原型模式:
function Person(){}
Person.prototype.name="myName";
Person.prototype. sayName=function(){

return this.name;

};
结构图如:

Person.prototype指向的就是Person的原型对象,实例person1与person2也都有一个指针指向此原型对象。

简单原型:
function Person(){}
Person.prototype={

name:"myName",

sayName:function(){

return this.name;

}

};
问题:这时的Person.prototype的constructor不再指向Person,指向了Object。因为Person.prototype指向一个Object对象。
如:
var person=new Person();
person instanceof Person  //true
person instanceof Object  //true
person.constructor==Person  //false
person.constructor==Object  //true
可通过重指constructor来修正
Person.prototype={
constructor:Person,
name:"myName",
sayName:function(){
return this.name;
}
};
此时:
person.constructor==Person  //true
person.constructor==Object  //false
有人说Person也是一种Object为什么会出现这种结果呢。 这个在后面继承中会再做解释。
 原型动态性:
修改原型中的属性,可立即在实例中得到体现,如:
var person=new Person();  //此时person中没有方法sayHi
Person.prototype.sayHi=function(){

alert("Hi");

}; 
person.sayHi();  //Hi
虽然实例化时person没有sayHi方法,但实例化后通过prototype添加了sayHi方法,原来的实例也一样可以访问此方法,并不是只有后面的实例才能访问。这就是原型的主要功能与优点。
但重写了prototype就会不同了:
function Person(){}
var person=new Person();
Person.prototype={

constructor:Person,

name:"myName",

sayName:function(){

return this.name;

}
};
person.sayName(); //error 访问不到sayName方法 
解释如图:
 
这里person实例重写之后的原型仍然指向原来的指针,原来没有sayName方法,重写后仍然也就没有了。
原型缺点:原型对于方法避免了重复定义方法,节省了内存空间,很是适用。但对于属性就不合适了,尤其是引用类型的属性。请看:
functon Person(){}
Person.prototype={

constructor:Person,

friends:["s","c"]

}; 
var person1=new Person(); 
var person2=new Person(); 
person1.friends.push("v");
person1.friends  //"s,c,v"
person2.friends //"s,c,v"
这就是问题所在了,修改了一个实例的属性,所有实例的这个属性都被修改了,而我们一般想要实例的属性归它自己所有,与其它的不同。方法共享,与其它的相同。
这样就产生了最为常用的模式:组合使用构造函数与原型模式:
function Peson(name){

this.name=name;

this.job=["s","c"];

}
Person.prototype={

constructor:Person,

sayName:function(){

return this.name;

}
};

这个模式有三人优点:
1.自己独有的属性
2.公用的方法,节省内存
3.支持传递参数
结构图如:

至此,js模仿类的封装已经完结,这个模式是最为常用的一个模式。 这里的Person是构造函数,Person prototype是Person的原型,
person是Person的实例。这里要注意一点,构造函数也是一个实例,是Function的实例,继承自Object,所以Person也是一个对象,它也有自己的属性,如prototype是构造函数时自动生成的,同时也可以自定义属性、方法,如:default属性。

继承

后台继承分为接口继承与实现继承两种,我们在这里只介绍实现继承。
实现的基本思路是借用构造函数的prototype指针指向,如果把这个指向另一人构造函数的实例会怎么样呢。如:
function SuperType(){

this.property=true;

}
SuperType.prototype.getSuperValue=function(){

return this.property;

};
function SubType(){

this.subproperty=false;

}
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){

return this.subproperty;

};

调用:
var instance=new SubType();
结构图如:

SubType的原型为SuperType的一个实例即SubType prototype,则SubType prototype指向SuperType的原型即SuperType prototype。如此类推下去就形成一个链式结构,我们称之为原型链。
借用原型链SubType就继承到了SuperType的所有属性与方法。
注意SuperType Prototype本身就是个对象,它也是继承了Object的实例。故:

问题:
1.超类型属性为引用类型时,所有实例都共享此属性
2.无法向超类型的构造函数传参数
解决以上问题借用构造函数:
function SuperType(name){

this.name=name;

this.colors=["s","c"];

}
SuperType.prototype.getSuperValue=function(){};
function SubType=function(name){

SuperType.call(this,name);

this.age=29;

}
解决了上面的两个问题,但却无法访问超类型的原型中的方法。
组合继承:
function SuperType(name){

this.name=name;

this.colors=["s","c"];

}
SuperType.prototype.getSuperValue=function(){

};
function SubType(name,age){

SuperType.call(this,name);    //第二次调用

this.age=age;

}
SubType.prototype=new SuperType();  //第一次调用
SubType.prototype.sayAge=function(){

return this.age;

};
这样即可以拥有自己的属性friends,又可以有相同的共享方法,还可以识别类型(instanceof,isPrototypeOf)
但SuperType的属性有存在两组:

寄生式组合继承:
function SuperType(name){

this.name=name;

this.colors=["s","c"];

}
SuperType.prototype.getSuperValue=function(){

};
function SubType(name,age){

SuperType.call(this,name);

this.age=age;

}
//
function F(){}
F.prototype=SuperType.prototype;
SubType.prototype=new F();
SubType.prototype.contructor=SubType;
//
SubType.prototype.sayAge=function(){

return this.age;

};
此方式为最常用的继承方式。

另SubType,SuperType等构造函数定义也是一种实例化,是Function的实例。再加上prototype(原型对象)是一个对象,也是Object的一个实例。故最终图为:


至此,js中模仿类的封装与继承就介绍完毕了。 
补充一张网上的object关系图
posted @ 2013-03-27 10:45  small-joker  阅读(287)  评论(0编辑  收藏  举报