JavaScript定义类的几种方式总结
本文总结了JavaScript中定义类的六种主要方式,包括工厂方式、构造函数模式、原型模式、混合构造函数/原型模式、动态原型法以及混合工厂方式。重点解析了各模式的实现原理、优缺点及适用场景,指出混合构造函数/原型模式是目前最广泛使用的方式,并建议避免单独使用经典构造函数或原型模式。
JavaScript类定义概述
谈及面向对象,必然涉及类、对象、封装、继承与多态。《JavaScript高级程序设计》(人民邮电出版社,曹力、张欣译,英文原名:Professional JavaScript for Web Developers)对此有详尽阐述。下文将梳理JavaScript中定义类的多种途径。
工厂方式
1.工厂方式
在JavaScript中,自定义类与对象是必备技能。我们知道,对象的属性可在创建后动态添加,例如以下代码:
代码如下:
<script type="text/javascript">
//定义
var oCar = new Object();
oCar.color = "red";
oCar.doors = 4;
oCar.showColor = function() {
alert(this.color);
}
//调用
oCar.showColor();
</script>使用oCar对象十分简便,但若需创建多个Car实例,可通过函数封装上述代码来实现:
代码如下:
<script type="text/javascript">
//定义
function createCar() {
var oCar = new Object();
oCar.color = "red";
oCar.doors = 4;
oCar.showColor = function() {
alert(this.color);
}
return oCar;
}
//调用
var ocar1 = createCar();
var ocar2 = createCar();
ocar1.color = "black";
ocar1.showColor();
ocar2.showColor();
</script>需知,JavaScript对象默认成员属性均为public。此法称为工厂方式,即创建并返回特定类型对象的工厂。
此举颇具趣味,但在面向对象编程中,我们常使用的对象创建语句为:
Car car=new Car();
鉴于new关键字已深入人心,上述定义方式略显别扭,且每次调用均新建属性及函数,功能上并不实用。接下来探讨构造函数的定义形式。
构造函数模式
2.构造函数
此法看似与工厂函数相似,具体表现如下:
代码如下:
<script type="text/javascript">
//定义
function Car(color, doors) {
this.color = color;
this.doors = doors;
this.showColor = function() {
alert(this.color);
};
}
//调用
var car1 = new Car("red", 4);
var car2 = new Car("blue", 4);
car1.showColor();
car2.showColor();
</script>效果显著,差异明显。构造函数内部利用this关键字创建对象,配合new运算符,令人倍感亲切。然而存在缺陷:每次new对象时均创建所有属性及函数,导致多个对象完全独立。定义类的初衷在于共享方法与数据,但car1与car2各自拥有独立的属性与函数,至少应共享方法。这正是原型方式的优势所在。
原型方式
3.原型方式
利用对象的prototype属性,可将其视为创建新对象所依赖的原型。方法如下:
代码如下:
<script type="text/javascript">
//定义
function Car() {
};
Car.prototype.color = "red";
Car.prototype.doors = 4;
Car.prototype.drivers = new Array("Tom", "Jerry");
Car.prototype.showColor = function() {
alert(this.color);
}
//调用:
var car1 = new Car();
var car2 = new Car();
car1.showColor();
car2.showColor();
alert(car1.drivers);
car1.drivers.push("stephen");
alert(car1.drivers); //结果:Tom,Jerry,stephen
alert(car2.drivers); //结果:Tom,Jerry,stephen
//可用json方式简化prototype的定义:
Car.prototype =
{
color: "red",
doors: 4,
drivers: ["Tom", "Jerry",'safdad'],
showColor: function() {
alert(this.color);
}
}
</script>首先,该段代码的构造函数内无任何代码,随后通过对象的prototype属性添加属性定义Car对象的属性。此法虽好,但问题在于Car对象指向Array指针,两个Car对象均指向同一Array数组。当car1改变属性对象的引用(数组Array)时,car2也随之改变,这是不被允许的。
此外,原型无法携带任何初始化参数,导致构造函数无法正常初始化。需采用另一种方式解决:混合的构造函数/原型模式。
混合构造函数/原型模式
4. 混合的构造函数/原型模式
联合使用构造函数和原型方式,定义类便十分便捷。
代码如下:
<script type="text/javascript">
//定义
function Car(color,doors)
{
this.color=color;
this.doors=doors;
this.drivers=new Array("Tom","Jerry");
}
Car.prototype.showColor=function(){
alert(this.color);
}
//调用:
var car1=new Car('red',4);
var car2=new Car('blue',4);
car1.showColor();
car2.showColor();
alert(car1.drivers);
car1.drivers.push("stephen");
alert(car1.drivers); //结果:Tom,Jerry,stephen
alert(car2.drivers); //结果:Tom,Jerry
alert(car1 instanceof Car);
</script>该方法将属性置于内部定义,方法置于外部利用prototype定义。此法解决了第三种方法的问题。
此法虽颇为友好,但相较于Java语法,仍显不和谐,略显凌乱。对于C++而言,并无如此繁琐之感,然而开发C++的研发人员通常较少涉及JavaScript。对于J2EE研发人员而言,此法总觉别扭,似非友好封装,仅视觉效果欠佳。若欲达视觉封装效果且兼具此法功能,个人以为较为麻烦,即动态原型法。
动态原型法
5.动态原型
对于习惯使用其他语言的开发者,混合的构造函数/原型方式显得不那么和谐。毕竟,定义类时,多数面向对象语言均对属性和方法进行了视觉封装。考虑以下C#类:
代码如下:
class Car //class
{
public string color = "red";
public int doors = 4;
public int mpg = 23;
public Car(string color, int doors, int mpg) //constructor
{
this.color = color;
this.doors = doors;
this.mpg = mpg;
}
public void showColor() //method
{
Console.WriteLine(this.color);
}
}C#很好地打包了Car类的所有属性和方法,见代码即知功能,定义了对象信息。批评混合的构造函数/原型方式者认为,在构造函数内存中找属性、外部找方法的做法不合逻辑。因此,他们设计了动态原型方法,以提供更友好的编码风格。
动态原型方法的基本思路与混合的构造函数/原型方式相同,即在构造函数内定义非函数属性,函数属性则利用原型属性定义。唯一区别在于赋予对象方法的位置。以下是用动态原型方法重写的Car类:
代码如下:
<script type="text/javascript">
//定义
function Car() {
this.color = "red";
this.doors = 4;
this.drivers = new Array("Tom", "Jerry");
if (typeof Car._initialized == "undefined") {
Car.prototype.showColor = function() {
alert(this.color);
}
//............
}
//最后定义
Car._initialized = true;
}
</script>在检查typeof Car._initialized是否等于"undefined"之前,构造函数未发生变化。此行代码是动态原型方法的核心。若该值未定义,构造函数将用原型方式继续定义对象的方法,并将Car._initialized设为true。若该值已定义(值为true时,typeof值为Boolean),则不再创建该方法。简言之,该方法使用标志(_initialized)判断是否已给原型赋予任何方法。该方法仅创建并赋值一次,为取悦传统OOP开发者,此代码看起来更像其他语言中的类定义。
混合工厂方式
6 混合工厂方式
此法通常为无法应用前一种方式时的变通手段。其目的是创建假构造函数,仅返回另一种对象的新实例。此代码看似与工厂函数极为相似:
代码如下:
function Car() {
var oTempCar = new Object();
oTempCar.color="red";
oTempCar.doors=4;
oTempCar.mpg=23;
oTempCar.showColor = function() {
alert(this.color);
}
return oTempCar;
}与经典方式不同,此法使用new运算符,使其看起来像真正的构造函数:
var oCar = new Car();
由于在Car()构造函数内部调用了new运算符,故将忽略第二个new运算符(位于构造函数之外)。在构造函数内部创建的对象被传递回变量var。此法在对象方法的内部管理方面与经典方式存在相同问题。强烈建议:除非万不得已(请参阅第15章),否则避免使用此法。
总结与建议
总结:(采用哪种方式) 目前使用最广泛的是混合的构造函数/原型方式。此外,动态原型方法也很流行,在功能上与构造函数/原型方式等价。可采用这两种方式中的任何一种。不过不要单独使用经典的构造函数或原型方式,因为这样会给代码引入问题。
代码如下:
//ps
//static class (1:function)
var CarCollection = new function() {
var _carCollection = new Array(); //global,private
this.Add = function(objCar) {
alert('Add');
}
this.Get = function(carid) {
alert('Get');
}
}
//static class (2:json)
var Car = {
color: 'red',
doors: 4,
showColor: function() { alert(this.color); }
}
Car.showColor();JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09
JS使用constructor判断数据类型可靠吗
2026-11-05 13:49:41
