`
handy
  • 浏览: 22373 次
  • 性别: Icon_minigender_1
最近访客 更多访客>>
社区版块
存档分类
最新评论

转 跨越边界: JavaScript 语言特性

阅读更多

JavaScript 常被人们认为是编程语言中无足轻重的一员。这种观点的形成可以“归功”于其开发工具、复杂且不一致的面向 HTML 页面的 文档对象模型以及不一致的浏览器实现。但 JavaScript 绝对不仅仅是一个玩具这么简单。在本文中,Bruce Tate 向您介绍了  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. 延迟执行
js 代码
  1.   
    
<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 中的一类对象,可以自由地操纵它们。首先我声明两个函数:hot 和 cold。并分别在不同的变量存储它们。单击 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 又可以有 hot 或 cold 函数。看一下这个有些陈旧的函数调用: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 小部件时,您至少知道该如何让此语言发挥它最大的优势。请继续跨越边界吧。

分享到:
评论

相关推荐

    Python-Skulpt是Python编程语言的一个JavaScript实现

    - **兼容性**:虽然Skulpt不支持Python的全部标准库,但它已经实现了许多核心的Python语言特性,包括基本的数据类型、控制结构、函数和模块。 **4. 使用Skulpt** 使用Skulpt通常涉及到以下几个步骤: - 下载并...

    C#:一门强大的多范式编程语言C#:一门强大的多范式编程语言

    随着.NET Core的推出,C#已经跨越了Windows平台的边界,成为了一门支持在Windows、Linux和Mac等多个操作系统上开发和运行的语言。这意味着开发者可以使用同一套代码在不同的平台上运行,极大地提高了开发效率和代码...

    rust-ffi-complex-types:通过FFI与复杂类型通信的示例库

    总结来说,“rust-ffi-complex-types”库展示了如何利用Rust的FFI功能,跨越语言边界处理复杂的类型通信。通过理解Rust与JavaScript之间的数据类型差异,以及如何进行有效转换,开发者可以构建出高效、安全的跨语言...

    双击单词翻译

    3. **单词识别**:为了确保准确地识别出单词边界,开发者可能需要使用正则表达式或者依赖于特定的语言库,例如英语的单词识别可能基于空格、标点符号的规则,而其他语言(如中文)可能需要更复杂的规则。 4. **翻译...

    圣诞老人过悬崖小游戏源码

    1. **HTML5**:HTML5是超文本标记语言的第五个主要版本,它增强了网页的多媒体功能,提供了更好的离线存储、图形绘制和表单控制等特性。在这个游戏中,HTML5被用来构建游戏的基本结构,包括游戏场景、角色、障碍物等...

    资料.zip

    在向ES6及更高版本迁移的过程中,TypeScript支持了最新的语言特性,如箭头函数、解构赋值、async/await等,让你在享受现代JavaScript语法的同时,也能得到类型安全的保障。 【images】 虽然“images”目录没有明确...

    QML2与C++交互

    QML2的主要改进包括性能提升、类型系统增强和新特性如JavaScript ES6语法的支持。在QML2中,你可以定义自定义元素并使用C++扩展它们,这为开发提供了更大的灵活性。 在QML与C++交互的过程中,主要涉及以下几个关键...

    webassembly-examples:MDN WebAssembly文档随附的代码示例-请参阅https

    这些调用可以跨越WASM模块边界,与JavaScript交互。 6. **JavaScript接口**:通过`WebAssembly.instantiate`和`WebAssembly.Module`等API,JavaScript可以加载、编译和实例化WASM模块。示例将展示如何在JavaScript...

    日历 前三天 前一个月

    这个功能的实现通常涉及到处理月份和年份的边界条件,因为不同月份的天数不同,且跨越月份时需要正确处理闰年的情况。当用户选择“前一个月”时,日历组件需要更新当前显示的月份和年份,并确保展示的日期范围正确。...

    Windows脚本技术手册

    远程脚本技术允许脚本跨越网络边界,与远程服务器进行交互。这在分布式系统和Web应用程序中十分常见,例如使用VBScript通过HTTP协议与服务器进行数据交换,实现动态网页。 Script控件是另一种增强脚本功能的工具,...

    从The Transistor到The WEB浏览器:灵感来自George Hotz

    从晶体管的发明到现代WEB浏览器的演变,这是一个跨越了几十年的科技创新历程。 【描述】:“从The Transistor到The WEB浏览器:灵感来自George Hotz” George Hotz,绰号“Geohot”,是计算机科学领域的一位杰出...

    Jquery Ajax学习实例3 向WebService发出请求,调用方法返回数据

    它为应用程序提供了跨越平台、语言边界的数据交换方式。 - **优点**:WebService的主要优点包括跨平台性、可重用性和灵活性等。通过WebService,开发者可以在不考虑客户端类型的情况下提供服务。 - **应用场景**:...

    track-dtg-admin

    开发者可以设置虚拟边界,当用户跨越这些边界时,应用会收到通知。 5. **服务器通信**:每当位置更新时,应用需要将这些信息发送到服务器。这通常通过HTTP请求(比如AJAX或Fetch API)实现,可能涉及到JSON数据格式...

    hyperspace:超空间创意

    它提供了一个在浏览器中绘制2D图形的画布,开发者可以利用JavaScript来绘制复杂的动画,创建出仿佛跨越现实与虚拟边界的视觉体验。例如,3D渲染、粒子系统、甚至虚拟现实(VR)场景都可以通过HTML5 Canvas和JavaScript...

    TM-0533_HTML手机电脑网站_网页源码移动端前端_H5模板_自适应响应式源码.zip

    其自适应性确保了在移动互联网占据主导地位的当下,网站能够无缝地跨越设备边界,提升用户满意度。同时,由于是源码形式,开发者可以根据自己的需求进行定制化修改,以满足特定的品牌风格或功能需求。 总的来说,TM...

    模块化不仅与代码有关

    总结来说,模块化是一种有效的软件设计策略,它跨越了语言边界,应用于多种编程语言和开发场景。理解并善用模块化,不仅能提高代码质量,还能优化开发流程,增强软件的可扩展性和维护性。无论是JavaScript的模块系统...

    基于Web服务和Ajax的校园MIS构件库设计研究.pdf

    它使得校园MIS的不同组件能够跨越平台和系统边界进行交互,提高了系统的灵活性和可扩展性。同时,Web服务的标准化特性有助于确保数据的一致性和安全性。 Ajax技术则在用户界面层面提供了更丰富的交互体验。通过在...

    Escape 2D-crx插件

    在实际操作中,"Escape 2D"可能会利用JavaScript等编程语言实现动态效果,如动画、声音和逻辑控制,来构建游戏关卡和剧情推进。用户可以通过点击、拖动或其他交互方式,与游戏场景进行互动,解决谜题,推动故事发展...

    marching-squares

    TypeScript 是 JavaScript 的超集,它引入了类、接口和其他高级语言结构,使得代码更易于理解和重构。 "marching-squares" 算法的基本步骤如下: 1. **输入准备**:首先,你需要一个二维数组,其中每个元素代表...

    Nutex PHP Framework-开源

    在Nutex框架中,XML-RPC被用来构建分布式应用,使得服务可以跨越服务器边界进行交互,这对于大型、复杂的网络应用程序至关重要。 接着,HTTPS(HyperText Transfer Protocol Secure)是HTTP的安全版本,它通过SSL/...

Global site tag (gtag.js) - Google Analytics