`

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。这种方式在对象方法的内部管理方面与经典方式有着相同的问题。建议还是避免使用这种方式。

  • 采用哪种方式

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

分享到:
评论

相关推荐

    6.类和对象作业-1

    1. **正确** - 类可以看作是一类对象的模板,而对象是根据类创建的具体实例。 2. **正确** - Java会为未初始化的成员变量赋予默认值,如数值变量为0,布尔变量为false,引用变量为null。 3. **错误** - 局部变量在...

    c++类和对象练习题.doc

    类和对象的概念和定义方法 在C++中,类是用户自定义的数据类型,用于描述对象的特征和行为。类是抽象的,不能直接创建对象,而是通过类创建对象。对象是类的实例,每个对象都有其自己的数据成员和成员函数。 ### ...

    类与对象.docx

    方法定义是类中实现特定功能的代码块,包括类的方法和构造方法等。 2. 执行 `Person p = new Person();` 语句时,系统会在堆内存中为 `Person` 对象分配空间,存储实例变量,同时在栈内存中为引用变量 `p` 分配空间...

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

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

    类和对象object.zip

    6. **访问修饰符**:Java有public、private、protected和默认四种访问修饰符,它们控制类、方法和变量的可见性。 7. **静态成员和非静态成员**:静态成员属于类,而非静态成员属于类的每个实例。静态成员在内存中...

    Java类和对象入门级实例代码

    Java是一种广泛使用的面向对象的编程语言,其核心概念包括类和对象。类是Java中的蓝图,定义了数据(属性)和行为(方法)的模板,而对象是类的实例,代表程序中的具体实体。让我们深入了解一下Java类和对象的基础...

    类与对象练习题 带答案

    练习题分为选择题和填空题两部分,涵盖了类的定义、对象的声明、成员函数、访问权限、继承等关键概念。 选择题 1. 下列有关类的说法不正确的是( )。答案:C. 一个类只能有一个对象 知识点:类的定义、对象的...

    C语言程序设计课件第6章类与对象.ppt

    【C语言程序设计课件第6章类与对象.ppt】深入解析 在C++编程中,类与对象是面向对象编程(OOP)的核心概念。面向对象编程是一种编程范式,它基于“对象”的概念,这些对象可以包含数据(属性)和行为(方法)。本...

    Python 面向对象编程

    你可以通过类对象访问其属性和方法,或者通过调用类的`__init__`方法创建实例对象。例如: ```python class People: name = 'jack' p = People() # 实例化 print(p.name) # 访问属性 ``` 3. **属性** ...

    C++面向对象实验代码实验2

    对象是类的实例,它们拥有类定义的所有属性和方法。`test2`可能包含了创建对象并调用其成员函数的代码,以展示如何通过对象来操作数据。 3. 构造函数与析构函数:C++中的构造函数用于初始化新创建的对象,而析构...

    6.类和对象作业答案-1

    题目中没有直接提到静态变量或方法,但在实际编程中,静态变量是所有类实例共享的,而静态方法不依赖于任何特定的对象实例就可以调用。 8. **包(package)**:包是Java中组织类的一种方式,可以避免命名冲突并提供...

    11.类和对象1

    类方法可以用`@classmethod`装饰器标识,可以被类或其对象调用,而静态方法用`@staticmethod`装饰器,不与特定实例绑定,可以直接通过类名或对象名调用。 6. **面向对象的特性**: 面向对象编程的三大特性是封装、...

    JAVA类与对象及数组习题及答案.pdf

    在本段内容中,我们将会讨论Java编程语言的基础知识点,包括类和对象的使用、基本数据类型、数组、方法的定义和调用等。 首先,我们来看看关于Java的类、对象以及数组的概念。Java是一种面向对象的编程语言,它的...

    类的方法练习

    9. 类对象:一个Java类可以创建多个对象,每个对象有自己的存储空间,可以独立初始化。选项B和C是错误的,选项D(以上都无)表示没有正确答案。 10. 包引用:要使用特定包中的类,需要在程序顶部使用`import`关键字...

    Labview面向对象编程

    4. **类与对象**:在Labview中,类是对象的蓝图,定义了对象的属性和方法。对象是类的实例,具有类所定义的特征和行为。创建类后,通过实例化过程可以生成对象,每个对象都有自己的属性值。 5. **事件驱动编程**:...

    面向对象方法学试卷17套

    了解如何定义类,包括属性(数据成员)和方法(成员函数),是掌握面向对象编程的关键。 2. **封装**:封装是面向对象的核心特性之一,它允许我们将数据和操作这些数据的方法绑定在一起,隐藏内部实现细节,对外...

    面向对象2练习题.doc

    * Java 语言基础:类的定义、构造方法、成员变量、成员方法 2. 银行账户功能模拟: * 属性:账号、储户姓名、地址、存款余额、最小余额 * 方法:存款、取款、查询 知识点: * 面向对象编程基本概念:类、对象、...

    C++_3_类与对象.docx

    缺省构造函数是没有参数的构造函数,而拷贝构造函数是一种特殊的构造函数,接收一个本类对象的引用,用于创建新对象并复制已有对象的状态。 4. 析构函数:对象生命周期结束时自动调用,负责清理对象资源。 5. 静态...

    Java面向对象程序设计 实验题

    类是创建对象的蓝图,定义了对象的状态(属性)和行为(方法)。在Java中,使用`class`关键字来声明一个类。对象则是类的实例,具有类定义的属性和方法。例如,你可以定义一个名为`Person`的类,包含`name`、`age`...

    php面向对象(类)教程

    面向对象编程是一种编程范式,它通过类和对象来组织代码,强调数据和操作数据的方法的封装。在PHP5及以上版本中,对OOP的支持大大增强,使得开发者可以构建更加结构化和可维护的程序。 1. **类(Class)**:类是...

Global site tag (gtag.js) - Google Analytics