JavaScript定义类的几种方式总结

作者:函数茶馆时间:2026-10-27 20:57:14浏览:83

本文总结了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();
作者最新文章