JavaScript中定义类型的方式有多种,这也形成了不同的类型模式。
(1)工厂模式
工厂模式是指通过函数把一个类型实例包装起来,这样可以通过调用函数来实现类型的实例化。
function wrap(title,pages){
var book=new Object;
book.title=title;
book.pages=pages;
book.what=function{
alert(this.title+this.pages)
}
return book;//初始化后的对象
}
也可以对该模式进行优化,进而消除重复创建相同函数的弊端,节约大量资源。
what=function{
alert(this.title+this.pages)
}
function wrap(title,pages){
var book=new Object;
book.title=title;
book.pages=pages;
book.what=what;
return book;
}
工厂模式只是一种伪装的构造函数,不推荐使用。
(2)构造函数模式
在JavaScript中,构造函数具有如下特性:
❑构造函数使用new运算符进行调用。
❑在构造函数内部,this关键字指代当前实例对象。
❑在构造函数内部,必须通过点运算符来声明和引用成员。构造函数的结构体内可以包含一般函数的执行语句。
例如,对于下面这个构造函数Box,传递给Box的是一个新创建的空对象,该对象是高度抽象但未知的,通过this关键字来代称,this的值就是这个新创建的空对象引用。当使用new运算符实例化构造函数时,可以通过传递参数来初始化这个对象的属性值。
function Box(w,h){//构造函数
this.w=w;
this.h=h;
}
var box1=new Box(4,5);//实例并初始化构造函数
由于每一个构造函数代表一种类型,为了与普通函数进行区分,函数名应该很直观,并且首字母要大写(非强制的)。如果构造函数返回对象,那么被返回的对象将覆盖this的值。例如:
function Box(w,h){//构造函数
this.w=w;
this.h=h;
return this;
}
(3)原型模式
先声明一个构造函数,并且利用构造函数的prototype属性为该构造函数定义原型属性title和pages,以及原型方法what。构造函数的原型成员将会被所有实例对象继承,这样当使用new运算符实例化对象时,所有对象都拥有原型属性中定义的成员。
function Book{//空类
}
Book.prototype.title=/"Javascript设计方法/";
Book.prototype.pages=200;
Book.prototype.what=function{
alert(this.title+this.pages);
};
从语义的角度分析,通过原型继承的方式,实现了在前面两种模式中将对象与其方法分离的设计思想。使用instanceof运算符能够方便地检测对象实例的类型。原型模式存在以下两个问题:
❑由于构造函数已被事先声明,而原型属性在类结构声明之后才被定义,因此无法通过构造函数参数向原型属性动态传递值。这样所带来的后果:由该类实例化的所有对象都是一个“模样”,没有“个性”。如果改变原型属性值,那么所有实例都受到干扰。这是非常严重的问题,如果无法解决,该项研究将无果而终。
❑当原型属性的值为引用类型数据时,如果在一个对象实例中修改该属性值,将会影响所有的实例。由于原型属性x的值为一个引用类型数据,因此所有对象实例的属性x的值都是指向该对象的引用指针。一旦某个对象的属性值被改动,其他实例对象的属性值也会随着发生变化。
(4)构造函数原型模式
构造函数原型模式是建立在原型模式基础上的一种混合设计模式,将构造函数模式与原型模式混合使用。对于可能会相互影响且希望动态传递参数的属性,将其拆分出来使用构造函数模式进行设计。而对于不需要个性、希望共享,并且又不会相互影响的方法或属性,单独使用原型模式来设计。
function Book(title,pages){//构造函数模式设计
this.title=title;
this.pages=pages;
}
Book.prototype.what=function{//原型模式设计
alert(this.title+this.pages);
};
在混合使用构造函数与原型模式时,可以不使用构造函数来定义对象的所有非函数属性(即对象属性),而使用原型模式来定义对象的函数属性(即对象方法)。这样所有方法都只创建一次,而每个对象都能够根据需要自定义属性值。这种混合型模式成为ECMAScript定义类的推荐标准,这也是使用最广的一种设计模式,它具有前面3种设计模式的所有优点,而且去除了它们的副作用。
遵循面向对象的设计原则,类的所有成员都应该封装在类结构体内,因此可以进一步优化构造函数原型模式,从而产生动态原型模式。优化的思路:使用条件结构封装该原型方法,判断原型方法是否存在,如果存在,则不再创建该方法,否则就创建该方法。
function Book(title,pages){
this.title=title;
this.pages=pages;
if(typeof Book.isLock==/"undefined/"){//创建原型方法的锁,如果不存在该方法则创建
Book.prototype.what=function{
alert(this.title+this.pages);
};
Book.isLock=true;//创建原型方法后,把锁锁上,避免重复创建
}
}
“typeof Book.isLock”表达式能够检测该属性值的类型,如果返回undefined字符串,则表示不存在该属性值,说明还没有创建原型方法,允许进入分支结构创建原型方法,并设置该属性的值为true,这样它的类型返回值就是boolean字符串。
动态原型模式与构造函数原型模式在性能上是等价的,不过目前使用最广泛的是构造函数原型混合模式。