2007年2月9日星期五

面向对象程序员 JavaScript指南

JavaScript不是Java
  Java和JavaScript的名字中蕴含着的是大量的市场考虑,而实质相对很对。JavaScript由“livescript”改名而来,是Netscape市场部在最后一刻决定的,现在这个名字已经被广与为接受。与一般的理解相反,JavaScript并不源自C系列语言,它的思想大多来源于类似Scheme和Self的函数式语言(functional language),与Python也有很多共同之处。很遗憾,它的名字以Java来命名,从语法样式上很像Java。在一些场合,它的行为与Java相同;但是在其他很多场合,它的行为与Java并不相同。
JavaScript的关键特征及其含义
特征含义
变量是弱类型的(loosely typed)变更仅仅声明为变量,而不是整数、字符串或者特定类的对象,在JavaScript中,给同一个变量分配不同的类型是合法的
代码是动态解释的与需要预编译的语言(例如Java、C、C#)相比,在运行时,JavaScript代码以文本形式保存并且在程序运行时解释为机器指令,Web网站的用户通常可以看到Ajax应用的源代码。而且,这使得通过其他代码动态生成代码成为了可能,而无需求助于特殊的字节码生成器
JavaScript函数是正常对象Java对象的方法与拥有它的对象绑在一起,只能通过该对象来调用。JavaScript函数可以附加到对象上,使得它们的行为类似于方法。但是它们也可以在其他上下文中调用,并且在运行时附加到其他对象上
JavaScript对象是基于prototype(原型)的Java、C++或者C#对象有一种定义类型:超类、虚的越类(即接口)。这严格定义了它的功能。任何JavaScript对象仅仅是一个对象,它仅仅是一个化装的关联数组。prototype在JavaScript中可以用来模拟Java风格的类型,但这只是表面相似

  这些特征使JavaScript能够以不同的方式来使用。同时也为很多古怪的技巧创造了机会,这些技巧值得经验丰富的Lisp黑客研究一番。如果你是一位聪明并受过专业训练的编码人员,可以利用这些技巧来做一些非凡的事情。甚至可能只用几百行代码就做到这些事情。另一方面。如果你自认为确实聪明并受过专业训练,那么你可能很快就会感到羞愧的。

JavaScript中的对象
  JavaScript不要求使用对象,甚至不要求使用函数。可以将JavaScript程序编写成一个文本流,当解释器读取它时直接执行。随着程序逐渐变大,函数和对象成为组织代码的极其有用的方式,我们建议两者都使用。
  创建一个新的JavaScript对象的最简单的方法是调用Object类内建的构造函数:

var myObject = new Object();

  这里对象myObject初始化为“空”,即它不包含属性或者方法。

创建即时对象
  前面曾以提到过,JavaScript对象本质上是一个关联数组,由以名称作为键的字段和方法组成。在上面修饰了一层类似于C语言的语法,使得它对于C系列的程序员更加熟悉,但是底层实现可以以其他方式使用。我们以每次一行的方式建立复杂的对象,对我们所认为的方式添加新的变量和函数。
  有两种以即时的方式创建对象的方法。第一种是直接使用JavaScript来创建对象。第二种是使用JSON来创建对象。
使用JavaScript语句
  在复杂的代码中,我们可能希望给一些对象属性赋值。JavaScript对象属性是可读/可写的,可以使用 = 操作符来赋值。我们将一个属性添加到刚才创建的简单对象上:

myObject.shoeSize = "12";

  在面向对象语言中,我们需要定义一个类来声明属性shoeSize,否则就会出现编译错误。对于JavaScript这是不必要的。事实上,仅仅为了强调对象类似于数组的本性,我们也可以使用数组的语法来引用属性:

myObject["shoeSize"] = "12";

  这种写法除了一个优点(即数组索引是一个JavaScript表达式,这提供了一种运行时反射功能)以外,对于变通的使用来说显得很笨拙。
  可以给对象动态添加一个新的函数:

myObject.speakYourShoeSize = function(){
alert("shoe size : " + this.shoeSize);
}

  或者借用一个预先定义的函数:

function sayHello(){
alert("hello, my shoeSize is " + this.shoeSize);
}
...
myObject.sayHello = sayHello;

  当分配预先定义的函数时,我们省略了圆括号。

myObject.sayHello = sayHello();

  那么将执行sayHello函数,并且用它的返回值来给myObject的sayHello属性赋值,在这里 null。
  可以将对象附加到其他对象上,从而创建复杂的数据模型:

var myLibrary = new Object();
myLibrary.books = new Array();
myLibrary.books[0] = new Object();
myLibrary.books[0].title = "Turnip Cultivation through the Ages";
myLibrary.books[0].authors = new Array();
var jim = new Object();
jim.name = "Jim Brown";
jim.age = 9;
myLibrary.books[0].authors[0] = jim;


使用JSON
  JSON是语言的一个核心特征,它提供了一种创建数组和对象图(object graph)的简单机制。
  JavaScript有一个内建的Array类,可以使用new关健字初始化:

myLibrary.books = new Array()

  数组有按照数字来分配的值,非常像传统的C或Java数组。

myLibrary.book[4] = somePredefineBook;

  数组也可以使用一个键值来关联,像是Java的Map或者Python的Dictionary。实际上这可以应用于任何JavaScript对象:

myLibrary.books["BestSeller"] = somePredefineBook;

  这种语法对于微调很有利,但是作为首选来创建一个大型的数组或者对象就很乏味了。创建一个数字索引的数组的快捷方法是使用方括号,将所有的成员写成一个用逗号分隔的值的列表。

myLibrary.books = {predefineBook1,predefineBook2,predefineBook3};

  为了创建JavaScript对象,我们使用花括号,将每个值写成“键:值”对的形式:

myLibrary.books = {
bestSeller : predefineBook1,
cookbok : predefineBook2,
spaceFiller : predefineBook3
};

  在两种符号中,会忽略额外的空白。键的内部可以有空白,可以在JSON符号中使用引号来引用。

"Best Seller" : predefineBook1

  可以通过嵌套JSON符号来创建复杂对象层次的单行定义:

var myLibrary = {
location : "my house",
keywords : {"root vegetables","turnip","tedium"},
books : [
{
title : "Turnip Cultivation through the Ages",
authors : {
{name: "Jim Brown", age: 9},
{name: "Dick Turnip", age: 312 }
},
publicationData : "long ago"
},
{
title : "Turnip Cultivation through the Ages, vol. 2",
authors : [
{name: "Jim Brown", age: 35}
],
publicationData : new Date(1605,11,05)
}
]
};

  给myLibrary对象分配了3个属性,location是一个简单字符串,keywords是一个按数字索引的字符串列表,books是一个按数字索引的对象列表,每个对象有标题(字符串)、发布日期(代表JavaScript Data对象的字符串)和作者列表(数组)。每个作者由name和age参数来代表。
通过内建的JavaScript Object和Array类以及JSON符号,可以创建我们喜欢的任意复杂的对象层次,我们不再需要其他任何东西。JavaScript也提供了创建对象的方法,为面向对象程序员提供了令人舒适的类定义的相似性。

构造函数、类和原型
  在面向对象编程中,我们通常使用希望实望实例化的类的声明来创建对象。Java和JavaScript都支持new关键字,允许我们创建预先定义类别的对象的实例,在这里两者是相似的。
在Java中,所有的东西都是一个对象,最后都继承自java.lang.Object类。java虚拟机对于类、字段和方法具有内建的理解。
  JavaScript也有对象和类的概念,但是没有内建继承的概念。事实上,每个JavaScript对象是相同基类(一个有能力在运行时将成员字段和函数与自己绑在一起的类)的实例。所以,有可能在运行时给对象分配任意的属性。

MyJavaScriptObject.completelyNewProperty = "something";

  可以通过使用一个原型把这种完全自由的状态组织为低层次面向对象开发者更加熟悉的东西。当使用一个特定的函数来构造对象时,原型定义了将自动绑定在对象上的属性和函数。有可能编写基于对象的JavaScript,而不使用原型,但是当开发复杂的富客户端应用时,原型在一定程序上为面向对象开发都提供了他们非常想要的规律和熟悉感。
  在JavaScript中,我们就可以编写一些看起来与Java声明很相似的东西。

var myObj = new MyObject();

  但是我们没有定义类MyObject,而是定义了一个同名的函数。这里是一个简单的构造函数:

function MyObject(name,size){
this.name = name;
this.size = size;
}

  随后可以像这样来调用它:

var myObj = new MyObject("tiddles","7.5 meters");
alert("size of " + myObj.name + " is " + myObj.size);

  在构造函数中,设置为this的属性的任何东西随后都可以作为对象的一个成员来使用。我们也许还希望初始化对alert()的调用,这样tiddlers可以自己负责告诉我们它有多大。一种通常的习惯做法是在构造函数中声明这个函数。

function MyObject(name,size){
this.name = name;
this.size = size;
this.tellSize = function(){
alert("size of " + this.name + " is " + this.size);
}
}
var myObj = new Object("tiddles","7.5 meters");
myObj.tellSize();

  这段代码可以工作,但是在两个方面不尽理想。首先,每当创建一个MyObject的实例时,我们都会创建一个新的函数,这样会存在内存泄漏问题。其次,我们在这里无意中创建一个闭包(closure),一旦在构造函数中包括了DOM节点,可以会遇到更加严重的问题。可是考察一下更加安全的替代品:原形(prototype)。
  原型是JavaScript对象的一个属性,在面向对象语言中没有对等物。函数和属性可以与构造函数的原型关联起来。然后原型和new关键字协同工作,当使用new调用函数时,函数原形的所有属性和方法会附加到结果对象上。

function MyObject(name,size){
this.name = name;
this.size = size;
}

MyObject.prototype.tellSize = function(){
alert("size of " + this.name + " is " + this.size);
}

var myObj = new MyObject("tiddles","7.5 meters");
myObj.tellSize();

  我们像以前那样声明了构造函数,然后向原型添加函数。当我们创建一个对象实例时,函数附加在上面。关键字this在运行时确定为对象的实例。
  在声明构造函数之后,我们才能引用原形,对象只继承那些在调用构造函数之前已经添加到原型上的东西。原型可以在两次调用构造函数之间进行修改,并且可附加任何东西到原型上:

MyObject.prototype.color = "red";
var obj1 = new MyObject();

MyObject.prototype.color = "blue";
MyObject.prototype.soundEffect = "bo000oing!!";
var obj2 = new MyObject();

  obj1将红色的且没有声音效果,obj2将是蓝色的且有着令人愉快的声音效果!以这种方式在运行时修改原型通常价值不大。知道发生这种事情是很有用的,但是使用原型来为JavaScript对象定义类似于类的行为,是一条安全和可靠的路径。

扩展内建类
  JavaScript用来嵌入在那些能够向脚本环境暴露自己本地对象的程序中。在Web浏览器中,DOM节点在IE浏览器中不能扩展,但是其他的核心类在所有主要的浏览器中都是可以扩展的。

Array.prototype.indexOf = function(obj){
var result = -1;
for(var i=0; i<this.length; i++){
if(this[i] == obj){
result = i;
break;
}
}
return result;
}

  为Array对象提供了一个额外的函数,它返回一个给定数组中的对象的数字索引,如查数组不包含这个对象就返回-1。我们可以在这个基础上更进一步,编写一个方便的方法来检查数组是否包含对象:

Array.prototype.contains = function(obj){
return(this.indexOf(obj) >= 0);
}

  然后添加另外一个函数,在经过了可选的重复检查之后添加新的成员:

Array.prototype.append = function(obj,nodup){
if(!(nodup && this.contains(obj))){
this[this.length] = obj;
}
}

  任何在这些函数声明之后创建的Array对象,无论是使用new操作符或者作为JSON表达式的一部分来创建,都能够使用这些函数。

var numbers = [1,2,3,4,5];
var got8 = numbers.contains(8);
numbers.append("cheese",true);

  对于用户定义对象的原型而言它们能够操作在多个对象的创建过程之间,但是我通常建议原型只在程序的开始修改一次,以便避免不必要的混乱,特别是当一个程序员团队共同工作的时候。

没有评论: