`

10、类或对象的6种定义方法

阅读更多

可以以六种方式定义类或对象:工厂方式、构造函数方式、原型方式、混合的构造函数/原型方式、动态原型方法、混合工厂方式 ,下面看看个体每种方式。

  • 工厂方式

var oCar = new Object;
oCar.color = "red";
oCar.doors = 4;
oCar.mpg = 23;
oCar.showColor = function () {
	alert(this.color);
};

 

上面的创建方式有一点不太好就是的,如果要创建多个car对象时,这时我们就会要重复写上面的代码,指定color、doors、mpg与 showColor属性方法。要解决此问题,开发者创造了能创建并返回特定类型的对象的工厂函数(factory function)。例如,函数createCar()可用于封装前面列出的创建car对象的操作:

function createCar(color, doors, mpg) {
	var oTempCar = new Object;
	oTempCar.color = color;
	oTempCar.doors = doors;
	oTempCar.mpg = mpg;
	oTempCar.showColor = function () {
		alert(this.color + " " + this.doors + " " + this.mpg);
	};
	return oTempCar;
}
var oCar1 = createCar("red", 4, 23);
var oCar1 = createCar("blue", 3, 25);

 

以上方式存在功能问题:功能问题在于用这种方式必须创建对象的方法。上面的例子中,每次调用函数createCar(),都要创建新函数 showColor(),意味着每个对象都有自己的showColor()版本,事实上,每个对象都是可以共享同一个函数。但下面可以避开此种问题,请看重写后的代码:

function showColor() {
	alert(this.color + " " + this.doors + " " + this.mpg);
}
function createCar(color, doors, mpg) {
	var oTempCar = new Object;
	oTempCar.color = color;
	oTempCar.doors = doors;
	oTempCar.mpg = mpg;
	oTempCar.showColor = showColor;
	return oTempCar;
} 
  • 构造函数方式  

function Car(color, doors, mpg) {
	this.color = color;
	this.doors = doors;
	this.mpg = mpg;
	this.showColor = function () {
		alert(this.color + " " + this.doors + " " + this.mpg);
	};
}
var oCar1 = new Car("red", 4, 23);
var oCar2 = new Car("blue", 3, 25);
oCar1.showColor();
oCar2.showColor();

你可能已经注意到第一个差别了,在构造函数内部无创建对象,而是使用this关键字。使用new运算符调用构造函数时,在执行第一行代码前就先创建出一个对象,然后才是执行构造函数,这与Java是一样的。在对象里面也只有用this才能访问该对象。this默认情况下是构造函数的返回值(不必明确使用 return运算符)
现在,用new运算符和类名Car创建对象,就更像创建ECMAScript中一般对象了。你也许会问,这种方式在管理函数方面是否存在与前一种方式相同的问题呢?是的。就像工厂函数,构造函数会重复生成函数,为每个对象都创建独立的函数版本。不过,与工厂函数相似,也可以用外部函数重写构造函数,同样的,语义上无任何意义。这就是原型方式的优势所在。 

  • 原型方式

该方式利用了对象的prototype属性,可把它看成创建新对象所依赖的原型。这里,用空构造函数来设置类名。然后所有的属性和方法都被直接赋予prototype属性。重新前面的例子,代码如下所示:

function Car() {
}
Car.prototype.color = "red";
Car.prototype.doors = 4;
Car.prototype.mpg = 23;
Car.prototype.showColor = function () {
	alert(this.color + " " + this.doors + " " + this.mpg);
 };
 var oCar1 = new Car();
 var oCar2 = new Car();
 oCar1.showColor();
 oCar2.showColor();

在这段代码中,首先定义构造函数(Car),其中无任何代码。接下来的几行代码,通过给Car的prototype属性添加属性定义Car对象的属性。调用new Car()时,原型的所有属性都被立即赋予要创建的对象,意味着所有Car实例存放的都是指向showColor()函数的指针。从语义上讲,所有属性看起来都属于一个对象,因此解决了前面两种方式的两个问题。

这个看起来是个非常好的解决方案。遗憾的是,并非尽如人意。
首先,这个构造函数没有参数。使用原型方式时,不能通过给构造函数传递参数初始化属性的值,因为car1和car2的color属性都等于"red",doors属性都等于4,mpg属性都等于23。这意味必须在对象创建后才能改变属性的默认值,这点很令人讨厌,但这还不是真真的问题所在。真正的问题出现在属性如果指向的是一个对象,而不是函数时。函数共享不会造成任何问题,因为代码区永远是可以共享的,但对象一般却不是我们所需要的多个实例共享的它。如果在上面代码的基础上为原型加如下属性时:

Car.prototype.Drivers = new Array('Mike','Sue');
oCar1.drivers.push('Matt');
alert(oCar1.drivers);//Mike,Sue,Matt
alert(oCar2.drivers); //Mike,Sue,Matt 

 由于drivers是引用值,Car的两个实例都指向同一个数组。这意味着给car1.drivers添加值"Matt",在car2.drivers中也能看到。
上面创建对象有很多的问题,下面联合使用构造函数和原型方式来解决这些问题。

  • 混合的构造函数/原型方式

用构造函数定义对象的所有非函数属性,用原型方式定义对象的函数属性(方法)。结果所有函数都只创建一次,而每个对象都具有自己的对象属性实例。再重写前面的例子,代码如下:

function Car(color, doors, mpg) {
	this.color = color;
	this.doors = doors;
	this.mpg = mpg;
	this.drivers = new Array("Mike", "Sue");
}
Car.prototype.showColor = function () {
	alert(this.color + " " + this.doors + " " + this.mpg);
};
var oCar1 = new Car("red", 4, 23);
var oCar2 = new Car("blue", 3, 25);
oCar1.drivers.push("Matt");
alert(oCar1.drivers);//Mike,Sue,Matt
alert(oCar2.drivers);//Mike,Sue 

现在就更像创建一般对象了。所有的非函数属性都在构造函数中创建。因为只创建showColor()函数的一个实例,所以没有内存浪费。这种方式是 ECMAScript主要采用的方式,它具有其他方式的特性,却没有它们的副作用。不过,有些开发者仍觉得这种方法不够完美。

  • 动态原型方法

批评混合的构造函数/原型方式的人认为,面向对象的设计就要求把属性与方法封装在类里面,而在其外部定义方法的做法不合逻辑。因此,他们设计了动态原型方法,以提供像面向对象语言一样的更友好的编码风格。
动态原型方法的基本想法与混合的构造函数/原型方式相同,即在构造函数内定义非函数属性,而函数属性则利用原型属性定义。唯一的区别是赋予对象方法的位置,把函数属性的定义放置到了构造函数里。下面是用动态原型方法重写的Car类:

function Car(color, doors, mpg) {
	this.color = color;
	this.doors = doors;
	this.mpg = mpg;
	this.drivers = new Array("Mike", "Sue");
	if (typeof Car._initialized == "undefined") {
		Car.prototype.showColor = function () {
			alert(this.color + " " + this.doors + " " + this.mpg);
		};
		Car._initialized = true;
	}
}

该方法使用标志(_initialized)来判断是否已给原型赋予了任何方法。该方法只创建并赋值一次,为取悦传统的OOP开发者,这段代码看起来更像其他语言中的类定义了。

  • 混合工厂方式

创建假构造函数,只返回另一种对象的新实例。这段代码看来与工厂函数非常相似:

function Car(color, doors, mpg) {
	var oTempCar = new Object;
	oTempCar.color = color;
	oTempCar.doors = doors;
	oTempCar.mpg = mpg;
	oTempCar.showColor = function () {
		alert(this.color + " " + this.doors + " " + this.mpg);
	};
	return oTempCar;
}

 

与经典方式不同,这种方式使用new运算符,使它看起来像真正的构造函数:

var car = new Car(); 

 

由于在Car()构造函数内部调用了new运算符,所以将忽略赋值表达中的new运算符(位于构造函数之外)(注:如果返回的是一个基本类型,则会忽略方法里的return,具体原理请见《7、对象》——《构造函数》 一节) 。在构造函数内部创建的对象被传递回变量car。这种方式在对象方法的内部管理方面与经典方式有着相同的问题。建议还是避免使用这种方式。

  • 采用哪种方式

如前所述,目前使用最广泛的是混合的构造函数/原型方式。此外,动态原型方法也很流行,在功能上与构造函数/原型方式等价。可以采用这两种方式中的任何一种。不过不要单独使用经典的构造函数或原型方式,因为这样会给代码引入问题。

分享到:
评论

相关推荐

    实验1 类的定义、对象数组的使用

    实验1 类的定义、对象数组的使用 1.定义一个学生类(Student), 属性有 1)非静态属性String studentNumber 2)非静态属性String studentName 3)非静态属性int markForMaths 4)非静态属性int markForEnglish 5)非...

    源代码+报告-Java类的定义和对象的创建

    6)void show( ) //按照实部+虚部i(如10+8i)的形式显示复数对象的值 7)String toString()//返回复数对象对应的串(如4+7i) (2)编写一个主类,定义并实例化两个复数对象,输出这两个复数对象的值,然后进行复数的加法...

    实现一个圆类

    (6) 提供一个判断当前圆对象与参数圆对象的大小关系的方法(判断依据是半径,半径之差的绝对值小于0.0001认为相等;返回负数表示当前圆对象小,0表示相等,正数表示当前圆对象) int compareTo(Circle another) (7)...

    仓库管理系统 面向对象分析与设计

    5.1 类定义 6 5.2 类关联描述 6 5.3 类模型图 6 6 动态模型 9 6.1 活动图 10 6.2顺序图 13 7 数据字典 ……………………………………………………….……………18 8 功能模型 ………………………………………………...

    面向对象与C++试题.doc

    12、在公用继承方式下,有关派生类对象和基类对象的关系,不正确的叙述是( )。 A.派生类的对象可以赋给基类的对象 B.派生类的对象可以初始化基类的引用 C.派生类的对象可以直接访问基类中的成员 D.派生类的...

    2011年6月面向对象分析与设计试题B卷.doc附有详答案

    6. 类的设计应遵循三条基本原则。对于一个设计并实现好的类,如果需要功能上的扩充,一般来说应该通过添加新类实现,而不是修改原类的代码。这种原则叫______。 A. 封装原则 B. 开放/封闭原则 C. 最小惊讶原则 D. ...

    beiyou Java作业 -1

    实验1 类的定义、对象数组的使用 1. 定义一个学生类(Student), 属性有 1)非静态属性String studentNumber 2)非静态属性String studentName 3)非静态属性int markForMaths 4)非静态属性int markForEnglish 5)非...

    Java面向对象程序设计(“方法”相关文档)共57张.pptx

    类主体定义类的成员,包括变量(数据)和方法(行为) Java面向对象程序设计("方法"相关文档)共57张全文共57页,当前为第5页。 类首声明: [<修饰符>] class <类名> [extends <超类名>] [implements <接口名>] class...

    深入理解Java:10个示例展示核心概念和用法涵盖了类和对象、继承和多态、接口和实现、异常处理、集合框架、文件操作、多线程、输入

    3. 接口和实现:展示了接口的定义和实现类的实现,实现类必须实现接口定义的方法。 4. 异常处理:示范了异常处理的基本用法,包括捕获和处理异常。 5. 集合框架:介绍了使用ArrayList作为动态数组来存储和遍历数据的...

    面向对象课设题目及实验报告成品一份

    2.重要方法(或函数)的算法实现分析 9 3.设计阶段得到的对象模型图 10 四、“小公司工资管理系统”实现 10 1欢迎界面 10 2添加界面 11 3查询界面 11 4查看工资界面 12 五、“小公司工资管理系统”设计经验总结 12 ...

    A面向对象分析与设计(UML.2.0版)

    3.5 重定义方法 3.6 实现栈类 3.7 多重继承 3.8 使用继承的规则 3.9 小结 3.10 课外阅读 3.11 复习题 3.12 复习题答案 第4章 类型系统 4.1 引言 4.2 动态和静态类型系统 4.3 多态性 4.4 动态绑定 4.5 多态性规则...

    java语言与面向对象程序设计作业题与参考答案汇编.doc

    A、创建一个数组类对象MyVector,有100个元素的空间,每个元素的初值为50 B、创建一个向量类对象MyVector,有100个元素的空间,每个元素的初值为50 C、创建一个数组类对象MyVector,有100个元素的空间,若空间使用...

    PHP 面向对象技术(全面讲解).txt

    类与对象的关系就如模具和铸件的关系,类的实例化结果就是对象,而对一类对象的抽象就是 类。类描述了一组有相同特性(属性)和相同行为(方法)的对象。 上面大概就是它们的定义吧,也许你是刚接触面向对象的朋友,...

    java面向对象程序设计习题-(11).doc

    类对象可以访问或修改静态属性吗? 5.同名的不同方法共存的情况称为什么?如何区分这些同名方法? 6.什么是包?如何创立包?为什么要使用包?包物理地对应什么? 7.试写出一个语句创立一个名为MyPackage的包,这个语句...

    《C--面向对象程序设计》习题与上机解答-提交稿-陈维兴

    (3) 有一组行为: 对象的行为或功能也称为方法,一般用一组操作来描述; (4) 有一组接口:除施加于对象内部的操作外,对象还提供了一组公有操作用于与外界接口,从而可以与其他对象建立关系。 【1.5】 【解】 在面向...

    设计模式:可复用面向对象软件的基础--详细书签版

    4.1 adapter(适配器)—类对象结构型 模式 92 4.2 bridge(桥接)—对象结构型 模式 100 4.3 composite(组成)—对象结构型 模式 107 4.4 decorator(装饰)—对象结构型 模式 115 4.5 facade(外观)—...

    创建一个Computer类

    准备工作:创建一个Computer类 要求成员变量包括如下: (1)编号(即计算机的唯一标识) (2)CPU型号, (3)主板型号, (4)硬盘大小, (5)内存大小, (6)显卡型号, (7)价格, 题目: (1)要求使用ArrayList存储...

    C++程序y课程设计 动态存储管理和程序调试

    本课程设计使用C++语言编写一个头文件和个源文件的方法,熟悉动态存储管理和程序调试,以便进一步巩固头文件的设计方法并熟悉编程环境及规范。加强使用C++语言运用、解决问题的能力。 假设有一个点类point,具有两个...

    PHP面向对象技术(全面讲解)

    6.如何去使用对象中的成员?7.特殊的引用“$this”的使用8.构造方法与析构方法9.封装性10.__set()、 __get()、 __isset()、 __unset()四个方法的应用11.类的继承12.多态的应用13.重载新的方法14.访问类型15.静态成员...

    点和直线类(综合型题目)

    (10)为CzLine定义公有方法Move,但它只用于平移直线,而不改变直线的斜率(即平移后的直线与原来的直线平行)。再为其定义公有方法Contains,用于判断某点是否在该直线上。 (11)类似的,为CzLine重载相等和不...

Global site tag (gtag.js) - Google Analytics