【原文地址】 Classes in Jscript - Part I
【原文发表日期】 Monday, September 24, 2007 9:23 AM by don.raman
【原文首次发表在:[译作]Class in Jscript Part I】
我是Ritesh Parikh,最近刚转入Jscript Team作一名SDET(译注:我也不知道SDET是什么样的岗位:()。我刚接触Jscript时,我有很多的疑问。其中一个就是 Jscript是否支持面向对象?(译注:这里的Jscript是MS所实现的Javascript,由于MS的Jscript完全兼容于Javascript 1.5,并在此基础上,针对IE做了一些扩展。这个系列里,介绍的内容实际上是Javascript 1.5中对面向对象支持。因此这些内容适合于我们目前广泛意义上的Javascript)
通常情况下,一种语言要支持面向对象的能力,必须具备以下的能力:
l 一种将相关函数组织在一个模块中,并封装他们实现细节的机制。比如:类。
l 一种重用函数,广泛意义上的继承机制。
l 一种逻辑上做相同事情,但却有不同实现的机制。我们通常称之为多态机制。
JScript支持以上所有的机制。在这篇博客中,我将会介绍Jscript是如何支持定义一个类的。
Jscript并不支持一般意义上的“类”,这就决定了它与传统上的面向对象语言,如C++或者JAVA有很大的不同。Jscript不支持传统意义上的类继承,取而代之的是一种利用prototype技术来实现(模拟)类的继承。
如果Jscript不支持一般意义上的类,那它是如何来支持定义“类”,并创建实例的呢?这就是我这篇博客将要解释的内容。
Jscript中并不支持与传统语言一模一样的类的概念,但是它使用构造器函数(constructor function)和prototype对象来模拟“类”。
让我们先来定义一个这篇博客中将要用到的几个关键词:
类(class):在传统的面向对象语言中,它是定义了对象的结构,包括对象包括的属性,属性的类型和对象的操作方法。在Jscript中,并没有这种类型的定义。(译注:在Jscript中并没有直接支持定义这样的类)
构造器函数(Constuctor functions):能够被new关键字初始化的函数称为构造器函数或简称为构造器。它的工作就是初始化一个带任意属性的对象,这个对象在使用new关键字后被返回。(译注:我不也不清楚该如何翻译这个概念,它可以这样理解:它本身就是一个函数,同时它可以被认为是一个对象的构造器。)
Prototype对象:在Jscript中,除了Object.prototype,每个对象(类型层面上,不是实例)都有带有一个被称为prototype对象的引用。在prototype里的所有属性(和方法),都会出现在每个对象的实例中。(译注:理解prototype的概念,对理解Jscript中的继承和性能提升有很重要的意义。)
带着这些信息,我们来开始使用Jscript来实现第一个“类”。
我们要来定义一个带有带有高和宽的矩形,并且要提供一个计算其面积的area方法。
首先,我们要来定义一个类,但Jscript中不支持Class关键字。那我们该如何做呢?看下面的代码。
定义一个构造器函数来初始所需的数据:
function Rectangle (ht, wt) {
this.height = ht;
this.width=wt;
}
接下来是创建Rectangle对象
var rect1 = new Rectangle(5,10);
var rect2 = new Rectangle(2,4);
然后定义一个函数来计算Rectangle的面积
function computeArea(rObj) {
return rObj.height * rObj.width;
}
调用该函数来计算面积:
var rectArea = computeArea(rect1); //will return 50.
但是这样做就不是一个严格意义上的面向对象了。一种更好的办法是在Rectangle对象中调用一个函数来直接返回它的面积。在类中添加一个函数是可行的,加上这个函数的话,Rectangle的构造器变成了这样:
function Rectangle (ht, wt) {
this.height = ht;
this.width=wt;
this.area = function() { return this.height * this.width;}
}
然后我们就可以这样调用计算它的面积了:
var rect1Area = rect1.area(); // rect1.area() will return 50.
var rect2Area = rect2.area(); // rect2.area() will return 8.
但这样还不是一个最理想的面向对象的实现,还有更好的方式。在这里矩形对象中会有会个属性,height, width和area。height, width对于每个矩形对象实例来说,可能会有不同的值,但是area对于每个对象实例来说使用的都是相同的函数原形。这就意味着area方法对每个矩形对象实例来说是可以共享的,那我们为什么不那么做呢?
我们可以做这张图片中得知prototype对象源来。(译注:图?)
我们把area定义为Rectangle的一个属性:
Rectangle.prototype.area = function() {return this.height * this.width;}
rect1.area(); // will return 50.
rect2.area(); // will return 8.
从上面我们所做的可以总结出以下的一些信息:
构造器会为类提供一个名称,和初始化每一个类的实现的属性。Prototype对象与构造器有关,prototype对象里定义的任何属性都被每个由于构造器实例化的对象所继承。因此prototype对象是一个最理想地方来存储像area这个所有对象都相同的属性。(常量)
这样做具有以下一些优点:
所有相同的方法(一次定义所有实例共享)被定义在prototype对象中可以被所有的实例共享。它可以大大减小内存的开销。
当一个属性在对象实例被创建之后被加在prototype对象中。那么这个对象实例同样也会继承这些新加的属性。
尽管所有的对象都能顺利的从prototype对象中读取属性,但是却无法修改它们,为什么会这样呢?
如果通过rect1(实例)我们可以修改area属性,那么势必其它的对象实例(像rect2, rect3…)在调用area属性时也一样会受到影响。尽管如此,我们仍然可以在对象的prototype的生命周期内来修改它本身的属性,但不是在对象的实例上。当我们试图在对象的实例上修改prototype属性时,它的结果是在对象实例中动态的添加这个属性以覆盖prototype的相同属性。
这只是一个开始,在接下来的文章中,我会来讨论实例属性和静态属性(类属性),实体方法和静态方法(类方法)。此外,我们还会来研究一下数据是如何被封装和继承的。
希望你喜欢这篇博客。
Thanks,
Ritesh
SDET, JScript Team
阿不 译