用面向对象技术创建先进的web应用

本文原文地址:http://msdn.microsoft.com/en-us/magazine/cc163419.aspx

翻译如下:

             使用面向对象技术创建先进的web应用程序

作者:Ray Djajadinata

     不久我面试了一位在开发web应用方面拥有五年编程经验的开发者,她使用javascript已有4年半的时间的了,自认为在javascript方面拥有很强的技能。随后我发现她对javascript所知甚少,但我并没有因此。实际上,javascript就是这样有趣,它很容易使人们产生自己很精通它的错觉,仅仅是因为他们熟悉C/C++或C#语言,或凭着自己先前的编程经验

      从某种程度上来说,该假设并不是毫无根据的。我们很容易使用javascript来做些简单的事情。入门的门槛很低,你并不需要知道多少东西,就能够开始用它编写代码,甚至一个毫无编程经验的人,就能够通过几个小时的学习为主页写一个有用的脚本。

     实际上,我一直仅凭参考MSDN上关于DHTML的内容以及自己的C++/C#的编程经验,利用自己对javascript的一知半解过活,当我在实际工作做有关AJAX应用的时候,我才意识的我对javascript理解是多么肤浅。新的web应用复杂性以及交互性的特点要求我们需用完全不同的方法来编写javascript代码。这些才是真正javascript应用程序。我们在编写一次性脚本时一直采用的方法已完全不再有效。

    为了使代码库便于管理和维护,面向对象程序设计方式在许多javascript库中非常流行。JavaScript支持面向对象程序设计,但跟Microsoft®.NET Framework中的C++/C#或Visual Basic 的实现方式有很大的不同。所以对那些一直使用这些语言的开发者来说,一开始在javascript中使用面向对象技术的时候感到很奇怪,不适应。我写这篇文章的目的就是深入的讨论javascript是怎样的支持面向对象的,你怎么在javascript中怎么有效的做一些面向对象的开发。我们一起来谈论这个话题吧。

   对象—字典

在C++ 或C#中,当我们谈论对象的时候,我们指的是类或结构体的实例。对象包含许多不同的属性和方法,它通过哪一个类实例化的,然而在javascript中并不是这样的。在Javascript中,对象仅仅是名称/值 的集合。你可以把javascript中对象想像为拥有字符串值的的字典。我们可以使用"."操作符或“[]”操作符("[ ]"很像查字典的时候)得到或设置一个对象的属性。

代码一与代码二功能是一样的

var userObject = new Object();

userObject.lastLoginTime = new Date();

alert(userObject.lastLoginTime);
代码二

var userObject = {}; // equivalent to new Object()

userObject["lastLoginTime"] = new Date();

alert(userObject["lastLoginTime"]);

我们也可以如下的方式定义在userObject 中定义lastLoginTime属性

var userObject = { "lastLoginTime": new Date() };

alert(userObject.lastLoginTime);

我们能够感觉到它和c#3.0 对象初始化化好相似。从代码二和代码三中,我们可以看出实例化userObject的方式与Pthyon语言中创建字典的语法好相似。

   唯一的不同点是,javascript 对象/字典只接受字符键,而不像python那样接受哈希对象。上面的例子也显示出了javascript对象比C++/C#对象拥有更多的灵活性从代码一,代码二可以看出,lastLoginTime并不需要事先声明,假如对象userObject没有该属性时,该属性会被添加到userObject对象中。当你意识到javascript对象仅是一个字典,这看上去并不惊讶。我们可以在任何时间往字典中增加键值。我们现在知道怎么去创建的对象的属性了。那对象中的方法我们该怎么去创建呢。javascript语言不同于C++/C#,为了能够理解javascript对象中方法,我们需要更进一步的认识javascript中的函数。

JavaScript函数是第一类函数

 在许多程序语言中,函数和对象是无放在一起比较的。然而在javascript中,它们之间的概念很模糊,在javascript中函数是一个关联可执行代码的对象。看看下面代码中的函数:

function func(x) {

    alert(x);

}

func("blah");
这是我们经常定义函数的方式,你也可以按如下的方式定义该函数,下面的方式定义个匿名函数,并把它赋给了变量func。
var func = function(x) {

    alert(x);

};

func("blah2");
甚至可以安照如下方式,使用Function构造器

var func = new Function("x", "alert(x);");

func("blah3");
上面展示了函数仅仅是一个支持函数调用操作的对象。最后一种方式是使用Function 构造器来定义一个函数,这种方式并不使用广泛。但它很有趣,我们可以看出,函数的主体仅仅是Function 构造器的一个String 类型的参数。这意味着你可以在运行期间构造任意的函数。

为了进一步的演示 函数就是一个对象,我们可以为其它对象那么为函数增加属性。代码如下

function sayHi(x) {

    alert("Hi, " + x + "!");

}

sayHi.text = "Hello World!";

sayHi["text2"] = "Hello World... again.";
作为一个对象, 函数也能够被分配给一个变量,或作为一个形参给传递其它函数,或作为其它函数的返回值,或作为一个对象的属性,或作为一个数组中的元素,等等

代码片段一提供了如上的例子。

代码片段一  在javascript 中函数是一等函数

// assign an anonymous function to a variable

var greet = function(x) {

    alert("Hello, " + x);

};

greet("MSDN readers");



// passing a function as an argument to another

function square(x) {

    return x * x;

}

function operateOn(num, func) {

    return func(num);

}

// displays 256

alert(operateOn(16, square));



// functions as return values

function makeIncrementer() {

    return function(x) { return x + 1; };

}

var inc = makeIncrementer();

// displays 8

alert(inc(7));



// functions stored as array elements

var arr = [];

arr[0] = function(x) { return x * x; };

arr[1] = arr[0](2);

arr[2] = arr[0](arr[1]);

arr[3] = arr[0](arr[2]);

// displays 256

alert(arr[3]);



// functions as object properties

var obj = { "toString" : function() { return "This is an object."; } };

// calls obj.toString()

alert(obj);
考虑到这一点,为对象增加方法很容易,就是增加一个名字,然后把一个函数分配给该名字。所以我通过将匿名函数分配到各自的方法名字中来定义了三个方法。

如下所示

var myDog = {

    "name" : "Spot",

    "bark" : function() { alert("Woof!"); },

    "displayFullName" : function() {

        alert(this.name + " The Alpha Dog");

    },

    "chaseMrPostman" : function() { 

        // implementation beyond the scope of this article 

    }    

};

myDog.displayFullName(); 

myDog.bark(); // Woof!
对于C++/C#开发者来说,在displayFullName函数中使用this关键字感觉到很熟悉,它指的调用该方法的对象(那些使用Visual Basic开发也应该很熟悉,在Visual Basic 中 使用"Me"来代替)。所以上面的例子中,displayFullName 中this的值是myDog对象。this的值并不是不变的,当调用一个不同的对象时,this的值也将会改变,并指向另一个对象,如代码片段2所示。

代码片段2 “this”随着对象的改变而改变。

function displayQuote() {

    // the value of "this" will change; depends on 

    // which object it is called through

    alert(this.memorableQuote);    

}



var williamShakespeare = {

    "memorableQuote": "It is a wise father that knows his own child.", 

    "sayIt" : displayQuote

};



var markTwain = {

    "memorableQuote": "Golf is a good walk spoiled.", 

    "sayIt" : displayQuote

};



var oscarWilde = {

    "memorableQuote": "True friends stab you in the front." 

    // we can call the function displayQuote

    // as a method of oscarWilde without assigning it 

    // as oscarWilde’s method. 

    //"sayIt" : displayQuote

};



williamShakespeare.sayIt(); // true, true

markTwain.sayIt(); // he didn’t know where to play golf



// watch this, each function has a method call()

// that allows the function to be called as a 

// method of the object passed to call() as an

// argument. 

// this line below is equivalent to assigning

// displayQuote to sayIt, and calling oscarWilde.sayIt().

displayQuote.call(oscarWilde); // ouch!

在代码片段2中展示了另一种调用一个函数作为一个对象的方法的方式。我们要始终记住在javascript中,一个函数就是一个对象。每一个函数都有一个名叫call的方法,,该call方法使得该函数成为第一参数的中一个方法。即,无论哪一个对象作为第一个形参被传进call方法后,都将成为该函数内部的this。这在调用基类的构造器是,是一个有用的技术。随后我们将会看到。

需要记住的一件事,绝对不要调用那些归属于哪一个对象的函数,并且该函数还包含"this"关键字。如果你这样做,那么你会污染全局命名空间,因为在这个调用中,“this”对应着全局对象,这会在你的应该程序中造成严重的破环。例如,下面的脚本将会改变javascript全局函数 isNaN的行为。这种写法我绝对不推荐。

alert("NaN is NaN: " + isNaN(NaN));



function x() {

    this.isNaN = function() { 

        return "not anymore!";

    };

}

// alert!!! trampling the Global object!!!

x();



alert("NaN is NaN: " + isNaN(NaN));

到目前为止,我们已经看见了怎么创建一个对象,实现对象的属性和方法。如果我们仔细观察上面的代码,我们会发现方法和属性在对象定义的时候就已经写死了。如果我们需要对对象的创建有更多的控制,我们该怎么办? 例如,我们可能需要根据一些参数来计算某个对象属性的值,或者是在运行期间获得的值来初始化对象的属,或者我们不仅仅只创建对象的一个实例,这只是一个很普通的需求。

在C#中,我们使用类来实例化对象。但是JavaScript 不同于C#,它没有类的概念。在下面章节中,我们将会充分得利用函数作为构造器与new关键字在一起来实现实例化对象。

构造函数 但它不是类

对于javascript面向对象程序设计来说,有一件事很奇怪,JavaScript 并不像C# 或C++那样有类的概念。在C#中,但你声明一个对象。你可以像下面这样

Dog spot = new Dog();

你将会得到一个对象,它是Dog 类的一个实例,但是在Javascript中,没有类,最接近类的方式是像下面的方式定义一个构造函数。

function DogConstructor(name) {

    this.name = name;

    this.respondTo = function(name) {

        if(this.name == name) {

            alert("Woof");        

        }

    };

}



var spot = new DogConstructor("Spot");

spot.respondTo("Rover"); // nope

spot.respondTo("Spot"); // yeah!

好吧,上面发生了什么呢?我们看下面这行代码。

var spot = new DogConstructor("Spot");
“new”关键字所做的事情很简单。首先它创建一个新的空对象。然后该函数紧跟着被执行,该新的对象设置为该函数中this对应的值。换句化说,上面代码跟下面的类似。

// create an empty object

var spot = {}; 

// call the function as a method of the empty object

DogConstructor.call(spot, "Spot");

我们从DogConstructor可以看出,调用该函数来初始化该对象,函数中的this关键字对应着调用期间的对象。这样的话,我们可以有一种方式来为对象创建一个模版。

无论我什么时候需要创建一个对象,我们只需要调用new关键字和构造函数,这样我们得到一个初始化的对象。看起来与c++/C#的类很相似,是吧! 实际上,在javascript语言中,构造函数的名字就是类似于c++/C#中类的名字。在上面的例子中,我们为该构造函数取名为Dog。

// Think of this as class Dog

function Dog(name) {

    // instance variable 

    this.name = name;

    // instance method? Hmmm...

    this.respondTo = function(name) {

        if(this.name == name) {

            alert("Woof");        

        }

    };

}



var spot = new Dog("Spot");
在上面的例子中,我定义了一个名叫"name"的实例变量。这样的话,每一个使用Dog作为构造函数的对象都拥有实例变量name 的副本,这正是我们所希望的。每一个对象都需要拥有属性来保存自己的状态。但是当我们看下一行时,我们会发现每一个Dog的实例都拥有respondTo 方法的副本,这实在是一种浪费,我们只需要一个在所有对象实例中都能够共享的respondTo方法。我们可以通过在Dog函数外面定义respondTo来实现这一个目标。代码如下所示。
function respondTo() {

    // respondTo definition

}



function Dog(name) {

    this.name = name;

    // attached this function as a method of the object

    this.respondTo = respondTo;

}
这样的话,Dog的所有实例仅仅共享respondTo方法的一个实例。但是这种写法也有它的不足之处,假如方法很多的话,代码会变的难以维护。你的代码中会参杂着很多全局变量,尤其是你拥有很多"类"的时候,如果它们又拥有类似的方法名称,那会变得更糟糕。有一种更好的方法来解决该难题,那就是使用原型对象。下面的章节中,我们一起来讨论该话题

原型(Prototypes)

原型对象在Javascript面向对象编程是一个很重要的内容。在JavaScript中有一个这样的概念,每一个对象都是作为一个已经存在的对象(原型对象)的副本的而创建的。原型对象的所有属性和方法都够被该原型构造器所创建的对象拥有。当你按如下的方式创建一个新的Dog对象时,我们可以这样说,该对象中属性和方法都继承于原型。

var buddy = new Dog("Buddy");
buddy对象从自己原型中继承了属性和方法,但我们无法从上面的一行代码中得知该原型从哪里来。实际上,对象buddy的原型(prototyoe)来自构造函数的一个属性(换句话说,就是函数Dog)。

在javascript中,每一个函数都有一个名为“prototyoe”的属性,该属性对应着一个原型对象。该原型对象也有一个名为“constructor”的属性,"constructor"属性指向函数自己本身。这是一个循环引用。图一能够更好的展现该关系。

图一:每一个函数的原型都有一个constructor属性。

当一个函数(比如上面的Dog)使用new 操作符来创建一个对象的时候,被创建的对象就继承该函数原型的属性。从图一中,我们可以看到Dog.prototype对象有一个指向Dog函数的constructor属性。因此每一个Dog对象(即继承于Dog.prototype)都有一个指向Dog函数constructor属性。下面的代码将会证实这一点。构造函数,原型对象,以及由他们创建的对象之间的关系将在图二中展现。
 

var spot = new Dog("Spot");



// Dog.prototype is the prototype of spot

alert(Dog.prototype.isPrototypeOf(spot));



// spot inherits the constructor property

// from Dog.prototype

alert(spot.constructor == Dog.prototype.constructor);

alert(spot.constructor == Dog);



// But constructor property doesn’t belong

// to spot. The line below displays "false"

alert(spot.hasOwnProperty("constructor"));



// The constructor property belongs to Dog.prototype

// The line below displays "true"

alert(Dog.prototype.hasOwnProperty("constructor"));


图2 实例继承于原型


我们当中的一些人可能已经发现了这样的一个现象,我们在上面的代码中调用了hasOwnProperty 和isPrototypeOf方法,但这些方法来自哪里呢?它们并来源与Dog.prototype。事实上,像toString,toLoaclStrng ,valueOf这些方法,我们都可以通过Dog.prototype或者Dog的实例来调用,但是这些方法中任何一个都不来源于Dog.prototype。正如.Net Framework 有System.Object作为所有类的基类一样,JavaScript中有Ojbect.prototype作为所有原型的的终极原型(Object.prototype的原型为null)。

在这个例子中,记住Dog.prototype 是一个对象。它通过调用Ojbect 构造函数来创建,但这是不可见的。

Dog.prototype = new Object();
就像Dog的实例继承于Dog.prototype,Dog.prototype继承于Object.prototype。这使得Dog的所有实例都继承了Object.prototype的方法和属性。
每一个JavaScript对象都继承了一个原型链,所有的原型都终止于Object.prototype。目前我们看到的继承都是活动对象之间的继承,这不同于类之间的继承(声明的时候实现继承),显然,JavaScript中的继承要灵活的多。我可以简单的描述一下:当我们使用某一个对象的属性/方法时,JavaScript将会检查在这个对象中是否定义了该属性/方法,如果没有,该对象的原型将会被检查是否定义了该属性/方法,以此类推,一直检测到Object.prototype。图三描述这个解析过程。

图三:解析toString()方法的原型链

JavaScript动态的解析属性和方法调用的方式会导致一些后果:改变一个原型对象很容易在继承于该原型的对象中显示出来。如果我们在一个对象中定义了属性/方法 X 。该对象原型中的相同名字属性/方法将会被隐藏。例如,我们可以在Dog.prototype中定义一个toString方法来覆盖Object.prototype中的toString方法。改变只是单向的,从原型对象到它派生的对象。

下面的代码向我们显示了这种情况。下面的代码也解决了我们早先遇到的难题-----怎么解决不必要的方法。我们可以把一个方法放入原型对像中,使得所有继承该原型的对象能够共享该方法。在下面的例子中,rover对象和spot对象共享同一个getBreed方法,直到在spot对象中重写的getBreed方法(此处原文有错处)。这样sopt对象拥有了自己的getBreed()f方法,但是rover对象和继承于GreatDane的对象仍然共享由GreateDane.prototype对象创建的getBreed方法。

从原型继承

function GreatDane() { }



var rover = new GreatDane();

var spot = new GreatDane();



GreatDane.prototype.getBreed = function() {

    return "Great Dane";

};



// Works, even though at this point

// rover and spot are already created.

alert(rover.getBreed());



// this hides getBreed() in GreatDane.prototype

spot.getBreed = function() {

    return "Little Great Dane";

};

alert(spot.getBreed()); 



// but of course, the change to getBreed 

// doesn’t propagate back to GreatDane.prototype

// and other objects inheriting from it,

// it only happens in the spot object

alert(rover.getBreed());

静态方法和属性

      有时候我们需要与类联系在一起的属性和方法,即静态属性和静态方法。JavaScript使的这个变得容易,因为函数就是对象,并且它的属性和方法能够随时添加。因为在JavaScript中 构造函数代表一个类,我们可以按照如下的方式向构造函数中添加静态方法和静态对象。

function DateTime() { }



    // set static method now()

    DateTime.now = function() {

        return new Date();

    };



    alert(DateTime.now());
在JavaScript中调用静态方法的语法跟C#中的是一样的。这并不感到意外,因为构造函数的名字实际上和类的名字是一样的。现在我们知道了类,知道创建共有属性和方法,知道怎么创建的静态属性和和方法,我们还缺啥呢?对了,我们还需要私有属性,但是JavaScript并不支持私有属性。一个对象的所有属性和方法都够被访问,解决方法还是有的,但是我们必须理解闭包的概念

 闭包

我并不是自愿学习JavaScript的,我不得不硬着头皮去学习它,不然在做一个ajax应用程序的时候,我会感到很被动。我自认为自己的水平已经很高了。当我改变自己最初的看法时,我发现JavaScript实际上是一门强大的,善于表达,简洁的语言。它甚至拥有目前许多流行语言刚开始支持的特性。

JavaScript最大的特点之一是它支持闭包。在C#中通过匿名方法来实现闭包。闭包是一种运行现象:当一个内部函数(在C#中,内部匿名方法)与外部函数中的局部变量绑定起来时会产生闭包。很显然,这并没有多大的意义,除非该内部函数和外部的某些变量关联起来。举一个例子会显的更清晰点。

假设我们要过滤一个数字序列,就是大于100的数字能够通过,其余的被过滤掉,我们可以按如下方式来实现。

function filter(pred, arr) {

    var len = arr.length;

    var filtered = []; // shorter version of new Array();

    // iterate through every element in the array...

    for(var i = 0; i < len; i++) {

        var val = arr[i];

        // if the element satisfies the predicate let it through

        if(pred(val)) {

            filtered.push(val);

        }

    }

    return filtered;

}



var someRandomNumbers = [12, 32, 1, 3, 2, 2, 234, 236, 632,7, 8];

var numbersGreaterThan100 = filter(

    function(x) { return (x > 100) ? true : false; }, 

    someRandomNumbers);



// displays 234, 236, 632

alert(numbersGreaterThan100);
我们现在想要创建不同的过滤准则,比如大于300的才能通过,我们可能像这样改。

var greaterThan300 = filter(

    function(x) { return (x > 300) ? true : false; }, 

    someRandomNumbers);
然后,也许需要筛选大于 50、25、10、600 如此等等的数字,但作为一个聪明人,您会发现它们全部都有相同的谓词“greater than”,只有数字不同。因此,可以用类似下面的函数分开各个数字:
function makeGreaterThanPredicate(lowerBound) {
    return function(numberToCheck) {
        return (numberToCheck > lowerBound) ? true : false;
    };
}
这样,您就可以编写以下代码:
var greaterThan10 = makeGreaterThanPredicate(10);
var greaterThan100 = makeGreaterThanPredicate(100);
alert(filter(greaterThan10, someRandomNumbers));
alert(filter(greaterThan100, someRandomNumbers));


通过观察函数 makeGreaterThanPredicate 返回的内部匿名函数,可以发现,该匿名内部函数使用 lowerBound,后者是传递给 makeGreaterThanPredicate 的参数。按照作用域的一般规则,当 makeGreaterThanPredicate 退出时,lowerBound 超出了作用域!但在这里,内部匿名函数仍然携带 lowerBound,甚至在 makeGreaterThanPredicate 退出之后的很长时间内仍然如此。这就是我们所说的闭包:因为内部函数关闭了定义它的环境(即外部函数的参数和本地变量)。
开始可能感觉不到闭包的功能很强大。但如果应用恰当,它们就可以非常有创造性地帮您将想法转换成代码,这个过程非常有趣。在 JavaScript 中,闭包最有趣的用途之一是模拟类的私有变量。

模拟私有属性

现在介绍闭包如何帮助模拟私有成员。正常情况下,无法从函数以外访问函数内的本地变量。函数退出之后,由于各种实际原因,该本地变量将永远消失。但是,如果该本地变量被内部函数的闭包捕获,它就会生存下来。这一事实是模拟 JavaScript 私有属性的关键。假设有一个 Person 类:

function Person(name, age) {
    this.getName = function() { return name; };
    this.setName = function(newName) { name = newName; };
    this.getAge = function() { return age; };
    this.setAge = function(newAge) { age = newAge; };
}
参数 name 和 age 是构造函数 Person 中的局部变量。Person 返回时,name 和 age 应当永远消失。但是,它们被作为 Person 实例的方法而分配的四个内部函数捕获,实际上这会使 name 和 age 继续存在,但只能严格地通过这四个方法访问它们。因此,您可以:

var ray = new Person(“Ray”, 31);
alert(ray.getName());
alert(ray.getAge());
ray.setName(“Younger Ray”);
// Instant rejuvenation!
ray.setAge(22);
alert(ray.getName() + “ is now “ + ray.getAge() + 
      “ years old.”);
未在构造函数中初始化的私有成员可以成为构造函数的本地变量,如下所示:

function Person(name, age) {
    var occupation;
    this.getOccupation = function() { return occupation; };
    this.setOccupation = function(newOcc) { occupation = 
                         newOcc; };
  
    // accessors for name and age    
}
注意,这些私有成员与我们期望从 C# 中产生的私有成员略有不同。在 C# 中,类的公用方法可以访问它的私有成员。但在 JavaScript 中,只能通过在其闭包内拥有这些私有成员的方法来访问私有成员(由于这些方法不同于普通的公用方法,它们通常被称为特权方法)。因此,在 Person 的公用方法中,仍然必须通过私有成员的特权访问器方法才能访问私有成员:

Person.prototype.somePublicMethod = function() {
    // doesn’t work!
    // alert(this.name);
    // this one below works
    alert(this.getName());
};

从类继承

到这里,我们已经了解了构造函数和原型对象如何使您在 JavaScript 中模拟类。您已经看到,原型链可以确保所有对象都有 Object.prototype 的公用方法,以及如何使用闭包来模拟类的私有成员。但这里还缺少点什么。您尚未看到如何从类派生,这在 C# 中是每天必做的工作。遗憾的是,在 JavaScript 中从类继承并非像在 C# 中键入冒号即可继承那样简单,它需要进行更多操作。另一方面,JavaScript 非常灵活,可以有很多从类继承的方式。
例如,有一个基类 Pet,它有一个派生类 Dog,如图 四所示。这个在 JavaScript 中如何实现呢?Pet 类很容易。您已经看见如何实现它了:

图四
 

// class Pet
function Pet(name) {
    this.getName = function() { return name; };
    this.setName = function(newName) { name = newName; };
}

Pet.prototype.toString = function() {
    return “This pet’s name is: “ + this.getName();
};
// end of class Pet

var parrotty = new Pet(“Parrotty the Parrot”);
alert(parrotty);
现在,如何创建从 Pet 派生的类 Dog 呢?在图 4 中可以看到,Dog 有另一个属性 breed,它改写了 Pet 的 toString 方法(注意,JavaScript 的约定是方法和属性名称使用 camel 大小写,而不是在 C# 中建议的 Pascal 大小写)。下面的代码 显示如何这样做。

// class Dog : Pet 
// public Dog(string name, string breed)
function Dog(name, breed) {
    // think Dog : base(name) 
    Pet.call(this, name);
    this.getBreed = function() { return breed; };
    // Breed doesn’t change, obviously! It’s read only.
    // this.setBreed = function(newBreed) { name = newName; };
}

// this makes Dog.prototype inherits
// from Pet.prototype
Dog.prototype = new Pet();

// remember that Pet.prototype.constructor
// points to Pet. We want our Dog instances’
// constructor to point to Dog.
Dog.prototype.constructor = Dog;

// Now we override Pet.prototype.toString
Dog.prototype.toString = function() {
    return “This dog’s name is: “ + this.getName() + 
        “, and its breed is: “ + this.getBreed();
};
// end of class Dog

var dog = new Dog(“Buddy”, “Great Dane”);
// test the new toString()
alert(dog);

// Testing instanceof (similar to the is operator)
// (dog is Dog)? yes
alert(dog instanceof Dog);
// (dog is Pet)? yes
alert(dog instanceof Pet);
// (dog is Object)? yes
alert(dog instanceof Object);

模拟命名空间

在 C++ 和 C# 中,命名空间用于尽可能地减少名称冲突。例如,在 .NET Framework 中,命名空间有助于将 Microsoft.Build.Task.Message 类与 System.Messaging.Message 区分开来。JavaScript 没有任何特定语言功能来支持命名空间,但很容易使用对象来模拟命名空间。如果要创建一个 JavaScript 库,则可以将它们包装在命名空间内,而不需要定义全局函数和类,如下所示:

var MSDNMagNS = {};

MSDNMagNS.Pet = function(name) { // code here };
MSDNMagNS.Pet.prototype.toString = function() { // code };

var pet = new MSDNMagNS.Pet(“Yammer”);

命名空间的一个级别可能不是唯一的,因此我们可以创建嵌套的命名空间:

var MSDNMagNS = {};

// nested namespace "Examples"

MSDNMagNS.Examples = {}; 



MSDNMagNS.Examples.Pet = function(name) { // code };

MSDNMagNS.Examples.Pet.prototype.toString = function() { // code };



var pet = new MSDNMagNS.Examples.Pet("Yammer");
可以想象,键入这些冗长的嵌套命名空间会让人很累。 幸运的是,库用户可以很容易地为命名空间指定更短的别名:

// MSDNMagNS.Examples and Pet definition...

// think “using Eg = MSDNMagNS.Examples;” 
var Eg = MSDNMagNS.Examples;
var pet = new Eg.Pet(“Yammer”);
alert(pet);
如果看一下 Microsoft AJAX 库的源代码,就会发现库的作者使用了类似的技术来实现命名空间(请参阅静态方法 Type.registerNamespace 的实现)。有关详细信息,请参与侧栏“OOP 和 ASP.NET AJAX”。

应当这样编写 JavaScript 代码吗?

您已经看见 JavaScript 可以很好地支持面向对象的编程。尽管它是一种基于原型的语言,但它的灵活性和强大功能可以满足在其他流行语言中常见的基于类的编程风格。但问题是:是否应当这样编写 JavaScript 代码?在 JavaScript 中的编程方式是否应与 C# 或 C++ 中的编码方式相同?是否有更聪明的方式来模拟 JavaScript 中没有的功能?每种编程语言都各不相同,一种语言的最佳做法,对另一种语言而言则可能并非最佳。
在 JavaScript 中,您已看到对象继承对象(与类继承类不同)。因此,使用静态继承层次结构建立很多类的方式可能并不适合 JavaScript。也许,就像 Douglas Crockford 在他的文章Prototypal Inheritance in JavaScript 中说的那样,JavaScript 编程方式是建立原型对象,并使用下面的简单对象函数建立新的对象,而后者则继承原始对象:
function object(o) {
        function F() {}
        F.prototype = o;
        return new F();
    }
然后,由于 JavaScript 中的对象是可延展的,因此可以方便地在创建对象之后,根据需要用新字段和新方法增大对象。
这的确很好,但它不可否认的是,全世界大多数开发人员更熟悉基于类的编程。实际上,基于类的编程也会在这里出现。按照即将颁发的 ECMA-262 规范第 4 版(ECMA-262 是 JavaScript 的官方规范),JavaScript 2.0 将拥有真正的类。因此,JavaScript 正在发展成为基于类的语言。但是,数年之后 JavaScript 2.0 才可能会被广泛使用。同时,必须清楚当前的 JavaScript 完全可以用基于原型的风格和基于类的风格读取和写入 JavaScript 代码。

展望

随着交互式客户端 AJAX 应用程序的广泛使用,JavaScript 迅速成为 .NET 开发人员最重要的工具之一。但是,它的原型性质可能一开始会让更习惯诸如 C++、C# 或 Visual Basic 等语言的开发人员感到吃惊。我已发现我的 JavaScript 学习经历给予了我丰富的体验,虽然其中也有一些挫折。如果本文能使您的体验更加顺利,我会非常高兴,因为这正是我的目标。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值