`

跨越边界: JavaScript 语言特性

 
阅读更多

几乎每个 Web 开发人员都曾有过诅咒 JavaScript 的经历。这个备受争议的语言受累于其复杂的称为文档对象模型 (DOM)的编程模型、糟糕的实现和调试工具以及不一致的浏览器实现。直到最近,很多开发人员还认为 Javascript 从最好的方面说是无可避免之灾祸,从最坏的方面说不过是一种玩具罢了。

然而 JavaScript 现在开始日益重要起来,而且成为了广泛应用于 Web 开发的脚本语言。JavaScript 的复苏使一些业界领袖人物也不得不开始重新审视这种编程语言。诸如 Ajax (Asynchronous JavaScript + XML) 这样的编程技术让 Web 网页更加迷人。而完整的 Web 开发框架,比如 Apache Cocoon,则让 JavaScript 的应用越来越多,使其不只限于是一种用于制作 Web 页面的简单脚本。JavaScript 的一种称为 ActionScript 的派生物也推动了 Macromedia 的 Flash 客户端框架的发展。运行在 JVM 上的实现 Rhino 让 JavaScript 成为了 Java™ 开发人员所首选的一类脚本语言(参见 参考资料 )。

我 的好友兼同事 Stuart Halloway 是 Ajax 方面的专家,曾在其教授的 JavaScript 课程中做过这样的开场白:“到 2011 年,JavaScript 将被公认为是一种拥有开发现代应用程序所需的一整套新特性的语言” 。他继而介绍说 JavaScript 程序要比类似的 Java 程序紧密十倍,并继续展示了使其之所以如此的一些语言特性。

关于这个系列

跨越边界系列中 , 作者 Bruce Tate 提出了这样一个主张:今天的 Java 程序员通过学习其他技术和语言,会得到很好的帮助。编程领域已经发生了变化,Java 技术不再是所有开发项目理所当然的最佳选择。其他框架正在影响 Java 框架构建的方式,而从其他语言学到的概念也有助于 Java 编程。对 Python(或 Ruby、Smalltalk 等等)代码的编写可能改变 Java 编码的方式。

这个系列介绍的编程概念和技术,与 Java 开发有根本的不同,但可以直接应用于 Java 编程。在某些情况下,需要集成这些技术来利用它们。在其他情况下,可以直接应用这些概念。单独的工具并不重要,重要的是其他语言和框架可以影响 Java 社区中的开发人员、框架,甚至是基本方式。

在这篇文章中,我将带您探究 JavaScript 的一些特性,看看这些特性如何让它如此具有吸引力:

  • 高阶函数: 一个高阶函数可以将函数作为参数,也可以返回一个函数。此特性让 JavaScript 程序员可以用 Java 语言所不能提供的方法来操纵函数。
  • 动态类型: 通过延迟绑定,JavaScript 可以更准确和更灵活。
  • 灵活的对象模型: JavaScript 的对象模型使用一种相对不常见的方式进行继承 —— 称为原型 —— 而不是 Java 语言中更常见的基于类的对象模型。

您可能已经熟悉动态类型模型、高阶函数形式的函数式编程以及开放对象模型这些概念,因为我在其他的跨越边界 系列文章中已经作过相关的介绍。如果您从未进行过任何正式的 JavaScript 开发,您很可能会认为这些特性属于非常复杂的语言,例如 Python、Lisp、Smalltalk 和 Haskell,而绝非像 JavaScript 这样的语言所能提供的。因此,我将用实际的代码示例来说明这些概念。

立即开始

您无需设置 JavaScript。如果您可以在浏览器中阅读此篇文章,就证明您已经准备就绪了。本文包含的所有编程示例都可以在大多数浏览器内运行。我使用的是 Firefox。

用在 <script type='text/javascript'></script> 标记之间所包含的 JavaScript 加载简单的 Web 页面。清单 1 可以显示 Hello, World 文本:


清单 1. Hello, world

				
<script type='text/javascript'>
alert('Hello, World.')
</script>

要运行此代码,只需创建一个名为 example1.html 的文件。将清单 1 的代码复制到该文件内,并在浏览器中加载此文件(参看 下载 部分以获得本文使用的所有示例 HTML 文件)。注意到每次重载此页面时,该代码都会立即执行。

alert 是个函数调用,只有一个字符串作为参数。图 1 显示了由清单 1 中的代码弹出的警告框,显示文本 “Hello, World”。如果代码在 HTML body 之内(目前并未指定任何 body,但浏览器能接受不规则的 HTML,并且整个页面都默然作为一个 body 被处理)。页面一旦加载,JavaScript 就会立即执行。


图 1. Hello, world
Hello, World.

如果要延迟执行,可以在 HTML <head> 元素声明 JavaScript 函数,如清单 2 所示:


清单 2. 延迟执行

				
<head>

<script type='text/javascript'>
function hello() {
alert('Hello, World.')
}
</script>
</head>
<body>
<button onclick="hello();">Say Hello</button>
</body>

将清单 2 中的代码输入到一个 HTML 文件,在浏览器内加载该文件,单击 Say Hello 按钮,结果如图 2 所示:


图 2. 延迟执行
延迟执行




回页首


高阶函数

清单 2 ,可以大致体会到一些 JavaScript 在操纵函数方面的能力。将函数名称传递给 HTML button 标记并利用 HTML 的内置事件模型。使用 JavaScript 时,我会经常在变量或数组中存储函数(在本文后面的 对象模型 一节,您会看到 JavaScript 对象模型策略大量使用了此技巧)。例如,查看一下清单 3:


清单 3. 用变量操纵函数

				
<head>

<script type='text/javascript'>
hot = function hot() {
alert('Sweat.')
}
cold = function cold() {
alert('Shiver.')
}

function swap() {
temp = hot
hot = cold
cold = temp
alert('Swapped.')
}
</script>
</head>
<body>
<button onclick="hot();">Hot</button>
<button onclick="cold();">Cold</button>
<button onclick="swap();">Swap</button>
</body>

函数是 JavaScript 中的一类对象,可以自由地操纵它们。首先我声明两个函数:hotcold 。并分别在不同的变量存储它们。单击 Hot 或 Cold 按钮会调用对应的函数,生成一个告警。接下来,声明另一个函数用来交换 Hot 和 Cold 按钮的值,将此函数与第三个按钮关联,该按钮显示如图 3 所示的告警:


图 3. 操纵函数

这个例子说明可以像处理其他变量一样处理函数。C 开发人员很容易将此概念看作是函数指针 功能,但 JavaScript 的高阶函数的功能更为强大。该特性让 JavaScript 程序员能够像处理其他变量类型一样轻松处理动作或函数。

将函数用作函数的参数,或将函数作为值返回,这些概念属于高阶函数的领域。清单 4 对 清单 3 做了一点点修改,显示了能返回函数的高阶函数:


清单 4. 高阶函数

				
<head>

<script type='text/javascript'>

function temperature() {
return current
}

hot = function hot() {
alert('Hot.')
}

cold = function cold() {
alert('Cold.')
}

current = hot

function swap() {
if(current == hot) {
current = cold
} else {
current = hot
}
}
</script>
</head>
<body>
<button onclick="funct = temperature()();">Temperature</button>
<button onclick="swap();">Swap</button>
</body>

这个例子解决了一个常见问题:如何将更改中的行为附加到用户接口事件?通过高阶函数,这很容易做到。temperature 高阶函数返回 current 的值,而 current 又可以有 hotcold 函数。看一下这个有些陈旧的函数调用:temperature()() 。第一组括号用于调用 temperature 函数。第二组括号调用由 temperature 返回 的函数。图 4 显示了输出:


图 4. 高阶函数
高阶函数

高阶函数是函数式编程的基础,对比面向对象编程,函数式编程代表了更高级别的抽象。但 JavaScript 的实力并不仅限于高阶函数。JavaScript 的动态类型就极为适合 UI 开发。




回页首


动态类型

通过静态类型,编译器可以检查参数和变量的值或针对一个给定操作所允许的返回值。其优势是编译器可以做额外的错误检查。而且静态类型还可以为诸如 IDE 这样的工具提供更多信息,带来其他一些特性,比如更好的代码完成功能。但静态类型也存在着如下一些劣势:

  • 必须提前声明意图,这常常会导致灵活性降低。例如,更改一个 Java 类就会更改类的类型,因而必须重新编译。对比之下,Ruby 允许开放的类,但更改一个 Java 类还是会更改类的类型。
  • 要实现相同的功能,必须输入更多的代码。例如,必须用参数形式包括进类型信息,必须用函数形式返回值和所有变量的类型。另外,还必须声明所有变量并显式地转化类型。
  • 静态语言的编译-部署周期要比动态语言的部署周期长,尽管一些工具可被用来在某种程度上缓解这一问题。

静 态类型更适合用于构建中间件或操作系统的语言中。UI 开发常常需要更高的效率和灵活性,所以更适合采用动态类型。我深知这种做法存在危险。相信使用过 JavaScript 的 Web 开发人员都曾经为编译器本应检测到的错误类型的变量而绞尽脑汁。但它所带来的优势同样不可否认。下面将举例加以说明。

首先,考虑一个对象的情况。在清单 5 中,创建一个新对象,并访问一个不存在的属性,名为 color


清单 5. 引入一个属性

				
<script type='text/javascript'>
blank_object = new Object();
blank_object.color = 'blue'
alert('The color is ' + blank_object.color)
</script>

当加载并执行此应用程序时,会得到如图 5 所示的结果:


图 5. 引入属性
 引入属性

JavaScript 并不会报告 blue 属性不存在的错误。静态类型的拥护者大都会被本例所吓倒,因为本例中的错误被很好地隐匿了。虽然这种做法多少会让您感觉有些不正当,但您也不能否认它巨大 的诱惑力。您可以很快引入属性。如果将本例和本文之前的例子结合起来,还可以引入行为。记住,变量可以保存函数!所以,基于动态类型和高阶函数,您可以在 任何时候向类中引入任意的行为。

可以轻松地重写 清单 5 ,使其如清单 6 所示:


清单 6. 引入行为

				
<script type='text/javascript'>
blank_object = new Object();
blank_object.color = function() { return 'blue'}
alert('The color is ' + blank_object.color())
</script>

从上例可以看出,在 JavaScript 的不同概念之间可以如此轻松地来回变换,其含义上的变化很大 —— 比如,是引入行为还是引入数据 —— 但语法上的变化却很小。该语言很好的延展性是它的一种优势,但同样也是其缺点所在。实际上,该语言本身的对象模型就是 JavaScript 延展程度的一种体现。




回页首


对象模型

到目前为止,您应该对 JavaScript 有一个正确的评价了,它绝非只如一个玩具那么简单。事实上,很多人都使用过其对象模型创建过极为复杂、设计良好的面向对象软件。但对象模型尤其是用于继承的对象模型又非您一贯认为的那样。

Java 语言是基于类的。当构建应用程序时,也同时构建了可以作为所有对象的模板的新类。然后调用 new 来实例化该模板,创建一个新对象。而在 JavaScript 中,所创建的是一个原型,此原型是一个实例,可以创建所有未来的对象。

现在先暂且放下这些抽象的概念,去查看一些实际代码。比如,清单 7 创建了一个简单的 Animal ,它具有 name 属性和 speak 动作。其他动物会从这个基础继承。


清单 7. 创建一个构造函数

				
<script type='text/javascript'>
Animal = function() {
this.name = "nobody"
this.speak = function () {
return "Who am I?"
}
}

myAnimal = new Animal();
alert('The animal named ' + myAnimal.name +
' says ' + myAnimal.speak());

</script>

清单 7 的结果如图 6 所示:


图 6. 创建一个构造函数
构造函数

对于 Java 开发人员而言,清单 7 中的代码看起来多少有点生疏和奇怪。实际上对于没有亲自构建过对象的许多 JavaScript 开发人员来说,这些代码同样看起来有点生疏和奇怪。也许,下面的解释可以让大家能够更好地理解这段代码。

实际上,您只需重点关注其中三段信息。首先,JavaScript 用嵌套函数表示对象。这意味着清单 7 中的 Animal 的定义是一种有效的语法。第二,JavaScript 基于原型或现有的对象的实例来构造对象,而非基于类模板。funct() 是一种调用,但 new Animal() 却基于 Animal 内的原型构造一个对象。最后,在 JavaScript 中,对象只是函数和变量的集合。每个对象并不与类型相关,所以可以自由地修改这种结构。

回到 清单 7 。如您所见,JavaScript 基于在 Animal 中指定的原型定义一个新对象:myAnimal 。继而可以使用原型中的属性和函数,甚或重定义函数和属性。这种灵活性可能会让 Java 开发人员受不了,因为他们不习惯这种行为,但它的确是一种十分强大的模型。

现在我还要更深入一步。您还可以使用名为 prototype 实例变量来指定对象的基础。方法是设置 prototype 实例变量使其指向继承链的父。如此设置 prototype 之后,您所创建的对象会为未指定的那些对象继承属性和函数。这样一来,您就可以模仿面向对象的继承概念。以清单 8 为例:


清单 8. 通过原型继承

				
<script type='text/javascript'>

Animal = function() {
this.name = "nobody"
this.speak = function () {
return "Who am I?"
}
}
Dog = function() {
this.speak = function() {
return "Woof!"
}
}
Dog.prototype = new Animal();

myAnimal = new Dog();
alert('The animal named ' + myAnimal.name +
' says ' + myAnimal.speak());
</script>

在清单 8 中,创建了一个 Dog 原型。此原型基于 AnimalDog 重定义 speak() 方法但却不会对 name() 方法做任何改动。随后,将原型 Dog 设置成 Animal 。图 7 显示了其结果:


图 7. 通过原型继承
继承

这也展示了 JavaScript 是如何解决到属性或方法的引用问题的:

  • JavaScript 基于原始的原型创建实例,该原型在构造函数中定义。任何对方法或属性的引用都会使用所生成的原始副本。
  • 您可以在对象内像定义其他任何变量一样重新定义这些变量。这样做必然会更改此对象。所以您显式定义的任何属性或函数都将比在原始的原型中定义的那些属性或函数优先级要高。
  • 如果您显式设置了名为 prototype 的实例变量,JavaScript 就会在此实例中寻找任何未定义的实例变量或属性。这种查找是递归的:如果 在 prototype 内定义的实例不能找到属性或函数,它就会在 原型中查找,依此类推。

那 么,JavaScript 的继承模型到底是什么样的?这取决于您如何对它进行定义。您需要定义继承行为以便可以覆盖它。然而,从本质上讲,JavaScript 更像是一种函数式语言,而非面向对象的语言,它使用一些智能的语法和语义来仿真高度复杂的行为。其对象模型极为灵活、开放和强大,具有全部的反射性。有些 人可能会说它太过灵活。而我的忠告则是,按具体作业的需要选择合适的工具。




回页首


结束语

JavaScript 对象模型构建在该语言的其他功能之上来支持大量的库,比如 Dojo(参见 参考资料 )。这种灵活性让每个框架能够以一种精细的方式更改对象模型。在某种程度上,这种灵活性是一种极大的缺点。它可以导致可怕的互操作性问题(尽管该语言的灵活性可以部分缓解这些问题)。

而 另一方面,灵活性又是一种巨大的优势。Java 语言一直苦于无法充分增强其灵活性,原因是它的基本对象模型还未灵活到可以被扩展的程度。一个典型的企业级开发人员为能够成功使用 Java 语言必须要学习很多东西,而新出现的一些优秀的开放源码项目和新技术,比如面向方面编程、Spring 编程框架和字节码增强库,则带来了大量要学的代码。

最后,JavaScript 优秀的灵活性的确让您体会到了一些高阶语言的强大功能。当然您无需选择为每个项目或大多数项目都做这样的权衡和折衷。但了解一种语言的优势和劣势 —— 通过参考大量信息,而不仅仅基于广告宣传或公众意见 —— 会让您可以更好地控制何时需要使用以及何时不能使用这种语言。当您在修改 JavaScript Web 小部件时,您至少知道该如何让此语言发挥它最大的优势。请继续跨越边界吧。





回页首


下载

描述 名字 大小 下载方法 本文的示例 HTML 文件
j-cb12196.zip 3KB HTTP
关于下载方法的信息


参考资料

学习


讨论


关于作者

Bruce Tate 是位父亲、山地车手、皮艇手,住在德克萨斯州的奥斯汀。他是三本最畅销 Java 图书的作者,包括获得 Jolt 奖的 Better, Faster, Lighter Java 。他最近推出了 From Java to RubyRails: Up and Running 。 他在 IBM 工作了 13 年,而后创立了 RapidRed 顾问公司,在那里他专攻基于 Ruby 的轻量级开发策略和体系结构及 Ruby on Rails 框架。现在他是 WellGood LLC 的 CTO ,该公司致力于为非盈利组织和福利机构开辟市场。

分享到:
评论

相关推荐

    跨越边界:闭包

    一些人认为闭包带给编程语言的额外复杂性并不划算。他们的论点是:为了闭包带来的一点点便利而打破原有语法糖的简洁性非常不值得。其他一些人则认为闭包将引发新一轮模式设计的潮流。闭包是可以用作函数参数和方法...

    跨越边界: Lisp之美

    Lisp长久以来一直被视为伟大的编程语言之一。其漫长的发展过程(接近五十年)中引发的追随狂潮表明:这是一门非同凡响的语言。在MIT,Lisp在所有程序员的课程中占了举足轻重的地位。像Paul Graham那样的企业家们将...

    跨越边界:REST on Rails

    Ruby on Rails是一个突然流行起来的框架,充当着Ruby编程语言的催化剂。随着Ruby的经验不断成功,开发人员开始寻求把他们的Ruby应用程序与用其他语言编写的应用程序集成。Rails对Web服务提供了优秀的支持。本文介绍...

    跨越边界:延迟绑定

    比起不具有此项编译时检查功能的动态类型语言来说,静态类型语言更加稳定且具有更佳的性能。然而静态类型语言存在一个严重的局限性:前期绑定。一些动态类型语言(如Ruby、Smalltalk和Self)允许延迟绑定,它们可以...

    跨越边界:Rails迁移

    Ruby on Rails是不断发展的Web开发框架,它实现了一些先进的想法,例如通过配置进行约定、大量的元编程、特定于域的语言以及用数据库包装代替对象关系映射。这篇文章研究的Rails模式迁移是一种把每个数据库的模式...

    跨越边界的社区.pdf

    跨越边界的社区.pdf

    跨越边界:研究活动记录

    Java编程语言对于广大的厂商、客户和行业来说,获得了前所未有的成功。但是,没有一种编程语言可以擅长每件工作。这篇文章开启了BruceTate的一个新系列,研究其他语言解决主要问题的方式以及这些解决方案对Java开发...

    跨越边界:AjaxonRails

     跨越边界系列之前的两篇文章(参见参考资料)全面介绍了Streamlined,这是Rails的辅助框架,该框架有效地利用scaffolding来快速生成简单的、使用Ajax的用户界面。除非您一直与世隔绝,不然您一定会知道A

    无边界企业:开放安全的混合云与网络是必由之路.pdf

    无边界企业:开放安全的混合云与网络是必由之路.pdf 无边界企业:开放安全的混合云与网络是必由之路.pdf 无边界企业:开放安全的混合云与网络是必由之路.pdf 无边界企业:开放安全的混合云与网络是必由之路.pdf 无...

    藏经阁-思维的边界:认知智能-44.pdf

    藏经阁-思维的边界:认知智能-44

    跨越边界1:Ajax on Rails

    您一定知道Ajax是这样一种编程技术,它使用XML、JavaScript和Web标准来创建高度交互性的Web页面,正如您在Google Maps和大量其他站点上所看到的页面那样。对Ajax这种使Web页面更具交互性的技术的大肆宣传已成过度之...

    论人工智能的边界:从逻辑时间的角度看-沈宏梁.pdf

    论人工智能的边界:从逻辑时间的角度看_沈宏梁

    20161115-企鹅智酷-中国新媒体趋势报告(2016):智媒来临与人机边界.pdf

    20161115-企鹅智酷-中国新媒体趋势报告(2016):智媒来临与人机边界.pdf

    超越边界:2023年生物科技行业报告:道阻且长,行则将至-安永-2023-62页.pdf

    超越边界:2023年生物科技行业报告:道阻且长,行则将至-安永-2023-62页

    黄河流域水土保持边界数据:包括泥沙区界、支流区界、流域界、流域省界等

    黄河流域水土保持边界数据:包括泥沙区界、支流区界、流域界、流域省界等; 该数据集较为齐全,统一采用WGS-84坐标系统; 数据集所包含数据的格式均为shapefile(shp)格式,数据完整; 该数据集可用于实验分析和...

    《程序天下:JavaScript实例自学手册》光盘源码

    15.23 到边界反弹的漂浮图片 15.24 用键盘控制图片移动 15.25 预装载图片提高站点速度 15.26 始终在屏幕右下角的图片 15.27 可拖动的图片 15.28 等比例缩略图 15.29 用JavaScript导出图像到Excel 15.30 使用VML打造...

    兀在matlab的代码是什么意思-LAB:[CVPR2018]看边界:一种边界感知的人脸对齐算法

    兀在matlab的代码是什么的英文看边界:边界感知的人脸对齐算法 由清华大学创建。 我们通过利用边界线作为人脸的几何结构来帮助面部地标定位,提出了一种新的边界感知人脸对齐算法。 与传统的基于热图的方法和基于...

    徐旸:跨越边界的新一代移动应用技术

    Appcelerator为什么是一个跨越边界的新一代的开发平台,徐旸表示因为有五个比较大的优点:一是做跨平台开发,有统一的代码,有原生应用最佳用户的体验;二是全力拥抱Java Script语言;三是我们是开源代码,有庞大的...

    跨越边界 Ajax on Rails

    跨越边界 Ajax on Rails(英文版)

    跨越边界并购.1.pptx

    跨越边界并购.1.pptx

Global site tag (gtag.js) - Google Analytics