JS中自定义类和对象
5 自定义类和对象
5.1 工厂方法
在ECMAScript中创建工厂方法,返回一个特定类型的对象,以此实现代码的简洁适用。
function createFruit() {
var tempFruit = new Object;
https://www.360docs.net/doc/b29214379.html, = "apple";
tempFruit.number = 5;
tempFruit.showName = function() {
alert(https://www.360docs.net/doc/b29214379.html,);
};
return tempFruit;
}
var Fruit1 = creatFruit();
var Fruit2 = creatFruit();
在createFruit()中可以加入形参来传入参数的值。随着ECMAScript不断被规范化,这种创建对象的方法已不再流行,一部分原因是语法上的,一部分原因是功能上的,如每个对象的实例都拥有属于自己的showName方法,给内存管理带来一定的开销。
5.2 构造函数
选择一个类名,第一个字母大写,该类名即是构造函数的名称。创建一个构造函数和工厂方法比较类似,不同的是需要使用关键字new来创建对象的引用。使用构造函数的方式来创建对象和使用工厂方法有着相同的弊端。
function Fruit(name, number) {
https://www.360docs.net/doc/b29214379.html, = name;
this.number = number;
this.showName = function() {
alert(https://www.360docs.net/doc/b29214379.html,);
};
}
var Fruit1 = new Fruit("apple", 5);
var Fruit2 = new Fruit("pear", 3);
5.3 使用Prototype
使用prototype属性可以用来创建新的对象,首先需要一个空的构造函数建立类的名称,然后所有的属性和方法都直接分配到prototype属性中。
function Fruit() {
}
https://www.360docs.net/doc/b29214379.html, = "apple";
Fruit.prototype.number = 5;
Fruit.prototype.showName = function() {
alert(https://www.360docs.net/doc/b29214379.html,);
};
var fruit1 = new Fruit();
var fruit2 = new Fruit();
但是,这样同样存在一些缺点。首先,构造函数中没有参数,给初始化带来一些麻烦,其次,当一个属性指向的是一个对象而非方法时,该对象会被所有的实例所共享,任何一点改动都会影响到其他对象引用的使用。
5.4 混合使用工厂方法和Prototype
这个概念很简单:使用构造函数定义所有除方法外的属性,使用prototype 定义对象的方法。这样每个方法只会被创建一次,每个对象都能拥有自己对象实例的属性。
function Fruit(name, number) {
https://www.360docs.net/doc/b29214379.html, = name;
this.number = number;
this.owner = new Array("Jerry", "Terry");
}
Fruit.prototype.showName = function() {
alert(https://www.360docs.net/doc/b29214379.html,);
};
var Fruit1 = new Fruit("apple", 5);
var Fruit2 = new Fruit("pear", 3);
5.5 动态prototype
简单来说,这种方法就是使用了一个标识符来判断prototype 是否已经被指向某个方法,从而保证这些方法只会被创建并指向一次。
5.6 混合工厂方法
这种方法和经典的工厂方法及构造函数方法在对象方法内存管理上存在同样的问题,一般不建议使用该方法,除了某些特殊情况(XML in JavaScript中有这样的例子)。
6 修改对象
使用prototype对象可以对对象进行修改。除了用户自定义的对象外,ECMAScript原始对象也有prototype属性。直接使用prototype可以给对象创建新的方法。
Number.prototype.toHexString = function() {
return this.toString(16);
};
var iNum = 10;
alert(iNum.toHexString()); //输出A
另外,使用prototype可以轻松修改已有的方法,让方法名指向新的方法。需要注意的是,指向新的方法后,原有的方法不再被任何对象使用,将会被垃圾回收器销毁,使得原有方法不再存在。比较安全的解决办法是,建立一个新的引用来保存原有的方法,然后再将原方法覆盖。
比较特殊的是,ECMAScript中创建对象,在对象引用被创建后,可以给对象加入新的方法,并且可以立即在对象的引用中使用。这是ECMAScript的一个特性,但不推荐这样使用,以免带来不必要的麻烦,例如阅读理解、文档资料等。
第一种方式:工厂方法
能创建并返回特定类型的对象的工厂函数(factory function)。
Javascript代码
1.function createCar(sColor){
2. var oTempCar = new Object;
3. oTempCar.color = sColor;
4. oTempCar.showColor = function (){
5. alert(this.color);
6. };
7. return oTempCar;
8.}
9.var oCar1 = createCar();
10.var oCar2 = createCar();
调用此函数时,将创建新对象,并赋予它所有必要的属性。使用此方法将创建car对象的两个版本(oCar1和oCar2),他们的属性完全一样。
使用此方法存在的问题:
1语义上看起来不像使用带有构造函数的new运算符那么正规。
2使用这种方式必须创建对象的方法。每次调用createCar(),都要创建
showColor(),意味着每个对象都有自己的showColor()版本,事实上,每个对象都共享了同一个函数。
有些开发者在工厂函数外定义对象的方法,然后通过属性指向该方法。从而避免这个问题:
Js代码
1.function createCar(sColor){
2. var oTempCar = new Object;
3. oTempCar.color = sColor;
4. oTempCar.showColor = showColor;
5. return oTempCar;
6.}
7.function showColor(){
8. alert(this.color);
9.}
在这段重写的代码中,在函数createCar()前定义了函数showColor().在createCar()内部,赋予对象一个已经指向已经存在的showColor()函数的指针。从功能上来讲,这样解决了重复创建对象的问题,但该函数看起来不像对象的方法。
所有这些问题引发了开发者定义的构造函数的出现。
第二种方式:构造函数方式
Js代码
1.function Car(sColor){
2. this.color = sColor;
3. this.showColor = function (){
4. alert(this.color);
5. };
6.}
7.var oCar1 = new Car("red");
8.var oCar2 = new Car("blue");
你可能已经注意到第一个差别了,在构造函数内部无创建对象,而是使用this 关键字。使用new运算符调用构造函数时,在执行第一行代码前先创建一个对象,只有用this才能访问该对象。然后可以直接赋予this属性,默认情况下是构造函数的返回值(不必明确使用return运算符)。
这种方式在管理函数方面与工厂方法一样都存在相同的问题。
第三种方式:原型方式
Js代码
1.function Car(){
2.
3.}
4.Car.prototype.color = "blue";
5.var oCar1 = new Car();
6.var oCar2 = new Car();
调用new Car()时,原型的所有属性都被立即赋予要创建的对象,意味着所有的Car实例存放的都是指向showColor()函数的指针。从语义上看起来都属于一个对象,因此解决了前面两种方式存在的两个问题。此外使用该方法,还能用instanceof运算符检查给定变量指向的对象类型。因此,下面的代码将输出true:
alert(oCar instanceof Car); //outputs "true"
这个方式看起来很不错,遗憾的是,它并不尽如人意。
1首先这个构造函数没有参数。使用原型方式时,不能给构造函数传递参数初始化属性的值,因为car1和car2的属性都等于“red”。
2真正的问题出现在属性指向的是对象,而不是函数时。函数共享不会造成任何问题,但是对象却很少被多个实例共享的。
第四种方式:混合的构造函数/原型方式(推荐)
联合使用构造函数和原型方式,就可像用其他程序设计语言一样创建对象。这种概念非常简单,即用构造函数定义对象的所有非函数属性,用原型方式定义对象的函数属性(方法)。
Js代码
1.function Car(sColor){
2. this.color =sColor;
3. this.drivers =new Array("Mike","Sue");
4.}
5.Car.prototype.showColor = function(){
6. alert(this.color);
7.}
8.var oCar1 =new Car("red");
9.var oCar2 =new Car("blue");
10.
11.oCar1.drivers.push("Matt");
12.
13.alert(oCar1.drivers); //outputs "Mike,Sue,Matt"
14.alert(oCar1.drivers); //outputs "Mike,Sue"
第五种方式:动态原型方式(推荐)
对于习惯使用其他语言的开发者来说,使用混合的构造函数/原型方式感觉不那
么和谐。批评混合的构造函数/原型方式的人认为,在构造函数内找属性,在外部找方法的做法很不合理。所以他们设计了动态原型方式,以提供更友好的编码风格。
动态原型方法的基本想法与混合的构造函数/原型方式相同,即在构造函数内定义非函数属性,而函数属性则利用原型属性定义。唯一的区别是赋予对象方法的位置。下面是使用动态原型方法重写的Car类:
Js代码
1.function Car(sColor){
2. this.color =sColor;
3. this.drivers =new Array("Mike","Sue");
4.
5. if(typeof Car._initialized == "undefined"){
6. Car.prototype.showColor = function(){
7. alert(this.color);
8. }
9. }
10. Car._initialized = true;
11.}
=====================================================================
Javascript本身并不支持面向对象,它没有访问控制符,它没有定义类的关键字class,它没有支持继承的extend或冒号,它也没有用来支持虚函数的virtual,不过,Javascript是一门灵活的语言,下面我们就看看没有关键字class的Javascript如何实现类定义,并创建对象。
一:定义类并创建类的实例对象
在Javascript中,我们用function来定义类,如下:
function Shape()
{
var x=1;
var y=2;
}
你或许会说,疑?这个不是定义函数吗?没错,这个是定义函数,我们定义了一个Shape
函数,并对x和y进行了初始化。不过,如果你换个角度来看,这个就是定义一个Shape
类,里面有两个属性x和y,初始值分别是1和2,只不过,我们定义类的关键字是function 而不是class。
然后,我们可以创建Shape类的对象aShape,如下:
var aShape = new Shape();
二:定义公有属性和私有属性
我们已经创建了aShape对象,但是,当我们试着访问它的属性时,会出错,如下:aShape.x=1;
这说明,用var定义的属性是私有的。我们需要使用this关键字来定义公有的属性function Shape()
{
this.x=1;
this.y=2;
}
这样,我们就可以访问Shape的属性了,如。
aShape.x=2;
好,我们可以根据上面的代码总结得到:用var可以定义类的private属性,而用this能定义类的public属性。
三:定义公有方法和私有方法
在Javascript中,函数是Function类的实例,Function间接继承自Object,所以,函数也是一个对象,因此,我们可以用赋值的方法创建函数,当然,我们也可以将一个函数赋给类的一个属性变量,那么,这个属性变量就可以称为方法,因为它是一个可以执行的函数。代码如下:
function Shape()
{
var x=0;
var y=1;
this.draw=function()
{
//print;
};
}
我们在上面的代码中定义了一个draw,并把一个function赋给它,下面,我们就可以通过aShape调用这个函数,OOP中称为公有方法,如:
aShape.draw();
如果用var定义,那么这个draw就变成私有的了,OOP中称为私有方法,如
function Shape()
{
var x=0;
var y=1;
var draw=function()
{
//print;
};
}
这样就不能使用aShape.draw调用这个函数了。
三:构造函数
Javascript并不支持OOP,当然也就没有构造函数了,不过,我们可以自己模拟一个构造函数,让对象被创建时自动调用,代码如下:
function Shape()
{
var init = function()
{
//构造函数代码
};
init();
}
在Shape的最后,我们人为的调用了init函数,那么,在创建了一个Shape对象是,init总会被自动调用,可以模拟我们的构造函数了。
四:带参数的构造函数
如何让构造函数带参数呢?其实很简单,将要传入的参数写入函数的参数列表中即可,如
function Shape(ax,ay)
{
var x=0;
var y=0;
var init = function()
{
//构造函数
x=ax;
y=ay;
};
init();
}
这样,我们就可以这样创建对象:
var aShape = new Shape(0,1);
五:静态属性和静态方法
在Javascript中如何定义静态的属性和方法呢?如下所示
function Shape(ax,ay)
{
var x=0;
var y=0;
var init = function()
{
//构造函数
x=ax;
y=ay;
};
init();
}
Shape.count=0;//定义一个静态属性count,这个属性是属于类的,不是属于对象的。Shape.staticMethod=function(){};//定义一个静态的方法
有了静态属性和方法,我们就可以用类名来访问它了,如下
alert ( aShape.count );
aShape.staticMethod();
注意:静态属性和方法都是公有的,目前为止,我不知道如何让静态属性和方法变成私有的~
六:在方法中访问本类的公有属性和私有属性
在类的方法中访问自己的属性,Javascript对于公有属性和私有属性的访问方法有所不同,请大家看下面的代码
function Shape(ax,ay)
{
var x=0;
var y=0;
this.gx=0;
this.gy=0;
var init = function()
{
x=ax;//访问私有属性,直接写变量名即可
y=ay;
this.gx=ax;//访问公有属性,需要在变量名前加上this.
this.gy=ay;
};
init();
}
七:this的注意事项
根据笔者的经验,类中的this并不是一直指向我们的这个对象本身的,主要原因还是因为Javascript并不是OOP语言,而且,函数和类均用function定义,当然会引起一些小问题。this指针指错的场合一般在事件处理上面,我们想让某个对象的成员函数来响应某个事件,当事件被触发以后,系统会调用我们这个成员函数,但是,传入的this指针已经不是我们本身的对象了,当然,这时再在成员函数中调用this当然会出错了。
解决方法是我们在定义类的一开始就将this保存到一个私有的属性中,以后,我们可以用这个属性代替this。我用这个方法使用this指针相当安全,而且很是省心~
我们修改一下代码,解决this问题。对照第六部分的代码看,你一定就明白了
function Shape(ax,ay)
{
var _this=this; //把this保存下来,以后用_this代替this,这样就不会被this弄晕了
var x=0;
var y=0;
_this.gx=0;
_this.gy=0;
var init = function()
{
x=ax;//访问私有属性,直接写变量名即可
y=ay;
_this.gx=ax;//访问公有属性,需要在变量名前加上this.
_this.gy=ay;
};
init();
}
以上我们聊了如何在Javascript中定义类,创建类的对象,创建公有和私有的属性和方法,创建静态属性和方法,模拟构造函数,并且讨论了容易出错的this。
关于Javascript中的OOP实现就聊到这里,以上是最实用的内容,一般用Javascript定义类,创建对象用以上的代码已经足够了。当然,你还可以用mootools或prototype来定义类,创建对象。我用过mootools框架,感觉很不错,它对Javascript的类模拟就更完善了,还支持类的继承,有兴趣的读者可以去尝试一下。当然,如果使用了框架,那么在你的网页中就需要包含相关的js头文件,因此我还是希望读者能够在没有框架的情况下创建类,这样,代码效率较高,而且你也可以看到,要创建一个简单的类并不麻烦~
在下一个版本的Javascript中,将会加入对OOP的支持,届时,我们将看到class关键字,public、private访问控制符~extend继承。如果大家对下一代Javascript感兴趣,不妨可以先接触一下ActionScript3.0,这是一门相当先进的脚本语言,是Adobe收购Macromedia后的一个力作,它和Javascript一样,都属于ECMAScript,只不过AS3.0是ECMAScript的第4版的实现。要学习ActionScript3.0,我重点推荐孙颖老师的《ActionScript3殿堂之路》,这本书写的绝对好,特别是面向对象这部分,写的特别通俗易懂,例子生动,读完以后感觉很有收获,在赞叹新版的ECMAScript的同时,也给了我极大的动力继续深入学习OOP编程思想,有兴趣的读者不妨去品读一下~