站内搜索

 

精品内容

5.7 实现高级JavaScript技术

[ 来源:网络 | 作者: | 时间:2008-05-14 | 浏览: 人次 ]

我们假设本书的读者对JavaScript至少有基本的实践知识。如果要提供JavaScript的全面教程,这本身就需要一本完整的书才能讲清楚,所以在这里我们不打算详细介绍这种语言。相反,本节只是讨论JavaScript的一些可能鲜为人知的高级特性,并说明如何在你的Ajax开发中结合使用这些特性。

  我们先来简单地谈谈JavaScript的历史,以便你了解它原来是什么样子,又是怎么发展到今天的。Netscape的Brendan Eich于1995年开发了JavaScript。他的任务本来是开发一种方法,使得创建和维护Web网站的非专业Web设计人员能够更容易地使用Java applet。Eich认为,适当的选择是开发一种不需要编译器的弱类型语言。

最初,Eich开发的这个语言有过很多名字,不过,后来由于Java在市场上大获成功,因此借着这股东风,最后命名为JavaScript。JavaScript很快成为Web上最流行的脚本语言,这要归功于它的低门槛,另外还因为它能够把JavaScript脚本从一个页面复制到另一个页面。在JavaScript和Navigator DOM的早期修订版本基础上,产生了DOM Level 0标准,该标准将表单元素和图像定义为DOM元素的子元素。

  Microsoft迎头赶上,创建了自己的脚本语言VBScript。VBScript在功能上与JavaScript类似,但采用了类Visual Basic语法,而且只能用于IE。Microsoft还提供了JavaScript的一个实现JScript(现在已由ECMA标准化并称为ECMAScript)。尽管不同JavaScript的语法几乎是一样的,但不同浏览器上DOM实现却大相径庭,以至于几乎不可能创建跨浏览器的脚本。使用“最小公分母”方法得到的脚本通常只能完成最简单的任务。

到了1998年,Netscape开放了其浏览器的源代码,决定开始重写浏览器,并把重点放在遵循W3C标准上。那时,IE 5是W3C DOM和ECMAScript的最佳实现。开源版本的Netscape以Mozilla为名于2002年问世。由此开始,浏览器领域形成了一个趋势:越来越多的浏览器开始努力遵循W3C和ECMA维护的Web标准。如今,现代浏览器(如Firefox、Mozilla、Opera、Konqueror和Safari)都严格遵循Web标准,这就大大简化了编写跨浏览器的HTML和JavaScript等的任务。IE 6与1998年的IE 5并没有太大差别,它严格禁止了最不合标准的行为。

5.7.1 通过prototype属性建立面向对象的JavaScript

  JavaScript通过一种链接机制来支持继承,而不是通过完全面向对象语言(如Java)所支持的基于类的继承模型。每个JavaScript对象都有一个内置的属性,名为prototype。prototype属性保存着对另一个JavaScript对象的引用,这个对象作为当前对象的父对象。

  当通过点记法引用对象的一个函数或属性时,倘若对象上没有这个函数或属性,此时就会使用对象的prototype属性。当出现这种情况时,将检查对象prototype属性所引用的对象,查看是否有所请求的属性或函数。如果prototype属性引用的对象也没有所需的函数或属性,则会进一步检查这个对象(prototype属性引用的对象)的prototype属性,依次沿着链向上查找,直到找到所请求的函数或属性,或者到达链尾,如果已经到达链尾还没有找到,则返回undefined。从这个意义上讲,这种继承结构更应是一种“has a”关系,而不是“is a”关系。

  如果你习惯于基于类的继承机制,那么可能要花一些时间来熟悉这种prototype机制。prototype机制是动态的,可以根据需要在运行时配置,而无需重新编译。你可以只在需要时才向对象增加属性和函数,而且能动态地把单独的函数合并在一起,来创建动态、全能的对象。对prototype机制的这种高度动态性可谓褒贬不一,因为这种机制学习和应用起来很不容易,但是一旦正确地加以应用,这种机制则相当强大而且非常健壮。

这种动态性与基于类的继承机制中的多态概念异曲同工。两个对象可以有相同的属性和函数,但是函数方法(实现)可以完全不同,而且属性可以支持完全不同的数据类型。这种多态性使得JavaScript对象能够由其他脚本和函数以统一的方式处理。

  图5-15显示了实际的prototype继承机制。这个脚本定义了3类对象:Vehicle、Sports-

  Car和CementTruck。Vehicle是基类,另外两个类由此继承。Vehicle定义了两个属性:wheelCount和curbWeightInPounds,分别表示Vehicle的车轮数和总重量。JavaScript不支持抽象类的概念(抽象类不能实例化,只能由其他类扩展),因此,对于Vehicle基类,wheelCount默认为4,curbWeightInPounds默认为3 000。

 

 图5-15 Vehicle、SportsCar和CementTruck对象之间的关系

  注意,这个UML图展示了SportsCar和CementTruck对象覆盖了Vehicle的refuel和mainTasks函数,因为一般的Vehicle、SportsCar(赛车)和CementTruck(水泥车)会以不同的方式完成这些任务。SportsCar与Vehicle的车轮数相同,所以SportsCar的wheelCount属性不会覆盖Vehicle的wheelCount属性。CementTruck的车轮数和重量都超过了Vehicle,所以CementTruck的wheelCount和curbWeightInPounds属性要覆盖Vehicle的相应属性。

  代码清单5-2包含了定义这3个类的JavaScript代码。要特别注意如何在对象定义中对属性和函数附加prototype关键字,还要注意每个对象由一个构造函数定义,构造函数与对象类型同名。

  代码清单5-2 inheritanceViaPrototype.js

 /* Constructor function for the Vehicle object */
  function Vehicle() { }
  /* Standard properties of a Vehicle */
  Vehicle.prototype.wheelCount = 4;
  Vehicle.prototype.curbWeightInPounds = 4000;
  /* Function for refueling a Vehicle */
  Vehicle.prototype.refuel = function() {
  return "Refueling Vehicle with regular 87 octane gasoline";
  }
  /* Function for performing the main tasks of a Vehicle */
  Vehicle.prototype.mainTasks = function() {
  return "Driving to work, school, and the grocery store";
  }
  /* Constructor function for the SportsCar object */
  function SportsCar() { }
  /* SportsCar extends Vehicle */
  SportsCar.prototype = new Vehicle();
  /* SportsCar is lighter than Vehicle */
  SportsCar.prototype.curbWeightInPounds = 3000;
  /* SportsCar requires premium fuel */
  SportsCar.prototype.refuel = function() {
  return "Refueling SportsCar with premium 94 octane gasoline";
  }
  /* Function for performing the main tasks of a SportsCar */
  SportsCar.prototype.mainTasks = function() {
  return "Spirited driving, looking good, driving to the beach";
  }
  /* Constructor function for the CementTruck object */
  function CementTruck() { }
  /* CementTruck extends Vehicle */
  CementTruck.prototype = new Vehicle();
  /* CementTruck has 10 wheels and weighs 12,000 pounds*/
  CementTruck.prototype.wheelCount = 10;
  CementTruck.prototype.curbWeightInPounds = 12000;
  /* CementTruck refuels with diesel fuel */
  CementTruck.prototype.refuel = function() {
  return "Refueling CementTruck with diesel fuel";
  }
  /* Function for performing the main tasks of a SportsCar */
  CementTruck.prototype.mainTasks = function() {
  return "Arrive at construction site, extend boom, deliver cement";
  }

代码清单5-3是一个很小的Web页面,展示了这3个对象的继承机制。这个页面只包含3个按钮,每个按钮创建一个类型的对象(Vehicle、SportsCar或CementTruck),并把对象传递到describe函数。describe函数负责显示各个对象的属性值,以及对象函数的返回值。注意,describe方法并不知道它描述的对象是Vehicle、SportsCar,还是CementTruck,它只是认为这个对象有适当的属性和函数,并由这个对象返回自己的值。

  代码清单5-3 inheritanceViaPrototype.html

  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
  <title>JavaScript Inheritance via Prototype</title>
  <script type="text/javascript" src="inheritanceViaPrototype.js"></script>
  <script type="text/javaScript">
  function describe(vehicle) {
  var description = "";
  description = description + "Number of wheels: " + vehicle.wheelCount;
  description = description + "
Curb Weight: " + vehicle.curbWeightInPounds;
  description = description + "
Refueling Method: " + vehicle.refuel();
  description = description + "
Main Tasks: " + vehicle.mainTasks();
  alert(description);
  }
  function createVehicle() {
  var vehicle = new Vehicle();
  describe(vehicle);
  }
  function createSportsCar() {
  var sportsCar = new SportsCar();
  describe(sportsCar);
  }
  function createCementTruck() {
  var cementTruck = new CementTruck();
  describe(cementTruck);
  }
  </script>
  </head>
  <body>
  <h1>Examples of JavaScript Inheritance via the Prototype Method</h1>
  <br/><br/>
  <button onclick="createVehicle();">Create an instance of Vehicle</button>
  <br/><br/>
  <button onclick="createSportsCar();">Create an instance of SportsCar</button>
  <br/><br/>
  <button onclick="createCementTruck();">Create an instance of CementTruck</button>
  </body>
  </html>

分别创建3个对象,并用describe函数描述,结果如图5-16所示。

 

图5-16 创建Vehicle、SportsCar和CementTruck 对象并使用describe函数分别描述的结果

  5.7.2 私有属性和使用JavaScript的信息隐藏

  铁杆的面向对象设计支持者会注意到,当使用prototype方法向JavaScript对象增加属性和函数时,所增加的属性和函数都是公用的,所有其他对象都能访问。对于函数来说,这通常没有问题,因为大多数函数都确实应当提供给外部客户。但是对于属性,面向对象设计的支持者就会指出,公有属性违反了信息隐藏的概念,对象的属性应当是私有的,因此外部客户不能直接访问。外部客户只能通过公用可用的函数来访问对象的私有属性。

  对于JavaScript,同样有可能创建外部客户不能访问的私有属性,而只能通过对象的(公用)方法来访问,但这一点很少有人知道。Douglas Crockford[3]提出了一种在JavaScript中创建私有属性的方法。这种方法非常简单,总结如下:

  l  私有属性可以在构造函数中使用var关键字定义。

  l  私有属性只能由特权函数(privileged function)公用访问。特权函数就是在构造函数中使用this关键字定义的函数。外部客户可以访问特权函数,而且特权函数可以访问对象的私有属性。

  下面来考虑前一个示例中的Vehicle类。假设你想让wheelCount和curbWeightIn- Pounds属性是私有的,并只能通过公用方法访问。新的Vehicle对象如代码清单5-4所示。

  代码清单5-4 重写后的Vehicle对象

  function Vehicle() {
  var wheelCount = 4;
  var curbWeightInPounds = 4000;
  this.getWheelCount = function() {
  return wheelCount;
  }
  this.setWheelCount = function(count) {
  wheelCount = count;
  }
  this.getCurbWeightInPounds = function() {
  return curbWeightInPounds;
  }
  this.setCurbWeightInPounds = function(weight) {
  curbWeightInPounds = weight;
  }
  this.refuel = function() {
  return "Refueling Vehicle with regular 87 octane gasoline";
  }
  this.mainTasks = function() {
  return "Driving to work, school, and the grocery store";
  }
  }

注意,wheelCount和curbWeightInPounds属性都在构造函数中使用var关键字定义,这就使得这两个属性是私有属性。属性不再是公用的,如果想通过点记法访问wheelCount属性的值,如下:

var numberOfWheels = vehicle.wheelCount;

 

  就会返回undefined,而不是wheelCount实际的值。

  由于属性现在是私有的,因此需要提供能访问这些属性的公用函数。getWheelCount、setWheelCount、getCurbWeightInPounds和setCurbWeightInPounds函数就是作此使用的。现在Vehicle对象可以保证只能通过公用函数访问私有属性,因此满足了信息隐藏的概念。

  5.7.3 JavaScript中基于类的继承

  JavaScript中基于prototype的继承机制可以很好地工作,但是对于一些已经习惯于C++和Java等语言中基于类的继承机制的人来说,JavaScript的prototype继承机制不是一种自然的编程方法。如果你不想用基于prototype的继承,而想用一种基于类的继承方法,那就继续读下去吧。

  Netscape的Bob Clary[4]也提出了一个方法,它可以使一个对象使用一个通用的脚本从另一个对象继承属性和函数。这个脚本只是将“父”对象的属性和函数简单地复制到“子”对象。为此,我们将说明如何对脚本稍加修改,从而只是将子对象中不存在的属性和函数复制到子对象;这样一来,子对象中的函数就能覆盖父对象的函数。在两个对象之间创建继承关系的通用函数如下:

  function createInheritance(parent, child) {
  var property;
  for(property in parent) {
  if(!child[property]) {
  child[property] = parent[property];
  }
  }
  }

createInheritance函数有两个参数,父对象和子对象。这个函数只是迭代处理父对象的所有成员(成员就是属性或函数),如果某个成员在子对象中不存在,则复制到子对象。

  使用createInheritance函数相当简单:首先创建子对象的一个实例,然后使用createInheritance函数,为它传递子对象以及父对象的一个实例,如下:

  var child = new Child();
  createInheritance(new Parent(), child);

 

  父对象中有而子对象中没有的所有属性和方法将复制到子对象。

  5.7.4 汇合

  前面已经了解到,JavaScript中也可以实现私有属性,而且JavaScript也能像C++和Java一样支持基于类的继承方法。为了展示这些是怎样实现的,下面说明如何转换前面使用Vehicle、SportsCar和CementTruck对象的示例,从而使用信息隐藏和继承的新模式。代码清单5-5列出了新的对象定义。

  代码清单5-5 classicalInheritance.js

  function Vehicle() {
  var wheelCount = 4;
  var curbWeightInPounds = 4000;
  this.getWheelCount = function() {
  return wheelCount;
  }
  this.setWheelCount = function(count) {
  wheelCount = count;
  }
  this.getCurbWeightInPounds = function() {
  return curbWeightInPounds;
  }
  this.setCurbWeightInPounds = function(weight) {
  curbWeightInPounds = weight;
  }
  this.refuel = function() {
  return "Refueling Vehicle with regular 87 octane gasoline";
  }
  this.mainTasks = function() {
  return "Driving to work, school, and the grocery store";
  }
  }
  function SportsCar() {
  this.refuel = function() {
  return "Refueling SportsCar with premium 94 octane gasoline";
  }
  this.mainTasks = function() {
  return "Spirited driving, looking good, driving to the beach";
  }
  }
  function CementTruck() {
  this.refuel = function() {
  return "Refueling CementTruck with diesel fuel";
  }
  this.mainTasks = function() {
  return "Arrive at construction site, extend boom, deliver cement";
  }
  }

 需要注意,SportsCar和CementTruck对象没有定义自己的wheelCount和curbWeightInPounds属性,也没有相关的存取函数,因为这些属性和函数会从Vehicle对象继承。

  与前面一样,需要一个简单的HTML页面来测试这些新对象。代码清单5-6列出了测试这些新对象的HTML页面。要特别注意createInheritance函数,看看如何使用这个函数在Vehicle和SportsCar对象之间以及Vehicle和CementTruck对象之间创建继承关系。还要注意describe函数有所修改,以试图直接访问wheelCount和curbWeightInPounds属性。这样做会返回一个undefined值。

  代码清单5-6 classicalInheritance.html

  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
  <title>Classical Inheritance in JavaScript</title>
  <script type="text/javascript" src="classicalInheritance.js"></script>
  <script type="text/javaScript">
  function createInheritance(parent, child) {
  var property;
  for(property in parent) {
  if(!child[property]) {
  child[property] = parent[property];
  }
  }
  }
  function describe(vehicle) {
  var description = "";
  description = description + "Number of wheels (via property): "
  + vehicle.wheelCount;
  description = description + "
Number of wheels (via accessor): "
  + vehicle.getWheelCount();
  description = description + "
Curb Weight (via property): "
  + vehicle.curbWeightInPounds;
  description = description + "
Curb Weight (via accessor): "
  + vehicle.getCurbWeightInPounds();
  description = description + "
Refueling Method: " + vehicle.refuel();
  description = description + "
Main Tasks: " + vehicle.mainTasks();
  alert(description);
  }
  function createVehicle() {
  var vehicle = new Vehicle();
  describe(vehicle);
  }
  function createSportsCar() {
  var sportsCar = new SportsCar();
  createInheritance(new Vehicle(), sportsCar);
  sportsCar.setCurbWeightInPounds(3000);
  describe(sportsCar);
  }
  function createCementTruck() {
  var cementTruck = new CementTruck();
  createInheritance(new Vehicle(), cementTruck);
  cementTruck.setWheelCount(10);
  cementTruck.setCurbWeightInPounds(10000);
  describe(cementTruck);
  }
  </script>
  </head>
  <body>
  <h1>Examples of Classical Inheritance in JavaScript</h1>
  <br/><br/>
  <button onclick="createVehicle();">Create an instance of Vehicle</button>
  <br/><br/>
  <button onclick="createSportsCar();">Create an instance of SportsCar</button>
  <br/><br/>
  <button onclick="createCementTruck();">Create an instance of CementTruck</button>
  </body>
  </html>

 

 

  分别点击页面上的各个按钮会得到图5-17所示的结果。正如所料,试图直接访问私有属性就会返回undefined。

 

  图5-17 创建Vehicle、SportsCar和CementTruck对象,并使用describe函数描述它们的结果。私有属性不能直接访问,见警告框中的undefined值

最新评论 共有有 0 位网友发表了评论

发表评论

评论内容:不能超过250字,需审核,请自觉遵守互联网相关政策法规。
用户名:(注册)
密码:
验证码:
匿名?