JavaScript-程序员参考-全-

JavaScript 程序员参考(全)

原文:JavaScript Programmer's Reference

协议:CC BY-NC-SA 4.0

零、简介

在过去的十年中,JavaScript 的受欢迎程度有了很大的提高。JavaScript 最初用于创建交互式网页和处理基本的表单验证,现在是许多复杂 web 应用的主干。因此,能够很好地使用 JavaScript 编程的人在各种项目中都有很高的需求。如果你想从事网络技术,你应该了解 JavaScript。

本书旨在为 JavaScript 提供完整的参考,并涵盖该语言的基础知识。我们的总体目标是涵盖在任何规模的项目中使用 JavaScript 所需的所有主题。

这本书是给谁的?

这本书面向两类读者:一类是已经了解 JavaScript 并需要可靠参考的人,另一类是刚刚开始学习该语言并希望快速上手的人。在这两种情况下,我们都假设你至少有一个基本的编程背景。特别是第一章,假设你是从更传统的语言,如 C++或 Java,学习 JavaScript。

我们还假设您对 HTML 有基本的了解,包括语义标记和各种文档类型声明——尽管整本书中使用 HTML 的示例都是用 HTML 5 编写的。我们还假设您对 CSS 以及如何使用它来管理网页外观有基本的了解。

最后,我们假设您对 web 及其底层协议有基本的了解。

如果你一生中从未写过一行代码,或者如果你是 web 技术的新手,这可能不是最适合你的书。但是只要你对编程和 web 技术有基本的了解,这本书就可以帮助你学习 JavaScript。

概观

这本书分为两部分。第一部分致力于讲授 JavaScript 及其相关技术的基础知识。第二部分是参考文献。

  • 第一章 针对的是从另一种语言来到 JavaScript 的程序员。JavaScript 是一种比大多数通用语言更加动态的语言,从这些语言迁移到 JavaScript 会带来特殊的挑战。首先,我们介绍 JavaScript 是什么以及它是如何产生的,然后我们深入探讨其他语言的程序员遇到的三个主要挑战:JavaScript 的对象继承和类的缺乏、它的作用域规则以及它的动态类型。所有这些特性在 JavaScript 中的工作方式与在其他语言中有很大不同,我们希望立即了解它们。我们通过提供一些使用我们所学的 JavaScript 中的常见模式来结束这一章。
  • 第二章 是对 JavaScript 语言的总体参考。我们从头开始,从 JavaScript 的词汇结构开始,然后快速进入它的操作符,它如何处理变量,JavaScript 对对象、数组和函数的处理。我们通过查看 JavaScript 的流控制语句来结束这一章。第二章更详细地讲述了第一章中提到的一些事情。它们共同构成了对这门语言的坚实介绍,从基础到中间概念,如闭包。
  • 第三章 涵盖了文档对象模型。虽然从技术上来说 DOM 不是 JavaScript 的一部分,但是我们在其中包含了一章,因为您将使用 JavaScript 完成的大量工作很可能会涉及到 DOM。这一章从 DOM 标准的简史和它是如何发展的开始。然后我们深入到细节:如何访问页面元素,如何操作它们(包括创建新元素和删除现有元素),以及 DOM 提供的事件模型(包括定制事件)。我们以跨浏览器策略的讨论来结束这一章,这些策略用于处理不同浏览器之间 DOM 实现的差异。
  • 第四章 把我们在第一章、第二章、第三章中学到的东西都拿来用。我们已经把这一章分成了几节,每一节都涵盖了不同的内容。第一部分,使用 JavaScript,涵盖了使用 JavaScript 需要的东西。我们涵盖了基本的工作流程以及工具和调试技术。第二部分通过仔细研究浏览器如何加载和解析脚本,以及如何利用这一点来提高 JavaScript 应用的效率。第三部分介绍使用 XMLHTTP 对象的异步通信——也称为 AJAX。第四部分介绍了浏览器强加的一个重要的安全限制——单源策略——以及使用该策略并完成工作的一些技巧。在第五节中,我们提供了一个数据缓存的实际例子。第六部分是关于选择 JavaScript 库,第七部分涵盖了最流行的 JavaScript 库 jQuery。最后,我们用一个实际的例子来结束这一章,这个例子使用我们在这一章中学到的所有东西来构建你自己的库。
  • 第五章 从本书的参考部分开始,涵盖了 JavaScript 的一部分对象。
  • 第六章 为 JavaScript 的控制语句提供了参考。
  • 第七章 讲的都是 JavaScript 操作符。
  • 第八章 是 DOM 引用。

尽管它们是参考章节,但我们始终试图提供有用的、重要的示例。

本书中使用的约定

在本书中,代码以fixed-width font的形式呈现。代码示例和语法定义与其他文本分开,并使用相同的字体。此外,代码元素(如对象、原始值等)的内联引用也以相同的字体显示。

代码下载

所有的代码片段和例子都可以从http://www.apress.com/9781430246299下载。该下载包括书中所有的示例代码,以及一些没有包含在书中的额外代码。我们鼓励您下载代码,并在阅读文本时使用它。

一、JavaScript 基础知识

在这一章中,我们将采用一种不同于大多数编程语言参考资料的第一章的方法。大多数书都会深入到语言的语法和其他细节,但是我们在这里不打算这样做。JavaScript 是一种非常难学的语言,也是一种相对容易让人讨厌的语言,所以我们首先想探究为什么有些人会纠结于它,然后我们将提供一种不同的、更直观的方法来掌握这门语言。

我们将从研究学习和使用 JavaScript 的挑战开始。我们将通过研究该语言的进化史和实现来介绍一些背景知识。然后,有了这些信息,我们将研究 JavaScript 面临挑战的三个特定领域:继承隐喻 、作用域隐喻和类型隐喻。最后,我们将研究 JavaScript 中两种非常常见的模式——这是一个大多数书籍直到很久以后才涉及的主题,但是我们认为在本章结束时,您已经为处理这个主题做好了充分的准备。这些模式也是你在本章中学到的所有东西的很好的应用。

当我们阅读本章时,我们将讨论 JavaScript 的基本框架,但是我们鼓励你在这个阶段不要在语法和其他细节的考虑上陷入太多。我们将在后面的章节中讨论这些主题。现在,专注于我们将要描绘的更大的图景。

难学,更难爱

JavaScript 是很多人憎恨的目标。如果你在你最喜欢的搜索引擎中输入“讨厌 JavaScript”或“JavaScript 烂透了”,你会立即得到一页又一页的关于为什么这种语言很糟糕的文章。你可以自己阅读这些文章——我们鼓励你这样做——但是在阅读了几篇之后,你会注意到投诉中出现的一种模式。人们不喜欢 JavaScript 的几个关键点是:

  • 它的对象和继承的实现——原型与类
  • 其范围规则
  • 对数据类型的处理

这是真的,JavaScript 做这三件事与许多普通语言完全不同。更糟糕的是,JavaScript 采用了类似于 C 或 Java 的语法和结构,这助长了一种可以理解的期望,即 JavaScript 应该像 C 或 Java 那样运行,但事实并非如此。(这是 JavaScript 作用域规则的一个特殊问题,我们将在本章后面更详细地讨论。)

还有,因为 JavaScript 很像 C,一个熟悉类 C 语言 (C,C++,Java,C#等)的程序员。)可以快速轻松地达到精通 JavaScript 的水平,而无需真正理解其内部工作原理。经常会遇到一些有才华的开发人员,他们已经使用 JavaScript 很多年了(甚至可能认为自己是 JavaScript 专家),但是他们对这种语言只有基本的了解,对它的真正力量几乎没有掌握。

所以 JavaScript 很容易被误解,很难掌握,并且三个重要的语言特性的实现有很大的不同。再加上不同浏览器之间不同的实现,难怪人们对这种语言评价不高。

以免我们把您从语言中吓跑,重要的是要认识到,很多时候这种低评价是由于误解了 JavaScript 的工作方式,或者试图应用其他语言的实践,而这些实践并不能很好地映射到 JavaScript 的行为。我们发现,开发人员越愿意学习 JavaScript,他们就越欣赏它。当然,这在某种程度上对任何语言都是正确的,但对 JavaScript 尤其如此。它的动态本质和真正的功能很难理解,但是一旦你理解了它,这种语言就开始呈现出很少语言所具有的美丽和简单。

我们教授 JavaScript 的方法旨在帮助您在我们开始讲述函数、数组和流控制等细节之前就形成对 JavaScript 的理解。我们也将非常详细地介绍这些内容,但在此之前,我们想正面解决人们对 JavaScript 感到困惑或困难的主要问题。这样做,我们希望你开始掌握 JavaScript 的旅程。精通的第一步是理解 JavaScript 的起源及其持续的发展。

JavaScript 是什么?

JavaScript 是一种编程语言,于 1995 年首次发布。尽管有它的名字,JavaScript 实际上和 Java 编程语言没有任何关系。从高层次来看,JavaScript 有几个显著的特性:

  • 它是一种脚本语言 : JavaScript 程序是由解释器(或引擎)读取并执行的“脚本”。这与编译语言不同,在编译语言中,程序由编译器读取并翻译成可执行文件。(注意,JavaScript 引擎本身通常是用编译语言编写的。)用脚本语言编写的程序具有高度的可移植性,因为它们可以在任何已经为该语言构建了解释器的环境中运行。
  • 是类 C:JavaScript 的基本语法和结构大量借鉴 C。
  • 它是一种面向对象的语言:JavaScript 不同于大多数面向对象的语言,因为它的继承模型是基于原型的,而不是基于类的。
  • 拥有一级函数 : JavaScript 函数是羽翼丰满的对象,有自己的属性和方法,可以作为参数传入其他函数,也可以从其他函数返回,赋给变量。
  • 它是动态的:“动态编程语言”这个术语很宽泛,涵盖了很多特性。JavaScript 最动态的特性是其变量类型的实现(见下一点)及其eval()方法和其他功能方面。
  • 既有动态类型又有弱类型 : JavaScript 变量在解释时不进行类型检查(使 JavaScript 成为动态类型语言),混合类型的操作数之间如何发生运算取决于 JavaScript 内部的特定规则(使 JavaScript 成为弱类型语言)。
  • 它是一个标准的实现:正如下一节所描述的,JavaScript 实际上是 ECMA-262 标准的一个实现,就像 C 编程语言受 ISO 标准管理一样。

这些主要特性结合起来使 JavaScript 变得有些独特。如果您对类似 C 的语言稍有了解,它们也有助于使 JavaScript 基础知识变得相当容易学习,因为您对 JavaScript 的语法或结构不会有什么问题。

JavaScript 也深受 Scheme 的影响,Scheme 是另一种函数式编程语言,是 Lisp 的一种方言。JavaScript 的许多设计原则都来自 Scheme,包括它的作用域。

那么 JavaScript 是如何拥有这种独特的特性组合的呢?

JavaScript 和 ECMA-262 标准的发展

如前所述,JavaScript 实际上是一个标准的实现。不过,事情并不是那样开始的。1995 年 9 月,Netscape 发布了 Navigator 浏览器的 2.0 版本,它有一个新的特性:一种面向对象的脚本语言,可以访问和操作页面元素。这种新的脚本语言由网景工程师 Brendan Eich 创建,最初代号为“Mocha”,最初发布时名为“LiveScript”此后不久,它被重新命名为“JavaScript”,以借助 Sun 的 Java 编程语言。

1996 年,网景向欧洲计算机制造商协会(或简称 ECMA )提交了 JavaScript 作为标准考虑,见http://www.ecma-international.org/memento/history.htm。由此产生的标准 ECMA-262 于 1997 年 6 月被采用。ECMA-262 恰当地定义了 ECMAScript 脚本语言,JavaScript 被认为是 ECMAScript 的一种“方言”。ECMAScript 的另一个值得注意的方言是 ActionScript 的版本 3 或更高版本。从技术上讲,Internet Explorer 不实现 JavaScript(出于版权考虑),而是实现微软自己的 ECMAScript 方言“JScript”

ECMAScript 的最新版本是 5.1,发布于 2011 年 6 月。从 ECMAScript 3 到 ECMAScript 5 的版本轨迹有一段有趣的政治历史,包括标准委员会(由 Brendan Eich 领导)和雅虎、微软和谷歌等行业利益相关者之间的分歧。我们不打算深入细节;可以说,最终各方都同意将 ECMAScript 5 作为一个统一的解决方案。

作为 ECMAScript 5 的一部分,ECMA International 发布了一套可由任何浏览器运行的一致性测试,并将显示浏览器支持哪些 ECMAScript 5 功能以及不支持哪些功能。该套件名为 Test262,可在http://test262.ecmascript.org/获得。请注意,运行全套测试可能需要几个小时,其中包含大约 11,500 个单独的测试。截至本文撰写时,没有一款浏览器在 Test262 中获得满分;目前最好的成绩属于 Safari 和 Internet Explorer,两者都只有 7 项测试不及格。Firefox 得分最低,目前未能通过 170 项测试(尽管这仍然是一个令人印象深刻的成就)。这些数字是在撰写本文时的数据,从现在到发表之前很可能会发生变化。我们鼓励您在自己喜欢的浏览器上运行测试套件,并探索在每种浏览器中失败的测试。这将让您对不同浏览器之间 JavaScript 实现的差异有所了解,以及它们到底有多小。

ECMAScript 的发展从第 6 版开始,代号为 ECMAScript Harmony。Harmony 还没有正式发布,在撰写本文时,还没有官方批准的发布日期。然而,规范草案都在http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts对公众开放,快速浏览它们表明,Harmony 将包含几个新特性,其中包括类的语法实现、函数的默认参数、新的字符串方法,以及在Math库中添加双曲三角函数。

许多浏览器制造商已经实现了一些 Harmony 特性,但是整体实现参差不齐,并且因制造商而异。在本书的大部分内容中,我们将把 JavaScript 作为 ECMAScript 5.1 的一种方言。在 ECMAScript 5 和 Harmony 重叠的地方,我们会注意到它们的区别,这样您就可以意识到潜在的支持陷阱。此外,在本书中,我们将使用“JavaScript”作为该语言及其实现的通用术语,除非我们需要引用特定的实现或标准本身。

由于 ECMA-262 的标准化影响,所有 JavaScript 的现代实现都非常相似。个别的实现会有所不同,特别是对于前沿的特性,但是核心标准是很好实现的。

JavaScript 实现 s

JavaScript 有几种不同的实现方式。例如,Adobe 的 Acrobat 文档系统实现了一个 JavaScript 版本,使用户能够在 Acrobat 文档中使用简单的脚本。JavaScript 引擎作为独立资源在 Windows、UNIX 和 Linux 上实现已经有一段时间了。在 1995 年首次引入 JavaScript 后不久,Netscape 在其企业服务器中包含了 JavaScript 的服务器端实现。今天,服务器端 JavaScript 最显著的实现是在 Node.js 软件系统中。

到目前为止,JavaScript 最常见的实现是在 web 浏览器中。web 浏览器的 JavaScript 引擎通常实现 ECMA-262 标准中规定的大多数功能。此外,浏览器经常用 ECMA 标准没有规定的其他特性来扩展 JavaScript。这些扩展中最值得注意的是文档对象模型(DOM ),它是由万维网联盟(W3C)维护的一个独立标准。重要的是要记住,DOM 和 JavaScript 是分开的、独立的标准,尽管 JavaScript 在浏览器中做的大部分工作都涉及到操纵 DOM。我们将在第三章更深入地讨论 DOM。

尽管 JavaScript 最初是一种基于浏览器的脚本语言,但是 JavaScript 的服务器端实现变得越来越普遍。在服务器端,JavaScript 实现将包括 ECMA-262 的大部分基本特性。而且,像基于浏览器的实现一样,服务器实现可以用其他特性扩展 JavaScript,比如库或框架。尽管服务器和浏览器的 JavaScript 实现在这些扩展特性上可能有所不同,但基本特性是相同的:无论是在浏览器中还是在服务器上实现,JavaScript Array对象都具有相同的方法和属性(当然,假设实现遵循 ECMA 标准)。

这使得您的 JavaScript 技能特别有价值。JavaScript 是少数同时拥有客户端和服务器端实现的语言之一,因此学习 JavaScript 是一项不错的投资。通过在服务器端使用 Node.js 以及客户端脚本,可以使用 JavaScript 作为主要语言来构建复杂的、数据驱动的应用,并提供丰富的用户交互。

可能在客户端和服务器端都使用 JavaScript 的两个最好的例子是微软的 Windows Azure 平台和 Windows 8 的 Windows 软件开发工具包(Windows SDK)。它们都支持使用 JavaScript 进行后端和前端实现,这使得用 JavaScript 构建 Windows 应用和利用微软平台的所有功能成为可能。

在本书中,我们不会讨论使用 Node.js 的服务器端 JavaScript,而是将重点放在 web 浏览器环境中的 JavaScript。

网络浏览器和 JavaScript

现代网络浏览器是复杂的软件。大多数人认为 web 浏览器是内容浏览器,可以说是“Web 上的窗口”。然而,对于理解 JavaScript 的程序员来说,web 浏览器变得更加强大:用户界面(UI) 平台。无论你是创建一个简单的网页还是一个复杂的数据驱动的应用,浏览器都是你的 UI 平台,JavaScript 是它使用的语言。

JavaScript 只是浏览器众多活动部件中的一个。在很高的层面上,浏览器由一堆独立的子程序(或称引擎)组成,每个子程序都有一个重要的功能:

  • UI 引擎:呈现给用户的实际可视界面,有地址栏、渲染窗口、后退和前进按钮、书签工具栏等等。
  • 浏览器引擎:在 UI 层和渲染引擎之间工作的控制器。
  • 渲染引擎:负责读取 HTML 文档及其相关素材(比如图像和级联样式表)并决定它们的外观。呈现引擎是 DOM 存在的地方。
  • 网络引擎:负责接入网络。
  • 数据持久引擎:管理应用的持久层,这是存储 cookies 的地方,也是 web 数据库和本地存储等 HTML 5 新特性存在的地方。
  • JavaScript 引擎:包括数据持久化、网络和渲染引擎的接口,可以观察和修改其中的任何一个或全部。

图 1-1 展示了 JavaScript 引擎是如何与浏览器的其他功能完全分离的,尽管它与浏览器的其他部分密切相关。

9781430246299_Fig01-01.jpg

图 1-1 。浏览器引擎堆栈

关于浏览器版本的一句话

在本书中,我们将使用 HTML5 语法作为我们的 HTML 标记。因此,一些例子在没有实现 HTML5 特性的旧浏览器上运行时会有问题。本书中的大多数例子都已经在 Chrome 的最新稳定版本中进行了测试,但也应该可以在最新版本的 Safari 、Firefox 和 Internet Explorer 10 中运行。

网页中的 JavaScript

Web 浏览器加载 JavaScript 或者作为文档本身的内容块(内联脚本)或者作为单独加载的链接脚本文件。

内联脚本使用<script>标签表示:

<script>
/* Your JavaScript here */
</script>

链接的脚本也是使用<script>标签添加的:

<script src="js/init-document.js"></script>

这指示浏览器获取被引用的文件,并将其直接提供给 JavaScript 引擎。

image 注意你必须同时使用开始和结束标签。HTML5 标准不允许使用自结束标记(尽管有些浏览器可能允许)。

您可以在 HTML 文档的头或正文中的任何位置包含内联或链接脚本。

执行顺序

所以现在我们在网页中包含了 JavaScript,但是当浏览器加载并解析文档时,实际上发生了什么呢?

事实证明,web 浏览器有一个显而易见的特定解析顺序:浏览器从顶部开始解析 HTML 文档,然后一路向下,当特定的素材到达浏览器时就加载它们。这意味着一个脚本(无论是内联的还是链接的)只能引用一些东西(样式、其他脚本、HTML 元素等等)。)在文档中位于它之上。

考虑清单 1-1 中的简单 HTML 页面。

清单 1-1。 基本 HTML 模板

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Developer's Guide</title>
    </head>
    <body>
        <h1>Hello World</h1>
    </body>
</html>

这将只是在浏览器中显示一个“Hello Word”消息。我们可以通过添加三个脚本来演示脚本执行的顺序,如清单 1-2 所示。

清单 1-2。 演示执行的顺序

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Developer's Guide</title>
        <script>
alert("This is the head.");
        </script>
    </head>
    <body>
        <script>
alert("This is the body, before the message.");
        </script>
        <h1>Hello World</h1>
        <script>
alert("This is the body, after the message.");
        </script>
    </body>
</html>

当载入浏览器时,这个页面会首先弹出一个提示窗口,上面写着This is the head。请注意,浏览器窗口本身中还没有任何内容;浏览器尚未解析文档的其余部分。

接下来,浏览器将向下移动到正文。下一条警告消息将会出现,但是浏览器窗口仍然是空的。然后浏览器窗口会出现“Hello World”的标题,然后会出现最后一条提示信息。

这个例子不仅展示了执行的顺序,还展示了一个重要的事实,即 JavaScript 可能会阻止对文档的解析。在我们的例子中,我们使用了阻塞函数alert(),但是如果我们在头部加载一个复杂的脚本,需要一些时间来下载和解析,它将阻塞对文档其余部分的解析。类似地,正文中的脚本会导致显示整个文档的延迟。对于复杂的 JavaScript 应用,解析顺序和阻塞的组合会导致一些不良影响。我们将在第四章中探讨一些克服这些问题的技巧。

简短的题外话:理解和运行示例

正如我们提到的,我们不打算在这一章中涉及语法的细节——那是本书其余部分的内容。但是在我们深入研究之前,我们确实想了解一些关于语法和运行这些示例的重要细节:

  • 变量声明:在这些例子中,我们将使用var关键字来声明变量。语法很简单:var variableName声明变量variableName,并赋予它特殊的值undefined。可选地,您可以为您的变量提供一个值作为声明的一部分:var variableName = myValue将声明variableName并给它赋值myValue。在本章后面的“JavaScript 中的作用域”一节中,你会学到更多关于var关键字的知识,但是在深入之前,我们想简单地涉及一下。
  • 点符号 : JavaScript 使用点符号访问对象上的属性和方法:myObject.propertyName引用myObject上的propertyNamemyObject.methodName()调用myObject上的methodName
  • alert:浏览器为 JavaScript 提供了一个alert函数,提供了一种快速显示字符串的方法。当您调用alert方法并将一个字符串作为参数传递给它时,浏览器执行以下步骤:
    • a.它会暂停脚本的执行。
    • b.它会弹出一个小窗口,显示您提供的字符串。弹出窗口包括一个标记为 OK 的按钮,单击该按钮可以关闭弹出窗口。
    • c.当弹出窗口关闭时,浏览器会在警报后的下一条语句处继续执行脚本。
  • 这使得 alert 成为一种轻松检查脚本变量和属性的方法。它还有一个优点,几乎可以在所有现存的浏览器上工作,甚至是非常旧的浏览器。

运行示例

有几种方法可以运行这些示例。最简单的方法可能是使用清单 1-1 中的模板,并在< H1>标签后添加一个< script>标签。然后将示例复制并粘贴到script标签中,保存文件,并将其加载到浏览器中。

许多浏览器还提供了 JavaScript 控制台,您可以使用它直接输入示例。但是,JavaScript 控制台会在您按 Enter 键时评估您的代码,我们的许多示例都被分成多行。我们不建议使用控制台,但如果你想尝试一下,在你最喜欢的浏览器上访问控制台(通常 Control-或 Option-Shift-J 是键盘快捷键,但也有所不同);例如:

  • 在 Chrome 中,你可以通过“定制和配置谷歌浏览器”菜单访问控制台。选择工具images JavaScript 控制台。您还会看到用于访问控制台的键盘快捷键(Windows 为 Control-Shift-J)。您可以在此处直接键入代码示例。
  • 在 Firefox 中,选择工具images Web Developer images错误控制台。您可以在标有“代码”的框中键入代码示例。如果要使用控制台,请确保在按 Enter 键之前键入完整的有效语句。要了解 JavaScript 中的语句是由什么组成的,你可以跳到第二章。

JavaScript 的三个困难特性

正如我们提到的,人们发现 JavaScript 的三个主要特性有问题:它实现继承的方式,它实现变量范围的方式,以及它实现数据类型的方式。我们不会回避这些特性,而是直接进入它们。

原型遗传

JavaScript 是一种面向对象的语言,但是,与大多数面向对象的语言不同,它的继承基于原型而不是类。这种差异经常被误解,并且很难解释。

最大的区别是在 JavaScript 中没有类这种东西。您可以用 JavaScript 构建类仿真,但是现成的 JavaScript 没有类。只有对象,你从其他对象实例化新对象。

JavaScript 中的继承是通过每个对象的一个特殊属性来处理的,这个属性叫做prototypeprototype属性引用它从其父对象继承的所有属性和方法——包括它的prototype。当您试图访问一个对象的属性或方法时,JavaScript 首先查看它是否存在于本地副本中。如果没有,JavaScript 会检查prototype。如果在prototype中没有找到请求的条目,它会检查prototype's prototype,依此类推,一直到继承链的顶端。

您可以覆盖prototype中的属性和方法。这将从本质上打破原型链,因此对象和任何从它实例化的子对象将继承覆盖,并且不再进一步搜索原型链。

在某种程度上,prototype链可以被认为是一个单向链表。prototype是对列表中前一个元素的引用。

原型继承的一个主要方面是,如果你改变一个对象的继承属性或方法,它的子对象也会反映这种改变,即使在它们被创建之后。这是因为子原型都引用了父属性和方法。

原型继承的另一个主要方面是,您可以更改任何全局对象的prototype,从而向它们添加您自己的属性和方法——甚至覆盖它们现有的属性和方法。但是请注意,覆盖全局对象的现有属性和方法可能是危险的。请记住,那些属性和方法是由标准定义的,所以如果您让它们做其他事情,那么您可能会失去遵循标准的好处。因此,通常认为覆盖这些属性和方法是不好的做法,除非您非常小心自己在做什么。

清单 1-3 提供了一个非常简单的原型继承的例子。

清单 1-3。 原型继承的简单例子

var myParent = {
    a: 10,
    b: 50
}

var myChild = Object.create(myParent);
var myGrandChild = Object.create(myChild);

alert(myGrandChild.a); // will alert 10
myParent.a = 20;
alert(myGrandChild.a); // will alert 20
alert(myChild.a); // will alert 20

一会儿我们会多谈一点关于Object.create 的语法;现在,只需关注脚本正在做的事情:首先,它创建一个具有属性ab的父对象,然后从该父对象创建一个子对象,并从子对象创建一个孙对象。我们现在有三个对象,每个都继承自其父对象。当我们检查孙儿对象的值a时,JavaScript 遍历prototype链,直到在父对象中找到属性。

由于prototype只是一个引用,向父对象添加属性会立即使它们在子对象中可用,如清单 1-4 所示。

清单 1-4。 给父节点添加一个属性使其对子节点可用

var myParent = {
    a: 10,
    b: 50
}

var myChild = Object.create(myParent);
var myGrandChild = Object.create(myChild);

myParent.c = "hello";
alert(myChild.c); // will alert "hello"
alert(myGrandChild.c); // will alert "hello"

这个例子类似于清单 1-3 ,但是我们在实例化了子对象之后给父对象添加了一个新的属性。

在一些浏览器中,你甚至可以直接检查prototype,因为它们在对象上提供了一个__proto__属性,你可以通过控制台查看(见图 1-2 )。

9781430246299_Fig01-02.jpg

图 1-2 。在 Chrome 的控制台中查看原型

在图 1-2 中,我们看到myGrandChild是一个对象,展开后看到它有一个__proto__属性。当我们展开它时,我们看到它有另一个__proto__属性,当我们展开它时,我们找到了ab属性。还有另一个__proto__属性,它引用全局对象 Object…因此,我们所有的对象都继承了全局对象 Object 的所有属性和方法。

原型继承的想法很简单,但是却很容易被误解。更复杂的问题是,在早期版本的 JavaScript 中,从其他对象创建新对象的方法和语法,如清单 1-5 所示,非常类似于传统继承语言的语法。

清单 1-5。 旧语法用于创建新对象和修改原型

function myObject() {}; // constructor function
var myInstance = new myObject; // instantiate a new instance
alert(myInstance.prop1); // will alert "undefined" because it doesn't exist
myObject.prototype.prop1 = "Here I am";
alert(myInstance.prop1); // will alert "Here I am"

这不仅有些不雅,关键字new还让人们从古典继承的角度思考问题,这只会让问题更加混乱。并且它要求任何你计划用来创建子对象的对象都是一个函数。

ECMAScript 5 在全局对象上定义了一个新属性:Object.create()。此方法将对象作为参数,并返回一个以参数对象为原型的新对象。这种语法更加整洁,如清单 1-6 所示,也有助于澄清继承链,并且消除了直接访问prototype property的需要。

清单 1-6。 改进创建新对象的语法

var myObject = {};
var myInstance = Object.create(myObject);
alert(myInstance.prop1); // will alert "undefined" because it doesn't exist
myObject.prop1 = "Here I am";
alert(myInstance.prop1); // will alert "Here I am"

这种新方法适用于现代浏览器,但是如果你发现自己在使用一个不支持这个标准版本的旧 JavaScript 引擎(最明显的是 Internet Explorer 8 和更早的版本),你总是可以使用清单 1-7 中的代码片段来提供相同的功能。

清单 1-7。 一种为不存在的对象添加创建方法的方法

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

清单 1-7 检查Object.create是否存在,如果不存在,就把它添加到全局对象 Object 中。这是一个安全扩展全局对象的好例子。

垫片

清单 1-7 是所谓的填充多填充的一个例子,这些术语指的是为特定环境添加缺失功能或修复不正确实现的小脚本。清单 1-7 弥补了 JavaScript 的一个缺点;还有针对各种 CSS 问题甚至 HTML 问题的垫片。

JavaScript 库中通常包含垫片——事实上,许多库最初只是各种垫片的集合。

JavaScript 中的作用域

JavaScript 的另一个经常被误解和中伤的特性是它的作用域:JavaScript 如何限制和允许访问它的变量。因为 JavaScript 在许多方面与 C 语言非常相似,所以很自然地认为它使用了类似 C 语言的块级作用域,清单 1-8 给出了一个例子。

清单 1-8。C 中的块级作用域

#include <stdio.h>
int main() {
  int x = 1;
  printf("%d, ", x); // 1
  if (1) {
    int x = 2;
    printf("%d, ", x); // 2
  }
  printf("%d\n", x); // 1
}

在 C 中,每个代码块(if语句、for循环等)。)是它自己的作用域:在一个作用域中定义的变量在另一个作用域中不可用。假设 JavaScript 采用块级作用域是合乎逻辑的,因为它使用的语法非常类似于 C。。但事实并非如此。

相反,JavaScript 使用所谓的函数作用域,这意味着作用域是由函数声明的。函数中定义的变量在该函数中的任何地方都是可用的,甚至在其他块中,如if语句、for循环或嵌套函数。作为示范,下面的清单创建了一个函数范围,并在:中测试变量

清单 1-9。 演示嵌套的功能范围

function testScope() {
    var myTest = true;
    if (true) {
        var myTest = "I am changed!"
    }
    alert(myTest);
}

在清单 1-9 中显示的例子创建了一个简单的testScope函数。在这个函数中,我们声明了一个变量myTest,这给了它在函数中任何地方都可用的范围。然后我们在一个if语句块中重新声明这个变量,并给它一个不同的值。最后,我们测试看看结果是什么:脚本将提醒I am changed!

在 C 或另一种具有块级作用域的语言中,一个类似的例子会警告true,因为if语句中的myTest重新声明在作用域上将被限制到该块。

如果我们试图在testScope函数之外访问myTest变量,将会失败,如清单 1-10 所示。

清单 1-10。 演示功能范围

function testScope() {
    var myTest = true;
    if (true) {
        var myTest = "I am changed!"
    }
    alert(myTest);
}

testScope(); // will alert "I am changed!"
alert(myTest); // will throw a reference error, because it doesn't exist outside of the function

testScope函数之外,myTest不存在。你可以让它存在,如清单 1-11 所示。

清单 1-11。 展示全局范围

var myTest = true;
function testScope() {
    if (true) {
       var myTest = "I am changed!"
    }
    alert(myTest);
}

testScope(); // will alert "I am changed!"
alert(myTest); // will alert "I am changed!"

通过在testScope函数之外定义myTest变量,它变得随处可用。这就是所谓的全球范围。全局函数和变量随处可用。这是 JavaScript 的一个非常强大的特性,但是很容易被滥用。一般来说,混淆全局范围被认为是不好的做法,主要是因为它会导致同名变量在脚本执行时互相碰撞对方的值,从而导致各种难以调试的问题。相反,建议尽可能将变量限制在私有范围内。

限制范围

到目前为止,我们已经用关键字var小心地声明了我们的新变量。但是var关键字在 JavaScript 中是可选的;你可以简单地通过提供一个值来声明一个新变量,如清单 1-12 所示。

清单 1-12。 声明一个没有 var 关键字的变量

var myNewVar = 1; // Using var to declare a variable.
myOtherNewVar = 2; // var is optional.
alert(myNewVar); // will alert 1
alert(myOtherNewVar); // will alert 2

然而,当你声明一个没有var关键字的变量时,JavaScript 假设你的意思是你在一个更高的作用域中定义了这个变量,并且你想要访问这个变量。因此 JavaScript 将查找包含范围,看看变量是否是使用关键字var声明的。如果不是,JavaScript 会继续查找作用域链,直到到达全局作用域。如果它到达了全局范围,仍然没有找到使用var关键字的声明,JavaScript 将为你把变量赋给全局范围,如清单 1-13 所示。

清单 1-13。 杂糅全局范围

function testScope() {
    myTest = true; // now myTest is global.
    alert(myTest);
}
testScope(); // will alert "true"
alert(myTest); // will alert "true" as well, because now myTest is global.

JavaScript 的这个特性叫做隐含全局作用域。它基本上意味着没有明确限定范围的变量被假定为全局变量。

为了限制变量的范围,在声明中使用var关键字,如清单 1-14 所示。使用var关键字指示 JavaScript 将变量的范围限制为当前变量。这可以防止意外弄乱全局范围。

清单 1-14。 用 var 限制范围

function testScope() {
    var myTest = true;
    function testNestedScope() {
        var myTest = false;
        alert(myTest);
    }
    testNestedScope();
    alert(myTest);
}

testScope(); // will alert false, and then true.

在清单 1-14 中,我们在不同的范围内定义了两个不同的myTest变量。在testNestedScope函数中,myTest有一个覆盖更高作用域的局部定义。这可以防止两个不同名称的变量互相取值。

你可能想知道如果我们交换testNestedScope函数中的两行会发生什么,如清单 1-15 所示——换句话说,如果我们在一个变量被定义在给定的作用域之前尝试访问它,会发生什么?

清单 1-15。 在给定范围内定义变量之前访问变量

function testScope() {
    var myTest = true;
    function testNestedScope() {
        alert(myTest);
        var myTest = false;
    }
    testNestedScope();
    alert(myTest);
}

testScope(); // will alert "undefined", and then true.

清单 1-15 将警告undefined然后是true。为什么呢?也就是说,testNestedScope里的第一条线为什么不报警true?毕竟myTest在更高的范围内设置为true,那么为什么在那里不可用呢?

原因是我们通过用var关键字定义变量testNestedScope来限制myTest的范围。但是当我们在给它一个值之前访问它时,它被设置为“未定义”所以这段代码相当于清单 1-16 中的代码。

清单 1-16。 更明确的等价于清单 1-15 中的

function testScope() {
    var myTest = true;
    function testNestedScope() {
        var myTest;
        alert(myTest);
        myTest = false;
    }
    testNestedScope();
    alert(myTest);
}

testScope(); // will alert "undefined", and then true.

清单 1-16 通过在变量作用域的最开始明确声明一个没有值的变量,说明了在清单 1-15 中发生了什么。在 JavaScript 中,在给定范围内声明的任何变量在该范围内的任何地方都是可用的,甚至在它被赋值之前。JavaScript 的这个特性通常被称为提升:变量被“提升”到声明它的作用域的开始。由于提升的原因,在 JavaScript 中,在变量作用域的开始显式声明变量通常被认为是一个好的做法,即使你很久以后才访问它们。

关闭

在大多数语言中,一旦一个函数返回,它的所有局部变量都被解除分配——从内存中移除并且不再可用。在 JavaScript 中,这不是必须发生的。由于 JavaScript 的动态特性和作用域规则,您可以编写这样的代码,即使在函数执行完毕后,函数中的局部变量仍然可用。考虑清单 1-17 中的例子。

清单 1-17。

function greet(myName) {
    var myAlertString = "Hello " + myName; // Local variable
    function doAlert() {
        alert(myAlertString);
    }
    return doAlert; // return the new function
}

var greetKitty = greet("Kitty"); // greetKitty is now a function
greetKitty(); // will alert "Hello Kitty"

清单 1-17 是一个有些做作的例子,这里有一些不寻常的事情,所以让我们一次看一个。第一件奇怪的事情是我们从函数中返回一个函数。这似乎有点奇怪,但在 JavaScript 中并不罕见。请记住,在 JavaScript 中,函数是对象,所以您可以返回它们,甚至可以像其他对象一样轻松地将它们赋给变量。

我们的greet函数将一个名称作为参数,在一个局部变量中将它连接成一个字符串,然后定义一个局部函数来警告该字符串。然后它返回本地函数。当我们调用greet函数时,我们将返回的函数赋给一个变量,然后执行返回的函数。

这种情况的特别之处在于,当我们将其结果赋给greetKitty变量时,greet函数被调用并完全执行。在大多数语言中,myAlertString变量会被释放并且不可用。但在 JavaScript 中,它仍然存在,因为我们已经创建了一个特定的情况,它需要保持在那里,以便当我们执行返回的函数时,一切都将按预期进行。换句话说,返回的函数及其直接的非局部函数作用域都被保留,即使创建它们的函数已经结束运行。这是 JavaScript 中变量作用域的副作用:解释器会维护一个作用域,直到不再需要它。

这也适用于我们创建的私有范围。清单 1-18 通过对另一只猫说“你好”来演示这一点。

清单 1-18。 一个保持着隐私的范围

function greet(myName) {
    var myAlertString = "Hello " + myName; // Local variable
    function doAlert() {
        alert(myAlertString);
    }
    return doAlert; // return the new function
}

var greetKitty = greet("Kitty"); // greetKitty is now a function
greetKitty(); // will alert "Hello Kitty"
var greetMax = greet("Max"); // greetMax is now a function
greetMax(); // will alert "Hello Max"
greetKitty(); // will alert "Hello Kitty"

greetMaxgreetKitty函数都可以访问它们自己维护的作用域,这些作用域对于彼此和全局作用域都是私有的。

这个特殊例子的结果是由 JavaScript 的作用域规则造成的。如果我们允许使用一个全局变量,如清单 1-19 所示,那么我们就不再为每个函数保留一个私有的范围。

清单 1-19。 使用全局变量

function greet(myName) {
    myAlertString = "Hello " + myName; // Now a global variable
    function doAlert() {
        alert(myAlertString);
    }
    return doAlert; // return the new function
}

var greetKitty = greet("Kitty"); // greetKitty is now a function
greetKitty(); // will alert "Hello Kitty"
var greetMax = greet("Max"); // greetMax is now a function
greetMax(); // will alert "Hello Max"
greetKitty(); // will alert "Hello Max"
var greetLenore = greet("Lenore");
greetLenore(); // will alert "Hello Lenore"
greetKitty(); // will alert "Hello Lenore"
greetMax(); // will alert "Hello Lenore"

在清单 1-19 中,我们通过不对myAlertString变量施加范围限制来改变这种情况。这允许 JavaScript 暗示它是一个全局变量,每次调用greet函数时都会被覆盖。

即使在父函数已经执行完之后,仍然保持一个函数及其父作用域被称为闭包。闭包是 JavaScript 极其重要和强大的特性,我们将在本书中广泛使用它们。

因为您可以使用它们来加强隐私,所以闭包对于封装功能和管理范围非常有用。它们在一些最常见的 JavaScript 模式中扮演着重要的角色,我们将在本章后面介绍这些模式。

闭包非常强大,但是它们有一个重要的缺点:因为闭包需要浏览器为函数及其作用域保留分配的内存,一旦不再需要闭包,浏览器有时可能不会将所有的内存返回给系统。这种情况的主要症状是内存泄漏:随着脚本继续在浏览器中执行,浏览器消耗越来越多的内存,最终耗尽所有可用内存。内存泄漏假设浏览器不应该消耗越来越多的内存,或者消耗得比应该消耗的要快。

老版本的浏览器由于关闭而存在严重的内存泄漏问题。现代浏览器效率更高,但仍有问题。您应该在浏览器运行您的脚本时监控它的内存使用情况,以确保它没有问题。

属于那种软弱的类型,嗯?

正如我们前面提到的,JavaScript 是弱类型的。这意味着,如果表达式中存在类型不匹配问题,JavaScript 将根据自己的规则解决它。以清单 1-20 中的代码为例。

清单 1-20。 弱打字演示

var myNumber = 5; // Integer
var myString = "7"; // String
var myResult = myNumber + myString; // Type mismatch: integer + string = what?
alert(myResult); // Will alert "57"

解析这个脚本时,静态类型语言会抛出一个错误。但是 JavaScript 自己解决了类型不匹配的问题,允许程序继续运行而不会崩溃。

许多人认为弱类型是一个缺点,事实上,对于 JavaScript 开发新手来说,弱类型很容易犯很多错误。但是,一旦掌握了 JavaScript 的类型规则,您就可以充分利用这一特性,创建比用等效的强类型语言创建的脚本更小、更优雅的脚本。

基本数据类型和原语

我们将通过回顾 JavaScript 的基本数据类型来开始我们对 JavaScript 类型的探索。是的,尽管它是弱类型的,JavaScript 实际上有数据类型,只是比典型的数据类型更广泛。四种基本数据类型是:

  • 布尔:为真或为假的变量或表达式。
  • 数字:JavaScript 中所有的数字都是 64 位浮点数。
  • 字符串:任意字符的字符串。
  • 对象:属性和方法的集合。

JavaScript 使用这些数据类型作为所有类型管理的基础。要确定 JavaScript 中任何东西的类型,使用typeof操作符,如清单 1-21 所示(参见第七章了解关于typeof操作符的全部细节)。

清单 1-21。 使用 typeof 运算符

var myArrayOfThings = ["hello", 5, true, {}];
for (var i = 0; i < myArrayOfThings.length; i++) {
    alert(typeof myArrayOfThings[i]);
}
alert(typeof myArrayOfThings);

这将依次警告“字符串”、“数字”、“布尔”、“对象”和“对象”。(是的,在 JavaScript 中,数组就是对象。)注意,typeof将为函数返回“Function ”,尽管在 JavaScript 中没有函数类型。

另外,JavaScript 有原语的概念:非对象简单值。JavaScript 原语是构建更复杂数据类型的基础。他们是

  • 布尔:关键字truefalse本身就是布尔原语。
  • Null :关键字null
  • :一个数本身就是一个数本原。
  • 字符串:用引号括起来的字符串是字符串原语。
  • 未定义的:一个特殊的值,代表一个用var关键字创建的变量,但是没有给它赋值。

需要注意的是,在 JavaScript 中,任何不是原语的东西都是对象。例如,函数就是对象。

您还可以在 JavaScript 中的原语和对象之间进行转换。你会注意到布尔、数字和字符串原语有匹配的全局对象(见第五章关于布尔、数字和字符串全局对象以及如何使用它们的细节)。考虑在清单 1-22 中显示的例子。

清单 1-22。 如此等等,到底是不是原始人?

var myString = "hello there" // primitive
alert(myString.length); // will alert 11\. . .but length is a property of the String object
alert(typeof myString); // will alert "string"

这段代码片段提醒11,字符串的长度。但是为什么呢?如果“hello there”确实是一个原语,我们怎么能访问myString.length

我们之所以能做到这一点,是因为在幕后,JavaScript 正在将我们的原始值转换为其关联的对象,从而使我们能够访问 String 的所有属性和方法。这种转换是短暂的,这就是为什么typeof myString仍然产生“字符串”而不是“对象”

这种幕后的转换在 JavaScript 中经常发生,这也是充分理解 JavaScript 如何做到这一点非常重要的另一个原因。

JavaScript 中的类型转换

现在我们已经定义了所有的基础知识,我们可以看看 JavaScript 实际上是如何处理类型不匹配的。JavaScript 有一组函数用于处理从一种类型到另一种类型的转换:toPrimitive()toNumber()toBoolean()。这些函数是抽象的,意味着它们是 JavaScript 内部工作的私有函数,不能被脚本直接调用。

toPrimitive()方法接受一个input参数,也可以接受一个可选的preferredType参数。它将非基元(也就是说,对象)转换为最接近的相关基元类型。如果input参数可以分解成多个原始类型,那么preferredType参数可以用来指定选择哪一个。根据input参数类型:,表 1-1 总结了toPrimitive()遵循的规则

表 1-1。规则为原始

input参数类型 结果
目标 如果valueOf()返回一个原语,则返回该原语;否则,如果toString返回原始值,则返回该值;否则,抛出一个错误
其他一切 无变化

toNumber()方法接受一个input参数并试图将其转换成一个数字,如表 1-2 所示。

表 1-2。【toNumber 规则

input参数类型 结果
布尔代数学体系的 1如果为真,+0如果为假
+0
数字 无转换
目标 toNumber(toPrimitive(object))
线 类似于parseInt()(参见第五章的中的“其他全局函数和变量”以获得对parseInt()的完整解释),除非原始值包含除数字、单个小数或前导+或-以外的任何内容,否则它返回NaN
不明确的 NaN

toBoolean()方法接受一个input参数,并试图将其转换为truefalse,如表 1-3T6 所示。

表 1-3。托布尔的规则

input参数类型 结果
布尔代数学体系的 无转换
false
数字 如果–0+0NaN,返回false;否则,返回true
目标 true
线 如果字符串为空,则返回false;否则,返回true
不明确的 false

脚本中最常发生类型转换的地方是在评估一个if (Expression) Statement条件时,以及使用==比较时。在有条件的情况下,使用toBoolean()将表达式简化为布尔值。==的类型转换算法是 ECMA-262 标准定义的简单算法,在表 1-4 中有概述。

表 1-4。类型转换算法为==运算符

x 的类型 y 的类型 结果
不明确的 true
不明确的 true
数字 线 x == toNumber(y)
线 数字 toNumber(x) == y
布尔代数学体系的 任何的 toNumber(x) == y
任何的 布尔代数学体系的 x = toNumber(y)
字符串或数字 目标 x == toPrimitive(y)
目标 字符串或数字 toPrimitive(x) == y

从这个算法中有几个重要的收获:首先,nullundefined彼此相等,其他都不相等,其次,最终所有其他的都被简化为数字以便于比较。

尽管这个算法实际上非常简单,但许多人不理解它,结果发现==的行为令人困惑。人们发现这非常令人困惑,以至于一个普遍推荐的 JavaScript 编码最佳实践是避免使用==(和!=),而是始终使用===(和!==)。清单 1-23 是一个例子的版本,这个例子经常被引用作为在 JavaScript 中避免使用==的理由。

清单 1-23。JavaScript 中令人困惑的类型转换

if ("Primitive String") {
  alert("Primitive String" == true);
  alert("Primitive String" == false);
}

这段代码将首先向false发出警报,然后再次向false发出警报,因此不难理解为什么这可能会导致人们沮丧地举手投降。让我们一步一步来看:

  1. if语句中,我们看到 JavaScript 在"Primitive String"上应用toBoolean(),其计算结果为true,因此执行移动到代码块中。
  2. 我们将算法应用于"Primitive String" == true,它告诉我们检查"Primitive String" == toNumber(true),这与"Primitive String" == 1相同。
  3. 我们查toNumber("Primitive String") == 1,和NaN == 1一样,都是false
  4. 我们对"Primitive String" == false进行同样的处理,在应用了几次类似于步骤 2 的算法后,我们得到了NaN == false,也就是false

现在我们理解了规则,清单 1-23 的结果实际上非常有意义。

考虑一下清单 1-24 中显示的通用代码模式,其中我们试图为函数中的参数提供一个默认值。

清单 1-24。 一个常见的错误:检查某物是否未定义或为空

function myFunction(arg1) {
    // Check if arg1 wasn't provided
    if ((arg1 === undefined) || (arg1 === null)) {
        // provide default value for arg1 here
    }
    // Continue with function. . .
}

清单 1-24 中常见的错误表明对一个最基本的类型转换规则缺乏理解:nullundefined彼此相等,除此之外别无其他。清单 1-25 展示了编写这段代码的一种更好的方法。

清单 1-25。

function myFunction(arg1) {
    // Check if arg1 wasn't provided
    if (arg1 == null) {
        // provide default value for arg1 here
    }
    // Continue with function. . .
}

您本来可以检查arg1 == undefined,但是因为undefined是一个变量,所以它有两个缺点:首先,与undefined比较需要范围链查找,这通常没什么大不了的,但是如果您深埋在范围中和/或在一个长循环中实现检查,它可能会影响性能;其次,它的值有可能被意外覆盖(这种情况很少发生,但确实会发生)。和null比更安全。

放在一起:两种常见的模式

每种语言都有常用的模式,JavaScript 也不例外。JavaScript 的通用模式利用了我们在本章中讨论的一个或多个特性,所以它们是这些特性的优秀的实际例子,也说明了它们有多么强大。第一种模式是一种语法模式,您将会在 JavaScript 中经常看到,并且您自己也会多次使用。第二种是同样非常常见的实现模式,它将帮助您开始将 JavaScript 组织成可管理的模块。

立即执行函数表达式

在本章的其他例子中,我们已经在函数中创建了函数,然后执行它们。有没有可能定义一个函数,然后立即执行它,而不必单独调用它?

在 JavaScript 中,调用一个函数的符号是在函数名后(或在它被赋值的变量名后)放置一对圆括号——参见第二章了解更多关于这种区别的细节。所以,如果我们只是在函数声明后放一对括号,会执行它吗?例如,清单 1-26 中的代码可以工作吗?

清单 1-26。 试图立即调用一个函数

function greet(myName) {
    var myAlertString = "Hello " + myName; // Local variable
    function doAlert() {
        alert(myAlertString);
    }()
}

greet("Kitty");

答案是,这个不行。当 JavaScript 解释器看到关键字function时,它认为后面是一个要添加到作用域中的声明,而不是要计算的表达式。你必须明确地告诉解释器你的函数是一个要被求值的表达式,你可以用圆括号把它括起来,如清单 1-27 所示。

清单 1-27。 立即调用功能

function greet(myName) {
    var myAlertString = "Hello " + myName; // Local variable
    (function doAlert() {
        alert(myAlertString);
    })()
}

greet("Kitty");

这实际上会像你所期望的那样工作。你甚至不必命名你的函数,如清单 1-28 所示。

清单 1-28。 立即调用匿名功能

(function() {
    // do stuff here
})();

您还可以将变量传递到调用中,如清单 1-29 所示。

清单 1-29。 传递变量到一个立即被调用的匿名函数中

(function(var1, var2) {
    // do stuff here
})(myExternalVar1, myExternalVar2);

立即调用的匿名函数非常有用,因为它们提供了一种利用闭包和管理作用域的方法。表达式中的所有内容都是私有的,除非你明确地将某些内容返回到全局范围,这样可以更容易地将全局范围清除掉不必要的混乱。立即调用的匿名函数模式在整个 JavaScript 开发中使用,尤其是在模块模式中。

模块模式

假设您正与许多其他开发人员一起开发一个大型 JavaScript 应用。您需要一种方法来封装代码段,以便它们可以有一个私有的名称空间,这样您就可以避免与现有代码的冲突。你会怎么做?当然是模块模式。

模块模式使用一个立即调用的函数为所有封装的代码创建一个闭包。您可以拥有私有成员,甚至可以发布公共 API。基本模式如清单 1-30 所示。

清单 1-30。 模块模式

var Module = (function() {
    var _privateVariable = "This is private",
        _otherPrivateVariable = "So is this",
        public = {}; // This object will be returned
    function privateMethod() {
        alert("This method is private as well");
    }

    public.publicProperty = "This is a public property";
    public.publicMethod = function() {
        alert("This is a public method");
    }
    return public;
})()

alert(Module._privateVariable); // will alert "undefined"
// Module.privateMethod(); // would throw an error if we let it run
alert(Module.publicProperty); // will alert "This is a public property"
Module.publicMethod(); // will alert "This is a public method"

模块模式是使用闭包来管理范围的一个很好的例子。在模块中,有一个独立的私有作用域,不会被修改。

这还不是全部。您甚至可以通过直接调用的函数来重新处理模块,从而轻松地扩展模块。你所要做的就是将原始模块作为一个参数传递给新的立即被调用的函数,如清单 1-31 所示。

清单 1-31。 扩展模块

var Module = (function(oldModule) {
    oldModule.newMethod = function() {
        alert("This is a new method!");
    }
    return oldModule;
})(Module)

你也可以在模块上创建子模块,如清单 1-32 所示。

清单 1-32。 创建子模块

Module.sub = (function() {
    var _privateSubVariable = "This is a private variable in the submodule",
    public = {};
    public.publicSubVariable = "This is a public variable in the submodule";
    return public;
})();

因为它充分利用了 JavaScript 的动态特性,所以模块模式非常灵活。事实上,如果您看一看现代 JavaScript 库(例如 jQuery)的源代码,您会发现其中许多都是使用这种模式构建的。

摘要

在这一章中,我们正面解决了人们认为 JavaScript 最难的问题。我们没有回避许多人因为这些事情不喜欢 JavaScript 的事实,我们解释了 JavaScript 的历史和持续的演变,以便您理解 JavaScript 是如何结束的。阅读完本章后,您现在应该了解以下内容:

  • 人们在学习 JavaScript 时最头疼的三件事是作用域、继承和类型。
  • JavaScript 的继承是原型的,而不是基于类的。
  • JavaScript 的范围是基于函数而不是代码块的。
  • JavaScript 的作用域和函数性质允许您创建闭包。
  • JavaScript 以非常具体和明确定义的方式处理类型。

您现在还应该熟悉立即执行的函数表达式和模块模式,以及模块模式如何使用闭包来维护范围和加强隐私。

有了这一章,你就可以更深入地研究 JavaScript 的具体细节了。在下一章中,我们将涵盖我们在本章中忽略的细节,从表达式和语句到对象,一直到函数和流控制。

二、JavaScript 螺母和螺丝

在第一章中,我们讲述了 JavaScript 的一些基础知识。我们深入研究了一些人们在学习语言时会纠结的概念。不过,我们并没有真正把语言作为一个整体来处理,这就是我们现在要做的。在这一章中,我们将深入到我们在第一章中忽略的细节中,并了解这门语言的具体细节。我们还将更详细地讨论我们在《??》第一章中提到的一些事情。

这一章将为你提供 JavaScript 语言的坚实基础,并且以一种既能让语言新手理解,又能让有经验的 JavaScript 开发人员有价值的参考的方式来完成。我们的希望是,随着您 JavaScript 开发技能的进步,您会参考这一章来提醒自己一些基础知识,并更深入地钻研特定的主题。

我们将首先回顾一些格式化 JavaScript 代码的基本问题,尤其是与本书中的例子相关的问题。然后我们将讨论表达式和语句,这是 JavaScript 的两个最基本的构件,所有的 JavaScript 程序都是从这两个构件构建的。有了这个基础,我们就可以讨论用操作符创建更复杂的语句了。我们将讨论变量以及如何在 JavaScript 程序中管理它们。然后我们将讨论对象和数组,这将为您提供所有其他内容的构建模块。然后我们将深入讨论函数:它们是什么,以及如何制作它们,我们将获得一些关于 JavaScript 动态本质的重要见解。最后,我们将讨论如何用条件和循环来控制我们的程序。

到本章结束时,你应该对 JavaScript 的词汇结构和语法有一个坚实的理解,并且应该对使用它的基本结构进行流控制和功能感到舒适。

image 在本章中,我们将引用甚至直接引用 ECMA-262 标准,其当前版本是 ECMAScript 语言规范 5.1 版。我们鼓励您在www.ecma-international.org/ecma-262/5.1/探索标准本身(它也提供了一个可下载的 PDF 版本的链接),因为这是扩展您对 JavaScript 理解的一个极好的方式。

格式化 JavaScript 代码

格式化代码是众多不可避免地导致一屋子愤怒的开发人员相互争吵的主题之一。(有一次,我看到有人在关于空格缩进和制表符缩进的争论中差点把椅子扔了出去。)尽管这是一个敏感的话题,但如果没有至少为未来的争论打下基础,以及定义我们将在本书中使用的约定,这种引用将是不负责任的。

一般来说,JavaScript 使用类似 C 的格式。最值得注意的是,JavaScript 使用花括号({ } ) 来表示代码块,比如循环或逻辑流控制。

JavaScript 还使用两种样式的注释分隔符。双斜杠(//)是单行分隔符,表示从该点到行尾的一切都是注释。JavaScript 还使用/*来表示多行注释的开始和*/来表示结束。包含在这些分隔符中的任何内容,不管新行是什么,都被视为注释。

空白,包括缩进,在很大程度上是不重要的。引用 ECMA-262 标准的第 7.2 节:“空白字符用于提高源文本的可读性,并将标记(不可分割的词汇单元)彼此分开,但在其他方面并不重要。”JavaScript 不在乎你是否用制表符或空格缩进,甚至根本不在乎你是否缩进。类似地,JavaScript 对新行没有要求。事实上,为了减小文件大小,通过删除所有空格并将所有内容放在一行来“压缩”JavaScript 是很常见的(参见第四章了解更多关于压缩 JavaScript 的信息)。

JavaScript 使用分号(; ) 来终止语句。然而,分号可以被认为是可选的,因为 JavaScript 解释器实践自动分号插入(ASI),这意味着它们试图通过在需要时自动插入分号来纠正没有分号将不起作用的代码。因此,您可以选择不使用许多(甚至任何)分号来编写您的 JavaScript,而是依赖 ASI。传统上,显式使用分号来终止语句被认为是一种最佳实践。然而,随着 CoffeeScript 等新的元脚本语言的出现,许多人现在更喜欢编写尽量少用分号的简洁代码,而是尽可能依赖 ASI。

从实践的角度来看,这两种方法都是可以接受的,因为这两种方法都有助于生成一致的、功能性的代码。然而,正如任何涉及编程风格的事情一样,最近有许多关于显式使用分号还是依赖 ASI 的激烈争论。

依靠 ASI

ASI 遵循 ECMA-262 标准(第 5.1 版第 7.9 节)中规定的一套明确的规则。如果您想编写不带分号的 JavaScript,我们鼓励您回顾一下这个标准,这样您就能确切地知道您在做什么。我们不会在这里详细讨论这些规则,但是如果你想依赖 ASI,有一些重要的事情要记住。

一般来说,如果 JavaScript 引擎遇到一个新行(或者一个花括号,尽管 ASI 主要是为新行而调用的),这个新行用来分隔本来不属于一起的标记,JavaScript 就会插入一个分号——但是只有在为了创建语法上有效的代码(解释器可以成功解析和执行的代码)而需要这样做的时候。但是解释器并不关心代码在执行时是否会导致错误。它只关心代码能否被执行。

为了说明这一点,考虑清单 2-1 中的两行 JavaScript 代码。

清单 2-1。 不带分号的 JavaScript

myResult = argX - argY
myFunction()

如果解释器遇到这个代码,它将确定确实需要一个分号来使这个代码起作用,并且它将插入一个分号(清单 2-2 ):

清单 2-2。 清单 2-1 上 ASI 的结果

myResult = argX - argY;
myFunction()

另一方面,考虑清单 2-3 中的两行代码。

清单 2-3。 更多 JavaScript 不带分号

myResult = argX - argY
[myResult].myProperty = "foo"

在这种情况下,解释器不会插入分号,因为即使有新的一行,也不需要分号来使代码起作用。相反,解释器会假设我们指的是你在清单 2-4 中看到的内容。

清单 2-4。 解释器认为清单 2-3 是什么意思

myResult = argX - argY[myResult].myProperty = "foo";

如果您实际运行这个例子,您的浏览器将抛出一个引用错误,抱怨一个无效的赋值。=操作符是 JavaScript 的赋值操作符,JavaScript 期望赋值的形式是左操作数取右操作数的值。在这个例子中,JavaScript 甚至无法确定左操作数的含义,更不用说使用结果赋值了。

这是一个人为的例子,但是它确实暴露了依赖 ASI 时的主要考虑:为了有效地使用它,您必须理解规则,而显式地使用分号是毫无疑问的。你不仅要理解这些规则,任何和你一起工作的人也必须理解它们。

保持一致

每个程序员对编程风格都有自己的个人见解,这没问题;重要的是选择一种做事方式并保持一致。一致编写的代码比用多种括号样式、不一致的缩进规则和变量命名约定编写的代码更容易阅读和理解。为此,在本书中,为了保持一致性,我们采用了以下风格:

  • 分号:我们显式使用分号(而不是依赖 ASI)。
  • 括号:我们使用所谓的“一个真正的括号样式”,其中,左括号和它们的关联语句放在同一行,右括号和它们的关联语句在同一行。
  • 变量命名:一般来说,属性是名词,方法是动词。在一些例子中,我们依赖于“匈牙利符号”的变体,其中变量名以它们的类型或功能性的指示为前缀(例如,intCounterstrMessage),只是为了在例子中更加明确变量的用途或角色。

这些特殊的选择并不意味着比其他人更好。当决定在项目中使用哪种风格时,您应该选择最适合您、您的团队和您的情况的风格。一致性是最重要的。

表达和陈述

表达式和语句是理解 JavaScript 的第一步,因为它们是 JavaScript 程序的基本构件。表达式和语句之间的区别很简单,但是很微妙。

表情

从概念上讲,表达式就像口语中的单词或短语。它们是程序最简单的组成部分。在 JavaScript 中,表达式是解析为一个值的任何代码段。由于文字表达式的计算结果是实际值,JavaScript 支持与变量相同的表达式类型:布尔、数字、字符串和对象。表达式可以很简单,只是陈述一个值,也可以是数学或逻辑运算,如清单 2-5 所示:

清单 2-5。 JavaScript 文字表达式

10            // Literal expression, resolves to 10
"Hello World" // Literal expression, resolves to the string "Hello World"
3+7           // Mathematical expression, resolves to 10

也可以写复合表达式。复合表达式是表达式中的一个(或多个)项是另一个表达式的表达式。复合表达式可以根据需要任意复杂和嵌套,如清单 2-6 所示:

清单 2-6。 复合表情

 (3+7)/(5+5)                      // evaluates to 1
Math.sqrt(100)                    // evaluates to 10

你最常遇到表达式的地方之一是在条件语句中,如清单 2-7 所示。

清单 2-7。 条件句中的复合表达式

if ((myString === "Hello World") && (myNumber > 10)) {
                                  // conditional code here
}

在本例中,我们有一个由两个表达式组成的复合表达式,一个测试myString的值,另一个测试myNumber的值,其值为 true 或 false。这些表达式包含在一个逻辑 AND 表达式中,因此如果两个表达式的计算结果都为 true,则条件代码将执行。(稍后我们将更多地讨论嵌套的多重表达式;现在,只要把注意力集中在每个单独的表达式上,就像它所代表的布尔值一样。)

最后,尽管表达式可以独立存在,如清单 2-8 所示,但这样的表达式通常不是很有用。

清单 2-8。 一个不太有用的字面表达

var myNumber = 10,
    myOtherNumber = 20;

"hello world";                    // um, okay?

if (myOtherNumber > myNumber) {
    alert("Condition was true!"); // will alert because conditional is true
}

这段代码将在不抛出错误的情况下执行,并发出警告“条件为真!”不出所料。清单 2-8 的第三行文字表达式完全有效,尽管它没有做任何有用的事情。为了实际做一些事情,文字表达式通常与操作符结合在一起:赋值(使用=操作符)、条件(使用逻辑操作符)等等。

关于表达式(甚至复合表达式)的底线是它们只表示值。如果你想用这些值做任何事情,你需要使用一个语句。

语句

在 JavaScript 中,语句是执行特定动作的一个或多个表达式的集合。回到口语类比,如果表达式是单词和短语,那么语句就是完整的句子。从概念上讲,最简单的语句类型是具有副作用的表达式,例如变量赋值或简单的数学运算。参见清单 2-9 中的一些例子。

清单 2-9。 简单语句

var x = 5,     // variable assignment, a statement
    y = 3,
    z = x + y; // mathematical operation, also a statement

有时这些简单的语句被称为表达式语句,以强调它们本质上是具有副作用的表达式。然而,这个术语会混淆表达式和语句之间的微妙区别,所以在本书中我们不会使用它。

就像 JavaScript 有复合表达式一样,它也有复合语句。复合语句是代码块中语句的集合,通常用花括号括起来。复合语句的优秀例子是if语句和循环,如清单 2-10 所示。

清单 2-10。 if 语句和循环是复合语句

if (expression) {
    // conditional statement--often a compound statement because it contains multiple statements.
}
for (expression) {
    // repeated statement--often a compound statement because it contains multiple statements.
}

但是,请注意,并不是花括号中的每一段代码都一定是一条语句。例如,对象文字是表达式,而不是语句,尽管是用括号括起来的多个表达式,正如你在清单 2-11 中看到的。

清单 2-11。 一个对象字面上不是一个语句

{
    prop1: "value",
    prop2: "value2"
}

然而,只要你记住表达式(甚至复合表达式)代表的是值而不是别的,那么对象文字不是语句的事实就应该很清楚了,因为对象文字只是实际对象值的规范。有关对象文字的详细信息,请参阅本章后面的“对象”一节。

运算符

操作符对表达式执行操作,这也许并不奇怪。运算符对操作数执行它们的功能(“运算”)。大多数 JavaScript 操作符都是二进制的,也就是说它们有两个操作数,通常是这样的:

operand1 operator operand2

JavaScript 中最常用的二元运算符可能是赋值运算符=。其他例子包括数学运算符和大多数逻辑运算符。

一些 JavaScript 操作符是一元的,这意味着它们只有一个操作数;例如:

operand operator

或者

operator operand

操作数和操作符的顺序取决于所讨论的操作符,有时还取决于你想对操作符做什么。示例包括逻辑“非”运算符或数学“非”运算符。

此外,JavaScript 还有一个三元运算符,称为条件运算符 。它接受三个操作数并执行条件测试:

conditionalExpression  ? valueIfTrue : valueIfFalse

条件运算符允许您编写比显式使用if-then-else语句更简洁的代码,并且可以在任何使用标准运算符的地方使用。

JavaScript 操作符分为以下几大类:

  • 算术运算符:对其操作数进行算术运算,如加、乘等。
  • 赋值操作符:修改变量,要么赋值,要么根据特定规则改变它们的值。
  • 按位运算符:将它们的操作数视为一组 32 位,并在该上下文中执行它们的运算。
  • 比较运算符:比较它们的操作数,并根据比较结果是否为真返回一个逻辑值(真或假)。
  • 逻辑运算符:对操作数执行逻辑运算,通常用于连接多个比较。
  • 字符串运算符:对两个字符串进行运算,比如串联。
  • 其他运算符:不属于以上任何一类的运算符。此类别包括条件运算符和运算符,如 void 运算符和逗号运算符。

在本章中,我们不打算详细讨论每一个操作符;该参考资料可在第七章中找到。然而,这里有一个重要的操作符概念我们想要讨论:优先级。

优先级

如果在一个语句中有多个操作符,如何确定它们的执行顺序?你严格从左到右评价他们吗?还有其他规则吗?根据操作符及其操作数的不同,不同的执行顺序会产生不同的结果,所以有一个处理这个问题的标准方法是很重要的。

考虑清单 2-12 中涉及数学运算符的例子。

清单 2-12。 单个语句中的多个数学运算符

var myVar = 5 + 7 * 3 + 4 - 2 * 8;
alert(myVar); // what will this alert?

如果你从左到右评估清单 2-12 中的语句,执行每一个操作,你会得到 304。然而,这个例子实际上警告了 14,因为根据一组称为优先级的规则,一些操作符在其他操作符之前被评估。在这个例子中,乘法比加法或减法具有更高的优先级,所以该语句实际上是如清单 2-13 所示进行求值的,清单 2-13 使用圆括号通过将实际求值的运算组合在一起来明确表示优先级。

清单 2-13。 用括号明确表示优先顺序

var myVar = ((5 + (7 * 3)) + 4) - (2 * 8);
alert(myVar); // will alert 14

碰巧的是,JavaScript 中的数学运算符优先级遵循数学本身的优先级规则:首先计算圆括号或方括号中的项,然后是指数和根,然后是乘法和除法,最后是加法和减法。

清单 2-14 提供了另一个例子,只涉及加法和减法,两个运算符具有相同的优先级。

清单 2-14。 多个优先级相同的运算符

var myVar = 5 + 6 - 7 + 10;
alert(myVar); // what will this alert?

myVar值多少?这取决于您执行操作的顺序。如果你从左到右评估它,它将是 14,如果你评估它为(5+6)-(7+10),它将是-6。

当您有多个优先级相同的运算符时,它们将根据它们的结合性进行计算:或者从左到右,或者从右到左。数学运算符的话,都是从左到右求值,所以myVar的值是 14。

因为 JavaScript 不仅仅有数学运算符,它还有比数学更复杂的优先级规则,正如你在表 2-1 中看到的。

表 2-1。JavaScript 中的运算符优先级

table2-1a.jpg

理解运算符优先级很重要;否则,您的语句可能会产生意想不到的结果。尽管如此,许多 JavaScript 最佳实践和风格指南都建议,对于包含多个操作符的复杂语句,应该用括号明确说明您想要的优先级。一般来说,这使得代码更可读,维护更容易,尽管如果你有一个非常复杂的语句,你可能会有很多括号。在这种情况下,将单个语句分解成一条或多条语句可能是有价值的,这样可以充分明确并减少括号的总数。

变量

广义地说,变量是一个带有关联值的命名存储位置。您可以通过使用名称来访问与存储位置关联的值。每种语言都有自己的变量实现:如何声明它们,它们的范围是什么,以及如何管理它们。

在 JavaScript 中声明变量

在 JavaScript 中,变量是使用var关键字声明的,如清单 2-15 所示。

清单 2-15。 在 JavaScript 中声明变量

var myVar = 1;

您也可以根据需要简单地访问变量,而不需要使用var关键字正式声明它们(清单 2-16 )。

清单 2-16。 通过访问创建一个新变量

var myVar = 1;
myOtherVar = 2;

这两种声明变量的方式在语法上都是有效的,但是它们对于变量的作用域有不同的含义(在下一节中描述)。

一次声明许多变量是常见的做法。您可以对每个变量使用var关键字,或者您可以使用一次var关键字并用逗号分隔变量声明。将每个变量声明放在自己的行上也是常见的做法,如清单 2-17 所示,以提高可读性。

清单 2-17。 一次性声明多个变量

var myObject = {},
    intCounter = 0,
    strMessage = "",
    isVisible = true;

JavaScript 风格指南通常建议在给定范围的开始声明该范围内的所有变量,主要是因为这有助于防止变量范围错误的问题。它还有助于 JavaScript 代码压缩器,它将获取变量列表并对每个项目运行搜索和替换,以将变量名更改为单字母或双字母名称,从而进一步减小文件的大小。

理解 JavaScript 中的变量范围

正如每种语言都有创建变量的规则一样,每种语言都有控制变量访问位置的规则。这就是所谓的变量范围。基本上,作用域规则决定了这个问题的答案,“如果我在这里创建这个变量,我还能在哪里访问它?”变量作用域对于任何语言来说都是一个重要的概念,因为它几乎影响到语言的方方面面,从调试到优化。

在第一章的中提到,JavaScript 有函数作用域:当你使用var关键字正式声明一个变量时,它的作用域被限制在当前函数作用域以及当前函数作用域中包含的所有函数作用域。换句话说,如果你在一个给定的作用域内声明一个变量,你将能够在一个子作用域内访问它,但不能在任何包含它的作用域内访问它。清单 2-18 提供了一个例子来说明这个概念。

清单 2-18。JavaScript 中的功能范围

function myFunction() {
    var myVariable = "Here"; // myVariable is now limited in scope to myFunction and any scopes we create within myFunction

    // Create a new function within myFunction to demonstrate scope nesting
    function myInternalFunction() {
        alert(myVariable);
    }
    myInternalFunction();    // call myInternalFunction when myFunction is called
}
myFunction();                // will alert "Here"
alert(myVariable);           // will throw an error; myVariable is not defined outside of myFunction().

当你在一个特定的作用域中声明一个变量时,这个作用域通常被称为这个变量的局部作用域。当您在一个函数中嵌套另一个函数时,您创建了嵌套的函数作用域,通常被称为作用域链

每当您在程序中访问一个变量时,JavaScript 引擎都会在当前范围内查找,看它是否在当前范围内定义。如果在那里找不到定义,它就向上找到包含它的作用域,以此类推,沿着链向上找到程序的最顶层作用域。这通常被称为范围链查找,或者有时仅仅是范围查找

任何 JavaScript 程序的最顶层作用域被称为全局作用域。在全局作用域中声明的任何变量对程序中的所有作用域都是可用的,如清单 2-19 所示。

清单 2-19。JavaScript 中的全局作用域

var myVariable = "This is a global variable";
function myFunction() {
    myVariable = "Global variable has been changed inside a function";
    alert(myVariable);
}

alert(myVariable);               // will alert "This is a global variable"
myFunction();                    // will alert "Global variable has been changed inside a function"
alert(myVariable);               // will alert "Global variable has been changed inside a function"

您总是可以通过在特定的函数范围内重新声明变量来覆盖更高范围的声明。这实质上创建了一个新的变量,其作用域仅限于该函数作用域;这通常被称为局部范围优先级。为了演示局部作用域优先级,请参见清单 2-20 。

清单 2-20。 局部范围优先

var myVariable = "This is a global variable";
function myFunction() {
    var myVariable = "Global variable has been overridden inside a function";
    alert(myVariable);
}

alert(myVariable);               // will alert "This is a global variable"
myFunction();                    // will alert "Global variable has been overridden inside a function"
alert(myVariable);               // will alert "This is a global variable"

由于局部作用域的优先性,JavaScript 变量(和函数声明,将在本章后面描述)在它们的作用域块的开始处立即可用,不管它们是否已经被定义。如果您试图在 JavaScript 变量初始化之前访问它们,您将得到一个未定义的值,但是它们会在那里,脚本不会抛出错误。这可能是相当出乎意料的行为,尤其是在覆盖已经在更高作用域中声明的变量的情况下,如清单 2-21 所示。

清单 2-21。 局部范围凌驾上级范围

function testScope() {
    var myTest = true;           // myTest is now present in this top level scope.
    function testNestedScope() { // Create a sub-scope within the main scope
        alert(myTest);           // Access myTest...but from which scope?
        var myTest = false;      // Redefine myTest in this sub-scope.
    }
    testNestedScope();
    alert(myTest);
}

testScope();                     // will alert "undefined", and then true.

当我们执行这个例子时,它首先警告“未定义”,然后警告“真”第一个警告的出现是因为,在testNestedScope()函数中,我们重新定义了变量myTest,因此它现在在该范围内。这使得它的新值在该范围内的任何地方都可用,有效地从该函数内更高范围的任何地方擦除了该变量的值。这被称为提升:一个变量声明(不是它的赋值,只是它的声明)被自动“提升”到它的包含范围的开始。换句话说,当创建一个新的作用域时,JavaScript 会在做任何事情之前立即声明所有的局部变量,包括赋值和函数调用。结果,清单 2-21 被解析,就好像它被写成了清单 2-22 中的所示。

清单 2-22。 明确地提升变量

function testScope() {
    var myTest = true;
    function testNestedScope() {
        var myTest;
        alert(myTest);
        myTest = false;
    }
    testNestedScope();
    alert(myTest);
}

testScope();              // will alert "undefined", and then true.

由于变量提升,许多 JavaScript 最佳实践和风格指南建议在访问变量之前,在它们的作用域的开始定义所有变量,从而显式地说明提升无形地做了什么。

如果访问一个变量而没有使用var关键字声明它,JavaScript 仍然会执行范围链查找。如果它到达了全局范围,仍然没有找到变量声明,它将假定该变量在范围上是全局的,并将它添加到那里。这被称为隐含全局作用域,清单 2-23 中显示了一个例子。

清单 2-23。 隐含全局范围

function myFunction() {
    myVariable = "Declared in function, default global scope";
    alert(myVariable);
}

alert(typeof myVariable); // will alert "undefined" because it wasn't created yet
myFunction();             // will alert " Declared in function, default global scope "
alert(myVariable);        // will alert "Declared in function, default global scope "

关于变量作用域的细节,包括像闭包这样的相关主题,请参见第一章中的“JavaScript 中的作用域”一节。

在 JavaScript 中管理变量

JavaScript 试图让程序员尽可能容易地管理变量。一旦声明了一个变量,就不需要显式地取消声明来释放内存——事实上,JavaScript 没有提供这样做的机制。解释器将自己管理变量,当所有的引用和闭包都完全结束时,释放它们的内存。

正如在第一章中提到的,JavaScript 是一种弱类型语言,这意味着它将根据一组特定的规则来管理表达式中的变量类型不匹配。因为 JavaScript 不断地在幕后管理变量类型,所以理解 JavaScript 的一个最重要的方面就是理解它是如何管理类型的,所以一定要仔细复习第一章。概括地说,JavaScript 有四种广泛的数据类型:

  • 布尔型:真或假值。
  • 数字:JavaScript 中所有的数字都是 64 位浮点数。
  • 字符串:任意字符的字符串。
  • 对象:属性和方法的集合。

此外,JavaScript 采用了原语的概念:非对象的简单变量,它们本身可以是布尔值、数字或字符串。任何不是原语的东西都是对象——尽管 JavaScript 会透明地将原语转换成它们相关的对象类型,并在需要时再转换回来。

在复制变量时,JavaScript 对原语和对象的处理是不同的。原语直接从一个变量实例传递到另一个变量实例。另一方面,对象是通过引用传递的:设置一个新变量等于一个现有的对象,并不将该对象大规模复制到新变量中;相反,它只是使新变量成为指向原始对象的指针。参见清单 2-24 中的示例。

清单 2-24。 直接赋值图元和引用对象

var myObject = {};
var myOtherObject = myObject;  // myOtherObject is now a reference to myObject
myObject.bar = "bar";          // This changes myObject directly
myOtherObject.foo = "foo";     // This changes myObject via reference
alert(myObject.foo);           // will alert "foo"
alert(myOtherObject.bar);      // will alert "bar"
var myInt = 5;                 // Primitive
var myOtherInt = myInt;        // myOtherInt is now its own primitive, there is no reference
myOtherInt++;
myInt--;
alert(myOtherInt);             // will alert 6
alert(myInt);                  // will alert 4
var myPrimitiveString = "My Primitive String";
var myOtherPrimitiveString = myPrimitiveString;
myOtherPrimitiveString += " is now longer."
alert(myOtherPrimitiveString); // Will alert "My Primitive String is now longer."
alert(myPrimitiveString);      // Will alert "My Primitive String"

因为 JavaScript 透明地管理类型不匹配,有时,正如你在清单 2-25 中看到的,很容易混淆什么是原语,什么是对象:

清单 2-25。 同一数据类型的对象和原语之间的类型转换

var myStringObject = new String("This is an object");
var myOtherStringObject = myStringObject;
myOtherStringObject += " which I just changed into a primitive"; // Type change, so no longer a reference!
alert(myStringObject);         // will alert "This is an object"
alert(myOtherStringObject);    // will alert "This is an object which I just changed into a primitive"

如果两个对象在内存中引用同一个对象,它们将在相等检查中返回相等,即使这两个对象在其他方面是相同的,如清单 2-26 所示。

清单 2-26。 对象只有在内存中引用同一个对象时才相等

var myObject = {};
var myOtherObject = {};
var myThirdObject = myObject;
alert(myObject == myThirdObject);         // will alert "true"
alert(myOtherObject == myThirdObject);    // will alert "false"
alert(myObject == myOtherObject);         // will alert "false"

JavaScript 只提供引用对象的方法;没有复制对象的方法。但是,如果需要的话,遍历一个对象并将其所有方法和属性复制到一个新对象中并不难。

目标

几乎在所有面向对象的编程语言中,对象都是属性的集合,JavaScript 也不例外。属性可以是原语,也可以是其他对象,包括函数。JavaScript 对象可以是任意深度的,这意味着您可以拥有具有作为对象的属性的对象,而这些对象又具有作为对象的属性,依此类推,直到您希望的深度。

继承

正如在第一章中详细介绍的,JavaScript 使用原型继承而不是类。每个对象都有一个特殊的原型属性,该属性充当指向创建它的对象的指针。当您试图访问对象上的属性时,解释器会检查当前对象中是否存在所需的属性。如果属性不存在,解释器检查原型。如果属性不存在,解释器检查原型的原型,依此类推,直到它找到属性或者到达原型链的末尾并返回一个错误。(参见第一章了解原型继承的细节和例子。)

访问属性和枚举

JavaScript 提供了两种访问对象属性的方法,如清单 2-27 所示。

清单 2-27。 在 JavaScript 中访问对象属性

var myObject = {};
myObject.property1 = "This is property1"; // access via dot notation
myObject["property2"] = 5;                // access via square brackets
alert(myObject["property1"]);             // will alert "This is property1"
alert(myObject.property2);                // will alert 5

ECMA-262 标准规定这两种方法完全相同:

  • 属性通过名称访问,使用点符号:

    MemberExpression.IdentifierName
    CallExpression.IdentifierName
    
  • 或括号标注:

    MemberExpression[ Expression ]
    CallExpression[ Expression ]
    
  • 点符号通过下面的句法转换来解释:

    MemberExpression.IdentifierName
    
  • 的行为与的行为相同

  • 类似

    CallExpression.IdentifierName
    
  • 的行为与的行为相同

  • 其中<identifier-name-string>是一个字符串文字,包含与 IdentifierName 相同的 Unicode 转义序列处理后的字符序列。

ECMA-262 版本 5.1,第 11.2.1 节,“属性访问器”

这种双重符号的好处是,您可以使用方括号符号轻松地以编程方式访问对象属性,而不必知道所有属性的名称。作为一个例子,考虑需要枚举一个对象的所有属性。你不知道它们是什么,所以你不能用点符号来访问它们。相反,您只需查询对象的每个属性,并使用括号访问它们的值,如清单 2-28 所示。

清单 2-28。JavaScript 中枚举对象的传统方法

// Assuming the existence of targetObject, which has many unknown properties:
var thing,
    strMessage = "";
for (thing in targetObject) {
    strMessage += "targetObject." + thing + " = " + targetObject[thing] + "\n";
}
alert(strMessage); // will alert all of the properties in targetObject

在清单 2-28 中,我们使用一个for循环遍历targetObject中的所有属性(参见本章后面的“循环”一节,了解关于for循环的细节)。我们构建一个包含每个属性及其相关值的字符串,每行一个,然后警告该字符串。这将只包括对象的非继承属性。这是 JavaScript 中枚举属性的传统方法。在新版 JavaScript 中,可以使用不同的方法枚举对象。在 ECMA-262 的版本 5 中,全局Object对象有了两个新方法:Object.keys()Object.getOwnPropertyNames()。(参见第五章了解这两种方法的详细信息及其区别。)现在我们可以枚举一个对象,如清单 2-29 所示。

清单 2-29。JavaScript 中枚举对象的新方法

// Assuming the existence of myObject, which has many unknown properties:
var arrKeys = Object.keys(myObject),
    strMessage = "",
    i = 0,
    arrKeysLength = arrKeys.length;
for (i = 0; I , arrKeysLength; i++) {
    strMessage += "myObject." + arrKeys[i] + " = " + myObject[arrKeys[i]] + "/n";
}
alert(strMessage);

创建对象

JavaScript 有三种创建对象的主要方式:使用构造函数、使用文字符号或使用Object.create()

使用构造函数

创建新 JavaScript 对象的传统方法是创建一个构造函数,并根据需要用它来创建新对象。要创建一个构造函数,你只需像平常一样创建一个函数,并根据需要添加属性,如清单 2-30 所示。

清单 2-30。 基本构造函数

function myConstructor() {
    this.property1 = "foo";
    this.property2 = "bar";
    this.method1 = function() {
        alert("Hello World!");
    }
}

您会注意到,在这个构造函数中,我们使用了this关键字向对象添加新的属性。关于函数中关键字this微妙之处的细节将在本章后面的“函数”一节中提供在构造函数的上下文中,关键字this指的是由构造函数创建的对象。

要从构造函数创建一个新的实例,使用new操作符,如清单 2-31 所示。

清单 2-31。 从构造函数创建新的实例

var myObject = new myConstructor();
myObject.method1(); // will alert "Hello World!"

new 运算符执行以下步骤:

  1. 它创建一个新的空对象,该对象继承自操作数的原型,
  2. 它将那个新对象设置为操作数的执行范围(因此在操作数内,this关键字引用新的空对象),
  3. 它调用操作数,因此操作数可以根据需要修改新对象,
  4. 它返回操作数返回的值,或者如果操作数没有返回任何内容,它会自动返回在步骤 1 中创建的新对象以及在步骤 3 中修改的操作数。

如果您是以 Java 或 C++等基于类的语言为背景来学习 JavaScript,您可能会想,“嘿,这看起来有点像类!”你是对的,这个方法表面上确实类似于类。您可以继续沿着这条路走下去,结合使用这种方法和其他方法来完全模拟 JavaScript 中的类。但是,我们鼓励您在使用 JavaScript 时尝试放弃类的概念,以便更好地利用语言的动态特性。

使用文字

在 JavaScript 中创建对象的另一种方式是使用文字符号。文字符号是一种在创建过程中为对象提供文字值的方法。在 JavaScript 中,文字符号是非常常见的,我们将在本章中多次提到它。

要按字面意思创建一个对象,首先像平常一样用var关键字定义它,如清单 2-32 所示,然后用花括号将属性括起来,属性应该是用逗号分隔的键/值对。

清单 2-32。 使用文字符号创建对象

var myObjectLiteral = {
    property1: "one",
    property2: "two",
    method1: function() {
        alert("Hello World!");
    }
}
myObjectLiteral.method1();                  // will alert "Hello World!"

因为我们已经创建了对象,所以我们可以立即使用它。因此,文字符号是在 JavaScript 中创建单例的最佳方式。以这种方式创建的对象仍然可以在以后根据需要通过添加属性和方法来扩展,如清单 2-33 所示。

清单 2-33。 扩展一个对象

myObjectLiteral.property3 = "New property"; // adds a new property to the previously created object

使用 Object.create( )

最后,最新版本的 JavaScript 提供了创建新对象的第三种方法:全局Object对象上的create()方法。如清单 2-34 所示,该方法将一个对象作为其参数,并返回一个以参数对象为原型的新对象。

清单 2-34。 使用 Object.create()创建新对象

var myObjectLiteral = {
    property1: "one",
    property2: "two",
    method1: function() {
        alert("Hello world!");
    }
}
var myChild = Object.create(myObjectLiteral);
myChild.method1();                          // will alert "Hello world!"

这个方法可以从任何对象创建一个新的对象,甚至是一个构造函数。

我应该使用哪种方法?

使用哪种方法在很大程度上是个人喜好的问题,尽管有时选择会受到惯例或情况的支配。例如,一些 JavaScript 库大量使用了Object.create()。或者,如果您正在使用 JSON 做大量的工作,您可能会发现使用文字来管理您的单例更有意义。如果你真的觉得你的 JavaScript 程序需要类行为,那么构造函数是最简单的方法。

数组

与大多数语言一样,在 JavaScript 中,数组本质上是索引数据结构,每个索引都有一个值。JavaScript 数组索引都是从 0 开始的,所以数组的第二项实际上索引是 1,数组的长度等于最后一个索引+ 1。

动态长度

为了与 JavaScript 的动态特性保持一致,它的数组具有动态长度。这意味着您可以在数组中添加和删除项目,并且数组的长度会根据需要而改变。因此,在向数组中添加或从中移除项时,不会产生边界错误。如果你试图访问一个不存在的元素,解释器将返回undefined而不是抛出一个错误。JavaScript 中一个常见的错误是试图从一个不存在的数组中检索某个东西,导致了undefined,然后试图用那个值做一些事情,而没有先检查它是否未定义。根据您试图对该值做什么,解释器可能会在该点抛出错误,但不会在数组被越界访问时抛出错误。

JavaScript 数组有一个length属性,它包含一个表示数组长度的数字。随着元素在数组中的添加和删除,这个数字会根据需要增加或减少。也可以直接设置length属性,如清单 2-35 所示;这样做将根据需要从数组末尾移除现有项或将未定义的项添加到数组末尾。

清单 2-35。JavaScript 数组的动态长度

var arrColors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
alert(arrColors.length); // will alert 7
arrColors.length = 10;   // adds three new elements to the array, each set to "undefined"
alert(arrColors[8]);     // will alert "undefined"
arrColors.length = 6;    // arrColors is now ["red", "orange", "yellow", "green", "blue", "indigo"]

访问和分配值

在 JavaScript 中,数组可以包含任何有效的数据类型:对象、函数、布尔值等等。数据类型也可以在数组中混合,这意味着数组可以由一个对象、一个布尔值、一个数字和一个字符串组成。

使用为对象描述的方括号符号访问数组值:数组的名称,后跟一组包含所需值的索引的方括号;例如,在清单 2-36 ,myArray[2]将访问数组myArray中的第三项。

清单 2-36。 访问数组

var myArray = new Array();
myArray[0] = "foo";
myArray[1] = "bar";
myArray[3] = 4;
alert(myArray.length);      // will alert 4
alert(myArray[2]);          // will alert "undefined"

var testVar = myArray[498]; // testVar is now "undefined" and no error will be thrown
alert(testVar);             // will alert "undefined"

数组实际上是 JavaScript 对象的特例。你可以像添加任何其他对象一样给数组添加属性,如清单 2-37 所示。

清单 2-37。赋值

var myArray = new Array();
myArray[0] = "foo";         // assign "foo" to the first element of the array
myArray[1] = "bar";         // assign "bar" to the second element of the array
myArray["foo"] = "bar";     // create the property "foo" on myArray and give it the value of "bar"
alert(myArray.length);      // will alert 2
myArray["2"] = 7;           // assign 7 to the third element of the array
alert(myArray.length);      // will alert 3
var strMyIndex = "3";
myArray[strMyIndex] = 8;    // will assign 8 to the fourth element of the array
alert(myArray.length);      // will alert 4

清单 2-37 展示了 JavaScript 如何将一个非数字索引的类型强制转换成一个数字值,如果可以的话,然后使用它作为一个索引。否则,它将使用提供的值作为数组对象本身的新属性的键。

由于这种行为,人们常说 JavaScript 有“关联数组”这并不完全正确,因为 JavaScript 数组不能有非数字索引。如果你用一个非数字索引向一个数组添加东西,如清单 2-38 所示,你只是简单地把它作为一个属性添加到数组对象本身,而不是把元素添加到数组。

清单 2-38。 数组元素对属性

var myArray = new Array();
myArray["foo"] = "bar";     // adds a property, not a new element
myArray["new"] = "old";     // adds a property, not a new element
alert(myArray.length);      // will alert 0, because no elements have actually been added to the array.
myArray[0] = 0;             // Adds a new element to the array
alert(myArray.length);      // will alert 1

创建数组

在 JavaScript 中创建数组有两种方法:使用全局Array对象作为构造函数,如清单 2-39 所示,或者使用文字符号,如清单 2-40 所示。

清单 2-39。 用构造函数创建数组

var myArrayObject = new Array(4);            // creates an array with 4 undefined elements
var myOtherArray = new Array(4, 2, 5, 2, 7); // creates an array with those values
alert(myArrayObject.length);                 // will alert 4
alert(myOtherArray.length);                  // will alert 5

清单 2-40。 用文字符号创建数组

var myLiteralArray = [];                     // Creates an array of length 0 with no elements
var myOtherArray = [1, "foo", {}, true];     // Creates an array of length 4 with a number, a string, an object, and a boolean

当使用Array对象作为构造函数时,您可以提供一个可选的单个数值,这将导致构造函数返回一个用指定数量的槽初始化的数组。每个槽将被设置为“未定义”如果您提供多个以逗号分隔的参数,构造函数将返回一个数组,每个参数作为一个索引值,从 0 开始按顺序排列。

使用文字符号创建数组类似于使用文字符号创建对象。

清单 2-40 展示了在一个数组中可以有多种数据类型。甚至可以将对象作为数组值,就像可以将数组作为对象的属性一样。

JavaScript 也支持多维数组,作为数组的数组,如清单 2-41 所示。

清单 2-41。 多维数组

var row1 = [0, 1, 2];
var row2 = [3, 4, 5];
var row3 = [6, 7, 8];
var array3by3 = [row1, row2, row3];
alert(array3by3[2][1]);                      // will alert 7

迭代数组

因为数组是数字索引的,所以对它们最常见的操作之一就是按顺序遍历它们的成员,通常是对每个成员做一些事情。迭代数组最常见的方式是在for循环中,如清单 2-42 所示。(有关for循环以及如何优化它们的详细信息,请参见本章后面的“for Loops”。)

清单 2-42。 使用 for 循环迭代数组

var myColors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
for (var i = 0; i < myColors.length; i++) {
    alert(myColors[i]);                      // will alert each color one at a time
}

在这个例子中,for循环将继续,直到迭代器i到达myColors.length -1。每次循环时,JavaScript 都会提醒存储在该索引中的值。这是目前为止最常见的数组迭代模式,而且速度非常快,即使对于非常大的数组也是如此。

您可能会尝试使用一个for-in循环,就像我们在枚举对象时所做的那样,但是请记住数组可以有属性和值,并且一个for-in循环将遍历所有这些项。此外,也不能保证循环会按顺序遍历所有索引值,或者一次遍历所有索引值。

通常,您会希望对数组中的每个元素做一些事情。如果你确定你的数组中没有元素是未定义的,你可以使用稍微不同版本的for循环,如清单 2-43 所示。

清单 2-43。 另一种迭代数组的方式

var myColors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
for ( var i = 0, color; color = myColors[i]; i++) {
    // Inside of the loop, the variable color will contain the value at the current index
    alert(color); // will alert each color one at a time
}

这种方法的优点是,在循环中,变量color已经被设置为当前索引处的一个值,省去了您自己获取它的麻烦。注意,如果其中一个数组元素未定义,那么你的变量在循环中也是未定义的。

在新版本的 JavaScript 中,数组有一个forEach()方法,可以用来迭代数组,如清单 2-44 所示。该方法将函数表达式作为参数,并对每个数组元素执行一次该函数。函数表达式可以接受一个可选参数,该参数将被设置为当前索引处的数组值。

清单 2-44。 迭代数组的第三种方式

var myColors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
myColors.forEach(function(color) {
    alert(color); // will alert each of the colors, one at a time
});

如果你有一个数组,你想在迭代时修改它,你需要注意不要跳过元素。考虑清单 2-45 中提供的例子。

清单 2-45。 迭代中修改数组

var myColors = ["red", "orange", "green", "green", "blue", "indigo", "violet"];
for (var i = 0; i < myColors.length; i++) {
    if (myColors[i] === "green") {
        myColors.splice(i, 1); // the splice() method removes the item at index i (see Chapter 5 for details on the splice() method)
    }
}

在本例中,我们从“red”开始,一次一个地检查myColors数组的每个成员。当循环到达i = 2时,myColors[i]将变成“绿色”,条件将导致该元素从数组中移除。因此,数组将从 7 个元素变为 6 个元素,第二个“绿色”元素将从索引 3 变为索引 2。然后,根据for循环功能(见本章后面的“循环”),索引将递增,从 2 到 3,循环将继续。这将导致第二个“绿色”元素被遗漏。

每当你在迭代时修改一个数组,你必须考虑这种可能性。有两种处理方法。一种是递减if语句内的计数器i,这样如果出现匹配,一个元素被弹出数组,i就会递减 1,然后递增 1,这样就避免了跳过这个元素。

另一个更好的解决方案是反向迭代数组,如清单 2-46 所示。

清单 2-46。 反向迭代数组

var myColors = ["red", "orange", "green", "green", "blue", "indigo", "violet"];
for (var i = myColors.length - 1; i >= 0; i--) {
    if (myColors[i] === "green") {
                myColors.slice(i, 1); // the slice method removes the specified element from the array.
    }
}

在清单 2-46 中,我们从数组的末尾开始向后工作。首先我们测试index i = 6,然后是i = 5,依此类推。在i = 3,我们遇到一个“绿色”元素,它将从数组中移除。这将使数组长度减少 1,并且“blue”元素(及其后面的所有元素)的索引将减少 1。然后,计数器将减 1,到i = 2,我们将点击数组中的另一个“绿色”元素。通过反向遍历数组,可以避免手动管理计数器。

数组方法和属性

数组有几个方法和属性来管理它们的元素。例如,在本章中,我们使用了数组的length属性。还有其他几个属性和方法;有关所有数组方法和属性的详细描述,以及示例,请参见第五章。

功能

函数是可重用的代码块,可以从程序的其他区域调用。在 JavaScript 中,函数也是一级对象,这意味着它们可以像语言中的任何其他对象一样被操作:它们可以有属性和方法,可以从函数返回,可以作为参数传递,等等。JavaScript 中函数的对象特性是理解语言动态特性的最重要的关键之一。

JavaScript 提供了两种创建新函数的方法:通过声明和通过表达式。

函数声明

JavaScript 提供了一个function关键字,可以用来声明函数。它的工作方式类似于声明变量的关键字var,在相同的上下文中考虑它是很有用的。根据 ECMA-262 标准,函数声明的形式如下:

function Identifier (FormalParameterList optional) { FunctionBody}

FormalParameterList是可选的(JavaScript 函数不需要有参数)。

这将创建一个名为Identifier()的函数,这个函数在它的父作用域和它自己的作用域中都可见。清单 2-47 显示了一个简单的函数声明。

清单 2-47。 简单函数声明

function saySomething(strMessage, strTarget) {
    alert(strMessage + " " + strTarget);
}
saySomething("Hello", "world"); // will alert "Hello world"

注意,因为函数名在它自己的作用域内可用,所以函数可以调用它自己,允许递归。清单 2-48 提供了一个例子,说明我们可以很容易地实现阶乘的数学概念,其中一个数 N!= N(N–1)(N–2)。。。(N-(N–1))。

清单 2-48。 递归函数

function factorial(number) {
    if (number <=1) {
        return 1
    } else {
        return number * factorial(number - 1);
    }
}
alert(factorial(5)); // will alert 120

像变量声明一样,函数声明被提升到其作用域的开始。事实上,它们在所有其他语句之前被解析和评估,这意味着它们将在其定义的范围内立即可用,甚至在代码中定义它们之前。(有关提升的完整解释,请参见本章前面的“理解 JavaScript 中的变量范围”。)作为示范,考虑在清单 2-49 中出现的常见 JavaScript 面试问题。

清单 2-49。 常见面试问题演示函数声明吊装

function myFunction() {
    function myInternalFunction() {
        return 10;
    }
    return myInternalFunction();
    function myInternalFunction() {
        return 20;
    }
}
alert(myFunction()); // What will this alert?

如果你回答“它会提醒 20”,那么恭喜你,你被录用了!函数myInternalFunction()定义了两次,第二次用第一次替换。在两个定义中间访问函数并不重要,因为定义被提升到了其作用域的顶部。它相当于清单 2-50 中的。

清单 2-50。 相当于清单 2-49 中的

function myFunction() {
    function myInternalFunction() {
        return 10;
    }
    function myInternalFunction() {
        return 20;
    }
    return myInternalFunction();
}
alert(myFunction()); // What will this alert?

因为函数声明被提升到其作用域的顶部,所以您可以在声明它们之前访问它们。你可以,但是,不代表你就应该;许多 JavaScript 风格指南建议不要这样做,因为这会导致代码混乱。不管你是否使用它,函数声明提升是存在的,当你决定函数的范围时,你应该记住这一点。

函数表达式

在 JavaScript 中创建函数的另一种方法是使用函数表达式。与任何表达式一样,函数表达式代表一个值;在函数表达式的情况下,值是一个函数对象。通常,函数表达式被赋值给变量,这样就可以访问它们了。

根据 ECMA-262 标准,函数表达式的形式为:

function Identifier optional (FormalParameterList optional) { FunctionBody }

这反过来又会产生类似

var myFunction = function foo() {
    // function body here
}

您会注意到Identifier是可选的。JavaScript 允许创建未命名的函数表达式,称为匿名函数 。匿名函数在 JavaScript 中很常见。创建函数表达式时不提供标识符是很常见的,因为变量是调用函数的一种方式。大多数情况下,除非函数需要调用自身,否则会将匿名函数作为函数表达式的一部分赋给变量:

var myFunction = function() {
    // function body
}

您还会注意到,这个定义看起来几乎与函数声明的定义(在上一节中介绍过)完全相同。这意味着可以将完全相同的代码用作函数声明或函数表达式,这取决于上下文:

function myFunction() {
    // function body
}

一般来说,在赋值(比如变量赋值)或表达式(比如作为另一个函数的参数提供的匿名函数)的上下文中,或者如果没有Identifier,那么解释器将假设代码是一个函数表达式。否则,代码将是函数体(或全局上下文)的一部分,并将被解释为函数声明。清单 2-51 展示了每一个例子。

清单 2-51。 函数声明与函数表达式

// This is not an assignment, there is an Identifier, and it's in the
// global scope, so it's a function declarationfunction myFunction() {
    // function body
}

// This is part of an assignment, so it is a function expression
var myOtherFunction = function foo() {
    // function body
}

// Part of a new expression, so it is a function expression
new function myThirdFunction() {
    // function body

    // This is part of a function body, so it is a function declaration
    function myInternalFunction() {
        // internal function body
    }
}

赋值函数表达式就像变量声明一样被提升,但是只提升它们的声明表达式,而不是它们的赋值表达式。作为一个例子,考虑另一个常见的 JavaScript 面试问题,如清单 2-52 所示。

清单 2-52。 吊装为函数表达式

function myTestFunction() {
    var myInternalFunction = function() {
        return "Hello World.";
    }
    return myInternalFunction();
    var myInternalFunction = function() {
        return "Second Definition.";
    }
}
alert(myTestFunction()); // What will this alert?

在这个例子中,代码将警告“Hello Word”第二个赋值表达式没有被提升,所以myInternalFunction()的赋值是返回字符串“Hello World”。

调用函数

到目前为止,本书中我们一直在调用函数,但我们从未真正定义具体的语法。语法很重要,因为在 JavaScript 中实际上有几种调用函数的方法,如何调用函数将决定它的执行上下文。

在 JavaScript 中,当您调用一个函数时,该函数会收到一个指向其执行上下文的指针,该指针将被设置为关键字this,可以在函数内部访问该关键字。

在 JavaScript 中,有三种方法可以调用函数:

  • 使用函数调用者(),它与函数和方法(附属于对象的函数)一起工作
  • 使用new关键字,就像构造一个新对象一样
  • 使用apply()call()方法

使用调用程序调用函数

在 JavaScript 中,函数 invoker 是一对括号,()。任何计算为函数的表达式都可以使用调用程序来调用。要将参数传递给被调用的函数,需要将它们作为逗号分隔的列表包含在括号中。

当您使用 invoker 调用函数时,函数的执行上下文被设置为window对象。当您调用一个方法时,执行上下文被设置为父对象。清单 2-53 提供了一些例子。

清单 2-53。 测试函数和方法的执行上下文

var myObject = {
    myMethod : function() {
        alert(this === myObject); // Test to see if this does indeed refer to the parent object of a method.
    }
}
function myGlobalFunction() {
    alert(this === window);     // Test to see if this refers to the window for functions
    function mySubFunction() {
        alert(this === window); // Test to see if this refers to window as well.
    };
    mySubFunction();
}

                                // Invoke our tests
myObject.myMethod();            // will alert "true"
myGlobalFunction();             // will alert "true" and then alert "true" again.

在清单 2-53 中,我们用一个方法建立了一个对象,在这个方法中我们测试了一下this关键字是否确实指向了父对象。然后我们创建一个全局函数,测试它的关键字是否被设置为window,并定义它自己的子函数。子函数测试它的关键字是否也设置为窗口对象。

就方法而言,让this引用父对象是 JavaScript 面向对象范式的主要特征之一。它使您能够轻松地访问和修改父对象及其方法。

调用函数作为构造函数

一个函数也可以通过使用new操作符来调用,如清单 2-54 所示。任何计算结果为函数的表达式都可以用这种方式调用。

清单 2-54。 使用 new 关键字调用函数

function myFunction() {
    alert('Hello world!');
}
new myFunction;                 // will alert "Hello world!"

尽管您可以使用new操作符调用任何函数,但它是用来构造新对象的。当您以这种方式调用函数时,JavaScript 会创建一个新的空对象,该对象从操作数继承其原型,并将其设置为函数的执行上下文。因此,当您构建一个构造函数时,this关键字将引用您正在创建的新对象。然后,您的构造函数可以显式返回新对象,或者 new 运算符会自动为您返回它。

这个调用方法给了你创建任何你需要的任意对象构造函数的能力,如清单 2-55 所示。

清单 2-55。 构造函数用来构造一个对象

// A common convention for constructors is to capitalize their first letter.
function Kitty() {
    this.soft = true;
    this.temperature = "warm";
    this.vocalize = function() {
        alert('Purr, purr, purr');
    }
}

var myKitty = new Kitty;                // create a new kitty.
alert(myKitty.soft);                    // will alert true
alert(myKitty.temperature);             // will alert "warm"
myKitty.vocalize();                     // will alert "Purr, purr, purr"

在这个例子中,当我们使用new关键字调用Kitty()函数时,JavaScript 首先创建一个新的空对象,并将其作为执行上下文传递给函数。然后,函数执行,将属性softtemperature以及方法vocalize()添加到对象中。最后,返回该对象,以便将其赋给myKitty变量。

这个语法看起来非常像从一个类中实例化一个对象的语法。但是不要忘记:JavaScript 没有类,只有对象。如果你有基于类的面向对象语言的背景,不要让这种熟悉的语法欺骗你,让你以为你在和类打交道。

使用 apply()和 call() 调用函数

最后,所有的 JavaScript 函数都有两个方法可以用来调用它们:apply()call()。这些方法允许我们为函数指定任何我们想要的上下文。

apply()call()方法有相似的语法:

myFunction.apply(thisContext, arrArgs);
myFunction.call(thisContext, arg1, arg2, arg3, ..., argN);

这两种方法都带有一个thisContext参数,它是一个对象或引用,指定函数的执行上下文。如果不指定参数,JavaScript 将在全局上下文中执行该函数,就像您传递了对window对象的引用一样。

这两种方法的区别在于如何为函数指定参数。使用apply()方法,在数组中指定参数,使用call()方法,以逗号分隔的列表形式提供参数。否则,这两种方法的工作方式完全相同。

清单 2-56 提供了一个使用这些方法调用函数的例子。

清单 2-56。 使用 call()和 apply()来调用函数

var contextObject = {
    testContext: 10
}
var otherContextObject = {
    testContext: "Hello World!"
}

var testContext = 15;                   // Global variable

function testFunction() {
    alert(this.testContext);
}

testFunction();                         // This will alert 15
testFunction.call(contextObject);       // Will alert 10
testFunction.apply(otherContextObject); // Will alert "Hello World!"

在清单 2-56 中,我们创建了两个上下文对象,它们的相同属性被设置为不同的值:一个被设置为数字 10,另一个被设置为字符串“Hello World!”。然后,我们创建一个与属性同名的全局变量,并将其设置为 15。最后,我们创建一个函数来提醒this.testContext的值。

当我们使用 invoker 调用函数时,执行上下文是 window 对象,因此函数会警告全局变量的值。当我们使用call()方法并提供第一个上下文对象作为新的执行上下文时,该函数会提醒该对象的属性值。类似地,当我们使用apply()方法并提供另一个上下文对象时,该函数警告该属性。

条件句

JavaScript 为代码的条件执行提供了一套相当标准的特性:测试表达式,并根据结果执行指定的语句块。

if 语句

JavaScript 中if语句的基本形式是:

if (conditionExpression) { statementBlock }

显而易见,如果conditionExpression评估为真,那么statementBlock中的代码将被执行。如果语句块中只有一行代码,括号是可选的。

JavaScript if语句可以使用else关键字扩展,允许逻辑分支:

if (conditionExpression) {
    statementBlock1
} else {
    statementBlock2
}

这里,如果conditionExpression评估为真,statementBlock1将执行;否则,statementBlock2就会执行。

您可以通过这种方式将if语句链接在一起:

if (conditionExpression1) {
    statementBlock1
} else if (conditionExpression2) {
    statementBlock2
} else if (conditionExpression3) {
    statementBlock3
} else {
    statementBlock4
}

在这个例子中,如果conditionExpression1为真,那么statementBlock1将执行,程序的控制将移动到链的末端。如果conditionExpression1为假,那么conditionExpression2将被求值。如果是真的,statementBlock2将执行,然后程序的控制权将再次移动到链的末端。因此,statementBlock4只有在conditionExpression1conditionExpression2conditionExpression3的计算结果都为假时才会执行;否则,它将永远不会执行。参见清单 2-7 中的if...else语句链示例:

清单 2-57。 一个 if-then-else 块

function alertGenres(authorName) {
    if (authorName === "Neil Gaiman") {
        alert("Fantasy");
    } else if (authorName === "Octavia Butler") {
        alert("Science Fiction");
    } else if (authorName === "Roger Zelazny") {
        alert("Science Fiction and Fantasy");
    } else {
        alert("Unknown author.");
    }
}

alertGenres("Roger Zelazny");    // will alert "Science Fiction and Fantasy"
alertGenres("Arthur C. Clarke"); // will alert "Unknown author."

在清单 2-57 中,我们构建了一个简单的函数来测试一个作者的名字,如果它识别出这个名字,就会提醒这个作者所写的流派的名字。如果作者未被识别,该函数会警告“未知作者”

切换报表

switch语句为大量的if-else-if-else-if-else链提供了一种替代方案,在测试单个conditionExpression并可能产生多个结果时特别有用。以下是switch语句的格式:

switch (expression) {
    case result1:
        statementBlock1
        [break;]
    case result2:
        statementBlock2
        [break;]
    case result3:
        statementBlock3
        [break;]
    default:
        statementBlockDefault
}

在一个case语句中,表达式应该解析为一个case标签。这反过来将导致执行移动到该标签,并从该点开始执行statementBlock。一个可选的break语句将停止块的执行。如果表达式没有解析出一个匹配的标签,那么解释器将寻找一个默认标签,如果存在的话,将执行其相关的statementBlock

为了演示,清单 2-58 提供了一个简单的例子。

清单 2-58。 一个开关语句的琐碎例子

var myColor = "yellow";
switch (myColor) {
    case "red":
        alert("myColor was set to red");
        break;
    case "yellow":
        alert("myColor was set to yellow");
    case "green":
        alert("myColor was set to green");
    default:
        alert("myColor was an unknown color");
}

在清单 2-58 中,myColor解析为“红色”,因此switch语句将警告“myColor 被设置为红色”。因为“红色”代码块包括一个break语句,所以switch语句的执行结束,程序在右括号后继续。如果myColor被改为设置为“黄色”,那么switch语句将移动到“黄色”情况,并警告“我的颜色被设置为黄色”。然后,因为“黄色”代码块没有break语句,程序将执行“绿色”情况,然后是默认情况。

任何编程语言都需要执行的最常见的任务通常是重复性或迭代性的任务。例如,假设您想检查给定 HTML 页面上的所有链接,看看其中是否有包含特定文本值的链接。JavaScript 有一组循环语句来处理这些情况。

一般来说,循环是一个重复执行的语句块,直到满足指定的条件。JavaScript 有四种基本的循环:for循环、for-in循环、while循环和do循环。

对于循环

大概最常用的循环语句是for循环。一般来说,for循环反复执行它的语句块,直到指定的条件为假。此时,程序会在循环结束后执行下一条语句。一个for循环将在第一次执行它的语句块之前执行它的条件检查,所以有可能(如果第一次条件检查为假)语句块永远不会执行。

for循环的语法是:

for (initialExpression; conditionExpression; incrementExpression)
    statementBlock

一个for循环执行如下:

  1. 执行initialExpression。通常这是用来定义和初始化计数变量,但它可以是任何有效的表达式。initialExpression也是可选的。
  2. conditionExpression进行评估。如果表达式评估为真,那么statementBlock将执行。如果表达式的计算结果为假,循环将终止而不执行statementBlockconditionExpression在技术上是可选的;如果你忽略它,解释器假设条件总是真的,因此总是执行循环,这意味着你必须手动中断循环。
  3. statementBlock执行之后,incrementExpression被执行。这个表达式也是可选的。一旦执行完毕,就重复步骤 2。

清单 2-59 提供了一个简单的例子来演示循环执行。

清单 2-59。 Simple为循环

for (var i =0; i < 10; i++) {
    alert(i); // will alert 0 through 9 one at a time
}

在此示例中,循环执行如下:

  1. 变量i被声明并设置为 0。
  2. 循环检查i < 10是否存在,如果存在,则执行其语句块(在这种情况下,警告i的值)。
  3. i增加 1,重复步骤 2。

请注意,因为每次循环执行时都要计算conditionExpression,如果它是一个开销很大的语句,那么它可能会在您的程序中导致严重的性能问题,尤其是当循环被执行数百次或数千次时。让conditionExpression尽可能简单是个好主意。例如,考虑清单 2-60 中的循环。

清单 2-60。 潜贵循环

for (var i = 0; i < someArray.length; i++) {
    // do things
}

在清单 2-60 中,在每次循环开始时,我们检查someArray数组的length属性。这并不是很贵,但是如果someArray有几千件商品,它就能加起来。清单 2-61 提供了一个简单的优化。

清单 2-61。 为循环优化

var someArrayLength = someArray.length,
    i = 0;
for (i = 0; i < someArrayLength; i++) {
    // do things
}

在清单 2-61 中,我们将数组的长度存储在一个变量中,从而简化了conditionExpression。我们还将i的变量声明移到了循环之外,以明确定义它的作用域。这些都是微小的改进,但是如果阵列很长,好处会很快增加。

for-in 循环

JavaScript 还有一个for-in循环语句。此构造专门用于枚举对象属性:

for (var variable in objectExpression) { statementBlock }

任何计算为对象的表达式都可以在objectExpression中使用;大多数情况下,它只是某种物体。在每次迭代中,对象的一个属性将被赋给变量,然后执行statementBlock。清单 2-62 中显示了一个例子。有关使用for-in循环枚举对象属性的更多信息和例子,请参阅本章前面的“对象”一节。

清单 2-62。 列举一个对象

var myObject = {
    prop1: "value1",
    prop2: "value2",
    prop3: true,
    prop4: 100
}
var strAlert = "";
for (var prop in myObject) {
    strAlert += prop + " : " + myObject[prop] + "\n";
}
alert(strAlert);

这个例子将提醒

prop1 : value1
prop2 : value2
prop3 : true
prop4 : 100

while 循环

JavaScript 也有while循环,只要它们的条件评估为真,就执行它们的语句块:

while (conditionExpression)
    statementBlock

for循环一样,每次在执行statementBlock之前都会测试条件。如果在第一次循环中,条件评估为假,那么statementBlock将永远不会执行。清单 2-63 提供了一个例子。

清单 2-63。 琐碎而循环

var counter = 0;
while (counter < 10) {
    alert(counter);
    counter++;
}

在这个例子中,counter变量在循环中递增,所以当它最终达到 10 时,循环将终止。这个例子将导致从 0 到 9 的数字报警,一次一个。

做循环

while循环类似,do循环在其条件评估为真时执行其语句块:

do
    statementBlock
while (conditionExpression);

while循环和for循环不同,在do循环中,statementBlockconditionExpression求值之前第一次被执行。因此,do循环的statementBlock将总是至少执行一次。清单 2-64 提供了一个例子。

清单 2-64。 琐碎做循环

var counter = 0;
do {
    alert(counter);
    counter++;
} while (counter < 10)

和前面的例子一样,这个例子将从 0 到 9 发出警报,一次一个。

摘要

在本章中,我们讨论了使用 JavaScript 的基础知识:

  • JavaScript 表达式是计算值的代码段,语句是实现特定目标的表达式块。
  • JavaScript 有几种不同的操作符:算术、赋值、按位、比较、逻辑和字符串,还有一些不属于这些类别。
  • JavaScript 有特定的优先级来决定在有多个操作符的语句中哪些操作符应该先执行。
  • var关键字声明的变量的作用域限于当前作用域;通过访问简单声明的变量被认为是全局变量。
  • 您可以使用点符号或方括号来访问对象的属性。
  • 可以用文字符号、构造函数或Object.create()方法创建对象。
  • JavaScript 数组是动态的,不会抛出越界错误。
  • 您可以通过表达式或声明来创建函数。
  • 调用函数的方式决定了它的执行范围。
  • 您可以使用call()方法或apply()方法来指定函数的执行范围。
  • JavaScript 同时支持if-then-elseswitch条件。
  • JavaScript 支持几种循环类型:for循环、for-in循环、while循环和do循环。

在下一章,我们将深入探讨文档对象模型:用 JavaScript 表示 HTML 页面。我们将应用本章的知识来创建动态网页、动画和其他效果。

三、DOM

在这一章中,我们将介绍文档对象模型 ,或 DOM。我们将从说明什么是 DOM 以及它是如何随着时间的推移而发展的开始。然后我们将探索它的内部结构和公开的属性和方法,接着详细讨论使用 JavaScript 处理 DOM。

在本章结束时,你应该对 DOM 及其内部工作原理有一个坚实的理解。您还应该对管理它的标准、在哪里可以找到它们以及它们是如何发展的有所了解。您不仅应该了解如何在脚本中处理用户事件,包括如何手动触发它们,还应该了解如何创建自己的自定义事件。

我是如何学会停止担忧并热爱大教堂的

文档对象模型就是这样:一个对当前加载到浏览器中的文档进行建模的对象。文档中的每个元素在 DOM 中都有相应的存在:每个段落、每个列表项(和每个列表)、每个跨度等等。这包括在呈现的文档中可能不可见的元素,比如<script>标签、样式表等等。甚至文档的标题也会在 DOM 中。如果它在文档的标记中,它就会出现在 DOM 中。此外,DOM 提供了许多用于访问和操作这些元素的有用属性。

DOM 还提供了一个事件模型,用于捕获用户与文档的交互:按键、鼠标移动等等。使用 DOM 事件,您可以编写响应用户交互的脚本,从简单的事情,比如当用户单击某个段落时高亮显示它,到在屏幕上拖放元素。

唐!唐!= JavaScript〔??〕

JavaScript 经常因为难以处理而受到抨击,而实际上具体的问题实际上与 DOM 有关,而不是 JavaScript 本身。理解 DOM 不是 JavaScript 很重要。DOM 是浏览器制造商提供的文档接口。这两者紧密交织在一起,许多人错误地认为它们是一样的,但事实并非如此。

很容易将 JavaScript 和 DOM 混为一谈,因为您在浏览器中用 JavaScript 做的大部分事情都涉及到操作浏览器加载的文档。但是如果您要在不同的上下文中访问 JavaScript 解释器,例如,在服务器或工作站上使用 Node.js,您不一定需要访问 DOM。

DOM 实际上不是 JavaScript 的一部分,也不是由 ECMA-262 标准定义的。相反,DOM 由自己的标准管理,由万维网联盟(W3C)维护。尽管 DOM 不是 JavaScript 本身的一部分,但是没有提到它,任何 JavaScript 参考都是不完整的。您在基于浏览器的 JavaScript 中所做的大部分工作都将涉及到 DOM。事实上,DOM 和 JavaScript 如此紧密地交织在一起,以至于不太有经验的 JavaScript 开发人员将 DOM 特性称为 JavaScript 特性并不罕见。

DOM 标准的产生直接源于 Web 历史初期所谓的“浏览器战争”。当时,浏览器制造商决定他们想要如何解析 HTML 文档,以及他们想要向他们的 JavaScript 引擎公开什么(如果有的话)。结果,web 开发成了依赖于浏览器的代码和“在 Netscape 中最佳浏览”以及“在 Internet Explorer 中最佳浏览”横幅的噩梦。

作为回应,业界决定不仅要标准化用于构建 Web 的语言(HTML、CSS 和 JavaScript ),还要标准化浏览器应该如何实现这些语言。这保证了所有浏览器之间的公平竞争,使得抛弃依赖于浏览器的编码成为可能。

然而,实现这一承诺需要一些时间。网络标准化是一项艰巨的任务,从许多方面来说,是一个永远不会真正“完成”的过程。技术在不断发展,导致标准也随之变化,这对浏览器制造商来说很难,因为他们必须瞄准多个移动目标。即便如此,正如我们所知,DOM 和其他 web 标准为现代 Web 铺平了道路。

DOM 标准的历史

如前所述,DOM 由它自己的独立标准管理,该标准归 W3C 所有。DOM 标准最初是在三个主要迭代中开发的,第 1 级、第 2 级和第 3 级。

第一级 DOM 标准是第一个被提出来的,它为所有其他标准提供了基础。它有两个主要特性:一组通用的底层接口,用于表示任何结构化的标记文档,特别是对 HTML 文档通用规范的扩展。

HTML :描述性、结构性、语义性

一般来说,标记语言是指定注释文档的语法的一族语言。概括地说,标记语言分为三类:

  • 表示:注释通常是低级的(通常不是人类可读的)格式,用于指定应用应该如何显示内容。例如,文字处理器经常使用表示性标记。
  • 程序性:类似于表示性,但是注释是更高级的格式。注释通常是人类可读的,旨在指定内容应该如何显示。示例包括 PostScript、troff 和 TeX。
  • 描述性:注释用于根据属性描述文档的各个部分。如何显示内容由解释应用决定。示例包括 Scribe、SGML 和 HTML。

HTML 的标签根据它们各自的结构属性来注释内容,所以 HTML 通常被称为“结构”标记语言。它也经常被称为“语义”标记语言,因为许多结构注释引用了它们目标内容的语义,例如段落的<p>,或者标题的<header>。然而,一些 HTML 标签在语义上是空的,仅指结构,例如<div><span>标签。

二级 DOM 标准紧随其后,提供了比一级更大的深度。它实际上由六个独立的规格组成:

  • 核心:扩展了 1 级 DOM 核心规范,并包含了新的 XML 接口。规格在www.w3.org/TR/DOM-Level-2-Core/
  • 事件:提供在大多数现代浏览器中实现的事件模型。具体请参见下面的 DOM 事件。链接:www.w3.org/TR/DOM-Level-2-Events/
  • HTML :用 HTML 和 XHTML 特有的特性扩展了 2 级核心规范。链接:www.w3.org/TR/DOM-Level-2-HTML/
  • 遍历和范围:提供识别文档内容范围的接口,以及在 DOM 中移动的接口:例如,从一个给定的元素开始,找到它的所有兄弟元素,或者它的所有子元素。链接:www.w3.org/TR/DOM-Level-2-Traversal-Range/
  • Style :为文档的层叠样式表(CSS)提供接口,以及应用于元素的样式。链接:www.w3.org/TR/DOM-Level-2-Style/
  • 视图:指定浏览器呈现给脚本引擎的视图,包括 JavaScript。链接:www.w3.org/TR/DOM-Level-2-Views/

第三级 DOM 规范是对第一级和第二级的扩展。它由五个独立的规格组成:

  • 核心:作为 1 级和 2 级核心的进一步扩展,提供新的接口和方法。链接:www.w3.org/TR/DOM-Level-3-Core/
  • Events :扩展了 2 级事件规范,为键盘和鼠标滚轮事件以及突变事件(当 DOM 节点被修改时触发的事件)提供了事件规范。链接:www.w3.org/TR/DOM-Level-3-Events/
  • 加载并保存:指定如何解析 XML 并从中生成 DOM 树,以及如何将 DOM 树序列化为 XML。链接:www.w3.org/TR/DOM-Level-3-LS/
  • 验证:指定当文档被各种方法更改时,如何保持文档内部的一致性。链接:www.w3.org/TR/DOM-Level-3-Val/
  • XPath :提供了使用 XPath 访问 DOM 树的规范。链接:www.w3.org/TR/DOM-Level-3-XPath/

最后,HTML5 系列规范是 W3C 在 2012 年 12 月推荐的候选规范,它包含了 DOM 规范,这些规范包含了以前规范中的大部分内容。

目前,有十几个文档指定了 DOM 标准,其中一些完全或部分地包含了其他标准。为了避免混淆,也为了避免无意中将 DOM 标准与 HTML5 语言规范捆绑在一起(您应该能够将 DOM 标准用于任何结构化标记语言,而不仅仅是 HTML),已经开始将所有不同的部分整合到一个规范中。您可以在以下两个位置查看结果:

  • W3C 的 DOM4(http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html)2012 年 12 月工作草案。该版本提供了更多关于各种版本如何合并在一起的背景信息,包括未来的整合计划。
  • Web 超文本应用技术工作组(WHATWG)的动态 DOM 规范(http://dom.spec.whatwg.org/)。

WHATWG 是一个独立于 W3C 的组织,它的成立是为了回应对 W3C 方法的关注。这两个组织之间存在一定程度的紧张,但最终他们有着相同的目标,尽管他们维护着不同版本的 DOM 规范,但他们的版本永远不会不一致。我们鼓励您查看这两个版本;它们提供了相同信息的略微不同的视图。

浏览器依赖关系

仅仅因为 DOM 存在管理标准,并不意味着所有浏览器都平等地实现它们。DOM 标准是随着时间的推移而产生的,其驱动力是控制浏览器制造商的不同和分散的文档模型实现的需要,即使在今天,标准化仍在进行中。我们在前两章学习的 JavaScript 在跨浏览器时相当稳定。对于 DOM,我们将会遇到不同浏览器之间的实现差异,甚至同一浏览器的不同版本之间的差异。不幸的是,这些变化通常非常明显,如果不加以解决,可能会导致代码在一种浏览器中运行,但在其他浏览器中却顽固地拒绝运行。

好消息是,现代浏览器(Internet Explorer 9 和更高版本、Firefox 4 和更高版本、Chrome、Safari 和 Opera)很好地实现了这些标准。Internet Explorer 的旧版本是不良实现的罪魁祸首,不幸的是,您经常不得不在您的项目中提供对这些旧版本的支持。大多数情况下,这些实现问题可以用 JavaScript 来缓解。更好的是,已经有几十个优秀的、经过良好测试的 JavaScript 库可以做到这一点。

这些库中最流行的可能是 jQuery,它最初是由 John Resig 创建的,现在作为一个开源项目维护着。根据一些浏览器统计,jQuery 是 Web 上部署最广泛的库,因此它肯定经过了很好的测试。我们将在第四章中介绍 jQuery 的使用。

jQuery 不是唯一可用的库;还有很多其他的。最早创建的库之一叫做 Prototype,它仍然被广泛使用,也是一个积极维护的开源项目。其他值得注意的库有 Dojo Toolkit、script-aculo-us、MooTools 和 Yahoo 的 YUI 库。如果你想要提供更多支持的东西——更像一个框架的东西——有 Sencha Ext JS、Closure Library 和 AngularJS(都是谷歌的)、Backbone.js 和 Montage(JavaScript 框架领域的新来者,但却是我们的最爱)等选择。

在这一章中,我们将从标准的角度来讨论 DOM。当我们在实现中遇到严重分歧时,我们会提到它们,但可以肯定的是,浏览器版本越老,它不能正确实现标准的某些方面的可能性就越大。我们还将讨论缓解技术,因此您应该能够解决最严重的问题。总的来说,如果您发现自己不得不支持许多旧版本的浏览器,您可能会发现一个精心选择的 JavaScript 库可以避免许多令人头疼的问题。

如果您将支持旧版本的浏览器,您将需要评估它们对项目所需功能的支持程度。Peter-Paul Koch 在他的 QuirksMode 网站上维护了两个优秀的页面,一个是 DOM 特性页面(www.quirksmode.org/dom/w3c_core.html),另一个是 DOM 事件页面 ( www.quirksmode.org/dom/events/)。这些页面是评估您的浏览器支持情况的一个很好的起点。

DOM 结构

现在您已经知道了 DOM 是什么以及它是如何产生的,您已经准备好深入了解它实际上是如何为您工作的了。从概念上讲,DOM 结构可以被认为是一棵树,它表示加载到浏览器中的文档和子文档,以及每个文档和子文档的单个元素。

该结构的顶部是window对象,它代表包含加载文档的实际浏览器窗口。因为 HTML 文档可以通过使用 iframes 包含子文档,所以window对象实际上是一个类似数组的对象。它有一个length属性,代表文档包含的 iframe 的数量,这些 iframe 可以通过索引(例如window[0])访问,或者,如果<iframe>标签有name属性,可以通过window['iframename']访问。window对象还有一个frames属性,它是一个数组,表示文档中 iframes 的数量。它只是对window对象本身的引用(换句话说,如果存在子文档,就是window === window.frameswindow[0] === window.frames[0]),但是它的优点是更加明确。两种语法都可以。

每个子文档都有自己的window对象,因为任何给定的子文档本身都可以包含子文档,所以子文档的window对象也是一个类似数组的对象。

window对象有几个其他有用的方法和属性。window对象也作为 JavaScript 的全局上下文,如清单 3-1 所示。

清单 3-1。 窗口对象是 JavaScript 的全局上下文

myVar = 5;           // defined without var keyword, so it is global
alert(window.myVar); // will alert 5

因为window为 JavaScript 提供了全局上下文,所以您不需要将document对象显式地作为window对象的属性来访问;相反,你可以直接访问它(换句话说,引用window.document.title和引用document.title anywhere in your script是一回事)。

window对象有一个document属性,它是对已经加载到浏览器窗口中的文档的引用。document对象包含了代表文档本身元素的树。每个段落标记、每个 span 标记、每个 div 标记,甚至脚本标记、HTML 和 body 标记以及文档标题都将出现在document对象中。如果它在 HTML 标记中,它将被表示在document对象中。此外,document对象有几个访问元素的方法,并为事件提供基础。

在结构上,DOM 由节点组成,每个节点代表 HTML 文档中的内容(通常是一个标签,但也可以代表注释和元数据)。因为 HTML 是结构化的,所以节点是结构化组织的:DOM 中元素的节点可以有子节点,这些子节点表示元素本身包含的标签。类似地,DOM 中的一个节点可以有一个表示该元素的父标记的父节点。

考虑清单 3-2 中的简单示例文档。

清单 3-2。 简单的 HTML 文档

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
          <p id="myParagraph">This is my paragraph! <span class="hideme">Lorem ipsum</span> dolor sit amet.<span></span></p>
        <p class="hideme">Another paragraph!</p>
        <script>
alert(document.title); // will alert "JavaScript Programmer's Reference"
        </script>
    </body>
</html>

HTML5 示例

清单 3-2 是我们展示整个 HTML 文档而不仅仅是 JavaScript 片段的第一个例子。因为 DOM 操作涉及到处理 HTML 文档,所以我们将在本章的例子中使用这种格式。特别是,我们将使用 HTML5,如 DOCTYPE 标签所指定的。

请注意,如果您使用的是特别旧的浏览器版本(例如,Internet Explorer 6 或 Firefox 2),这些示例可能会有问题。(如果您使用的是这种旧版本的浏览器,我们鼓励您尽可能升级。)

如果加载这个文档,浏览器会提示“JavaScript 程序员参考”,因为标题被document.title属性引用。

正文中的节点由document对象上的childNodes属性引用:

alert(document.childNodes); // Depending on browser, will alert something like "[object NodeList]"

DOM 中的节点由类似数组的节点列表表示。它们有一个length属性,表示存在多少个节点,单个节点本身可以通过它们的索引来访问。

每个单独的节点都有不同的属性,这取决于它所代表的元素,但本质上,元素上的任何属性都有一个作为节点上的属性的表示。例如,段落标签上的 class 属性将在其节点上的className属性中表示。

使用这种节点树,您可以访问 DOM 中的任何元素。例如,document.childNodes[1].childNodes[2].childNodes[3]表示我们标记中的第二段:

alert(document.childNodes[1].childNodes[2].childNodes[3].className); // will alert "hideme"

单个节点通过parentNode属性引用它们的父节点。因为一个标签只能有一个父标签,所以parentNode属性不是一个类似数组的集合,而是一个简单的属性:

alert(document.childNodes[1].childNodes[2] == document.childNodes[1].childNodes[2].childNodes[3].parentNode); // will alert "true" because the body tag is the parent node of the paragraph

这是 DOM 的基本结构,但是在访问元素时,它非常笨拙。你可以访问任何东西,但是即使我们的超级简单的文档,我们已经产生了相当长的引用链。想象一下,如果我们有一个复杂的文档,这些链会有多长!幸运的是,DOM 提供了比通过这些长引用链访问元素更好的方法。

访问 DOM 中的元素

通常,您希望直接访问 DOM 中的元素,并对它做一些事情:隐藏、显示、移动、删除、监听事件等等。DOM 提供了几种不同的方法来直接访问元素,或者从一个已知的位置开始遍历树。

可能最著名和最简单的访问元素的方法是使用document.getElementById()方法,如清单 3-3 所示。

清单 3-3。 使用 getElementById()

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
        <body>
        <p id="myParagraph">This is my paragraph! <span class="hideme">Lorem ipsum</span> dolor sit amet.<span></span></p>
        <p class="hideme">Another paragraph!</p>
        <script>
var myPar = document.getElementById("myParagraph"); // myPar is now a pointer to the paragraph.
myPar.innerText = "I have changed the content!"; // Change the text of the paragraph to "I have changed the content!"
        </script>
    </body>
</html>

在这个例子中,我们得到一个指向特定节点的指针,然后用新内容更新它的innerText属性。当这个文档加载时,您将看到两段:第一段将显示“我已经更改了内容!”而第二个会读作“又一段!”

在清单 3-3 中,document.getElementById()方法和innerText属性是由 DOM 标准而不是 JavaScript 标准指定的。由于它们与浏览器中的 JavaScript 无缝集成,您可以看到很容易将它们误认为 JavaScript,如前所述。

DOM 还公开了其他几种直接访问节点的方法。getElementsByTagName()方法将一个标签名作为参数,并返回它在文档中找到的属于该标签的所有节点的集合,如清单 3-4 所示。集合是一个类似数组的对象,所以你可以迭代单个元素,它们在文档中按照解析的顺序出现,如清单 3-4 所示。

清单 3-4。 使用 getElementsByTagName()

<!DOCTYPE html>
<html>    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <p id="myParagraph">This is my paragraph! <span class="hideme">Lorem ipsum</span> dolor sit amet.<span></span></p>
        <p class="hideme">Another paragraph!</p>    <script>
var myPars = document.getElementsByTagName("p"),
  mySpans = document.getElementsByTagName("span"),
  myParsLength = myPars.length,
  mySpansLength = mySpans.length,
  i;

// Give paragraphs a red background color
for (i = 0; i < myParsLength; i++) {
  myPars[i].style.backgroundColor = "red";
}

// Add some content to our empty span, and alert its index
for(i = 0; i < mySpansLength; i++) {
  if (mySpans[i].innerText === "") {
    mySpans[i].innerText = "No longer empty!";
    alert(i);
  }
}        </script>
    </body>
</html>

清单 3-4 将两个段落都改为红色,添加文本“不再是空的!”到空跨度,并警告数字 1,它是由getElementsByTagName()方法返回的跨度集合中的空跨度的索引。

在 HTML5 之前,getElementById()getElementsByTagName()是 DOM 标准指定的直接访问元素的两种方法。HTML5 规范增加了三个新方法:getElementsByClassName()querySelector()querySelectorAll()

getElementsByClassName()方法的工作方式类似于getElementById(),除了它将一个类作为参数,并返回一个类似数组的包含该类元素的集合,如清单 3-5 所示。

清单 3-5。 使用 getElementsByClass()

<!DOCTYPE html>
<html>
    <head>
    	<title>JavaScript Programmer's Reference</title>
    </head>
    <body>
      <p id="myParagraph">This is my paragraph! <span class="hideme">Lorem ipsum</span> dolor sit amet.<span></span></p>
      <p class="hideme">Another paragraph!</p>
      <script>
var hideme = document.getElementsByClassName("hideme"),
  hidemeLength = hideme.length,
  i;

// Hide all the elements that have a class of "hideme"
for (i = 0; i < hidemeLength; i++) {
  hideme[i].style.display = "none";
}
      </script>
    </body>
</html>

在清单 3-5 的中,我们获取了所有具有“hideme”类的元素,然后通过将其 CSS display属性改为“none”来隐藏每个元素

querySelector()querySelectorAll()方法比其他方法提供了更多的灵活性。这两种方法都将任何有效的 CSS 选择器作为参数。querySelector()参数将返回文档中匹配的第一个元素(按标记顺序),而querySelectorAll()将返回与选择器匹配的所有元素的类数组集合(如果只有一个匹配,方法将返回一个有一个成员的类数组对象;如果没有匹配,该方法将返回一个没有成员的类似数组的对象,即长度为 0)。这给了我们一个强大的工具来访问 DOM 中的元素,如清单 3-6 所示。

清单 3-6。 使用 querySelector()

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
      <p id="myParagraph">This is my paragraph! <span class="hideme">Lorem ipsum</span> dolor sit amet.<span></span></p>
      <p class="hideme">Another paragraph!</p>
      <script>
var emptySpan = document.querySelector("#myParagraph span:last-child"); // Get the last span
emptySpan.innerText = "Not empty anymore!" // And give it some text.
      </script>
    </body>
</html>

在这个例子中,我们使用 CSS 伪选择器last-child访问段落的最后一段。当它运行时,这个例子将会显示“不再是空的!”在最后一段时间里。

关于使用querySelector()querySelectorAll()的一个有趣的事情是,你想用 JavaScript 访问的元素通常就是你想用样式表访问的元素。因此,您经常会发现在 CSS 中使用的一些相同的选择器会作为查询出现在 JavaScript 中。

image 注意如果您打算编写依赖于querySelector()querySelectorAll()的代码,并计划支持旧版本的浏览器,这些版本可能不会为您提供这些方法,因为这些方法是最近添加到 DOM 标准中的。在这种情况下,您总是可以使用选择器库来添加特性。最常用的选择引擎叫做 Sizzle,在www.sizzlejs.com/可以买到。它不依赖于其他库,非常小,非常高效,并且经过了很好的测试(它是 jQuery 库中包含的引擎)。

最后,DOM 中的每个节点都拥有这些元素选择方法,就像顶级的document对象一样。当你在一个 DOM 节点上使用其中一个方法时,这个方法的范围被限制在那个节点的子节点上,正如你在清单 3-7 中看到的。

清单 3-7。 使用元素上的方法

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
      <p id="myParagraph">This is my paragraph! <span class="hideme">Lorem ipsum</span> dolor sit amet.<span></span></p>
      <p class="hideme">Another paragraph!</p>
      <script>
var myPar = document.getElementById("myParagraph"), // Get a reference to the first paragraph
  mySpan = myPar.querySelector("span:last-child"); // Get a reference to the last span in that paragraph

mySpan.innerText = "Not empty anymore!"
      </script>
    </body>
</html>

这个例子,如清单 3-6 ,将添加文本“不再为空!”到那一段的最后一段。在这种情况下,我们首先使用getElementById()获得对段落的引用,然后使用querySelector()只搜索该段落的子元素。这也适用于 DOM 片段(关于 DOM 片段的详细信息,请参见下一节)。

遍历 DOM

访问 DOM 中元素的另一种方式是从 DOM 树中的一个已知位置开始,然后使用父/子/兄弟关系遍历到一个不同的位置。在前一节中,我们已经看到了一个基本的例子。幸运的是,DOM 为遍历提供了一些方便的属性:

  • Node.firstChild:引用节点的第一个子节点
  • Node.lastChild:引用节点的最后一个子节点
  • Node.nextSibling:引用节点的下一个兄弟节点
  • Node.previousSibling:引用该节点的上一个兄弟节点

清单 3-8 显示了这些遍历属性的一个例子。

清单 3-8。 使用遍历属性

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
      <p id="myParagraph">This is my paragraph! <span class="hideme">Lorem ipsum</span> dolor sit amet.<span></span></p>
      <p class="hideme">Another paragraph!</p>
      <script>
var mySpan = document.getElementById("myParagraph").lastChild;
mySpan.innerText = "Not empty anymore!"
      </script>
    </body>
</html>

此示例将在第一段的最后一个跨度中填入文本“不再为空!”

现在您已经知道了如何访问 DOM 中的元素,下一节将介绍如何使用它们。

修改 DOM

除了使您能够访问文档中的元素之外,DOM 还为操作这些元素提供了一个灵活的框架。您可以通过更改现有元素的属性、内容,甚至将它们从 DOM 中的一个地方完全移到另一个地方来修改它们。您也可以删除元素并创建新的元素。

修改现有元素

您可能想要对现有元素进行的最基本的修改是访问并更改其属性。大多数简单元素属性在元素的关联节点上显示为简单属性,您可以直接获取和设置值。

例如,清单 3-9 展示了如何直接改变一个简单锚标签的href属性。

清单 3-9。 修改元素的属性

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
      <p id="myParagraph"><a href="http://www.yahoo.com/">This is my link!</a></p>
      <script>
var myLink = document.querySelector("#myParagraph a");
myLink.href = "http://www.google.com";
      </script>
    </body>
</html>

在这个例子中,我们正在修改href属性,将其从www.yahoo.com更改为www.google.com。如果你把这个例子载入你的浏览器,点击链接,它会把你带到谷歌而不是雅虎。使用这种技术,您可以修改元素的大多数简单属性:namehref,甚至元素的id

修改样式

对于某些属性,DOM 提供了更健壮的接口。例如,在清单 3-10 的中,一个元素的style属性提供了一个元素上所有内联样式的映射。

清单 3-10。 修改元素的样式属性

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
      <p id="myParagraph"><a href="http://www.yahoo.com/">This is my link!</a></p>
      <script>
var myLink = document.querySelector("#myParagraph a");
myLink.style.backgroundColor = "#ff0000";
myLink.style.color = "#fff";
      </script>
    </body>
</html>

在本例中,我们修改了链接,使背景色为红色,前景色为白色。

当访问一个元素的style属性时,如清单 3-11 所示,你所做的就是处理内联样式。style属性不是通过样式表应用于元素的样式的表示,如清单 3-11 所示。

清单 3-11。style 属性仅用于内联样式

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
        <style>
#myParagraph a {
  background-color: #ff0000;
  color: #ffffff;
}
        </style>
    </head>
    <body>
      <p id="myParagraph"><a href="http://www.yahoo.com/">This is my link!</a></p>
      <script>
var myLink = document.querySelector("#myParagraph a");
alert(myLink.style.backgroundColor); // will alert "" (empty)
alert(myLink.style.color); // will alert "" (empty)
      </script>
    </body>
</html>

即使我们已经设置了背景颜色和文本颜色,这两个警告都是空的。这是因为它们是使用样式表而不是内联样式设置的。

类似地,如果您修改了元素的style属性,DOM 会将您的更改作为内联样式插入。检查清单 3-11 中的元素,你会看到它看起来像这样:

<a href="http://www.yahoo.com/" style="background-color: rgb(255, 0, 0); color: rgb(255, 255, 255);">This is my link!</a>

您可以通过使用window对象的 DOM 的getComputedStyle()方法来确定哪些样式当前正在元素上工作,如清单 3-12 所示。此方法采用一个元素引用并返回一个对象,该对象表示元素上当前活动的样式,无论这些样式来自样式表还是内联样式。该对象将与元素上的style属性具有相同的格式。

清单 3-12。 使用 window.getComputedStyle()方法

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
        <style>
#myParagraph a {
  background-color: #ff0000;
  color: #ffffff;
}
        </style>
    </head>
    <body>
      <p id="myParagraph"><a href="http://www.yahoo.com/" style="color: #00ff00">This is my link!</a></p>
      <script>
var myLink = document.querySelector("#myParagraph a"),
  styleObject = window.getComputedStyle(myLink);

alert(styleObject.backgroundColor); // will alert something like "rgb(255, 0, 0)"
alert(styleObject.color); // will alert something like "rgb(0, 255, 0)"
      </script>
    </body>
</html>

本例将首先警告应用于目标元素背景的颜色——在本例中为红色或 rgb(255,0,0)。第二个警告将显示文本的颜色。在这种情况下,我们有两个冲突的样式,一个在样式表中,另一个是内联样式。内联样式具有更高的特异性,因此它会胜出,脚本将发出绿色或 rgb(0,255,0)警告。

另一个要更改的常见属性是元素的类。HTML5 DOM 规范包括一个用于管理类的健壮接口:classList属性。当直接访问时,classList属性将返回一个类似数组的对象,其中包含应用于该元素的类。(如果没有类应用于元素,对象的长度将为 0。)每个单独的类都可以通过索引来访问。此外,classList属性公开了一组有用的助手方法(参见清单 3-13 中的使用):

  • classList.add(classname):在classList中增加classname类。

  • classList.contains(classname):如果classname出现在classList中,则返回 true。

  • classList.remove(classname):从classList中移除类classname

  • classList.toggle(classname): If classname is present in classList, it is removed; otherwise it is added.

    清单 3-13。 使用 classList 接口

    <!DOCTYPE html>
    <html>
        <head>
            <title>JavaScript Programmer's Reference</title>
            <style>
    .redclass {
      background-color: #ff0000;
    }
    .greenclass {
      background-color: #00ff00;
    }
            </style>
        </head>
        <body>
          <p id="myParagraph" class="redclass">Here is a paragraph.</p>
          <script>
    var myPar = document.getElementById("myParagraph");
    myPar.classList.toggle("redclass"); // removes redclass from classList
    myPar.classList.add("greenclass"); // adds greenclass to classList
          </script>
        </body>
    </html>
    

在本例中,段落的背景色是绿色。

旧版本的浏览器没有classList接口,你必须通过className属性手动修改类字符串。此外,许多 JavaScript 库提供了管理类的方法。

修改内容

另一个常见的任务是访问和修改元素的内容。DOM 提供了一个属性来访问元素中的实际标记,还提供了一个属性来访问标记中包含的文本:

  • Node.innerHTML:提供一个节点内部 HTML 的接口。当简单访问时,它返回包含在节点中的 HTML。如果用作 setter,它将删除元素中包含的 HTML(及其在 DOM 中的关联节点),并用指定的 HTML 替换它(并将关联节点添加到 DOM 中)。这个接口最初是由 Internet Explorer 团队在许多年前创建的,它非常有用,以至于在它成为 HTML5 标准的一部分之前,所有其他浏览器团队都实现了它。
  • Node.innerText(非标准,在除 Firefox 之外的所有浏览器中可用)或Node.textContent(标准,在除 Internet Explorer 之外的现代浏览器中可用):类似于Node.innerHTML,除了它只返回节点中包含的所有元素的文本。它不返回任何 HTML 标记。当用作 setter 时,它删除节点中的所有内容,并插入提供的文本。

清单 3-14 展示了这两种方法的使用示例。

清单 3-14。 使用 innerHTML 和 innerText

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
      <p id="firstParagraph">Here is a paragraph.</p>
      <p id="secondParagraph">Here is another paragraph. It contains <span>some other tags,</span> <a href="http://www.google.com/">as well.</a></p>
      <script>
var firstPar = document.getElementById("firstParagraph"),
  secondPar = document.getElementById("secondParagraph");

alert(firstPar.innerText); // will alert "Here is a paragraph."
alert(secondPar.innerText); // will alert "Here is another paragraph. It contains some other tags, as well."
alert(firstPar.innerHTML); // will alert "Here is a paragraph."
alert(secondPar.innerHTML); // will alert "Here is another paragraph. It contains <span>some other tags,</span> <a href="http://www.google.com/">as well.</a>"

firstPar.innerText = "I have changed the text."; // will change the text of the first paragraph
secondPar.innerHTML = "<ul><li>How do I love thee?</li><li>Let me count the ways!</li></ul>"; // will change the HTML inside the second paragraph
      </script>
    </body>
</html>

在这个例子中,我们首先使用属性来查看段落的内容,然后使用它们来更改内容。

image 警告这些方法非常有用,但是有一个重要的警告:使用innerHTML时要非常小心,因为你放入的任何东西都会被解析成 DOM 节点并插入到 DOM 中。如果不仔细清理插入到 DOM 中的 HTML,就会出现严重的安全问题。具体来说,对于来自用户或者您无法完全控制的任何内容,使用innerHTML时要非常小心。这些方法将插入任何 HTML,包括脚本标记,因此如果您盲目地将用户提供的 HTML 插入到您的文档中,那么对于用户来说,包含可能访问您的应用数据并完全危及您的安全性的恶意脚本是微不足道的。

创建新元素

除了innerHTML,DOM 还提供了创建新元素的通用方法:createElement()方法。它将 HTML 标记名作为参数,并返回指定类型的普通 DOM 节点。然后,您可以使用该节点,就好像它是您通过某种访问方法访问过的节点一样:您可以修改它的属性、更改它的内容等等。

生成的节点没有附加到文档,因此 DOM 还提供了以下一组方法,用于将这些片段插入到主 DOM 中,从而使它们的关联标记呈现在浏览器窗口中:

  • parentNode.appendChild(fragment):将 DOM 片段作为parentNode的子节点附加在其现有子节点(如果有)的末尾
  • parentNode.insertBefore(fragment, targetNode):将 DOM 片段作为parentNode的子元素和targetNode的兄弟元素插入到文档中
  • parentNode.replaceChild(fragment, targetNode):用fragment代替targetNode

注意,片段可以引用使用createElement()(或其他方法)创建的分离片段,也可以引用文档中的现有节点。如果是后者,这些方法会在将片段插入到新位置之前,将它从先前的位置移除。这使得在 DOM 中将节点从一个地方移动到另一个地方变得很容易。

最后,DOM 提供了一种使用cloneNode()方法复制现有节点的方法,参见清单 3-15 。cloneNode()方法可以接受一个可选的布尔参数,如果设置为 true,则指示克隆是“深层的”,并包括目标节点的所有子节点。

清单 3-15。 使用 DOM 方法创建新节点并将它们添加到文档中

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
      <p id="firstParagraph">Here is a paragraph.</p>
      <p id="secondParagraph">Here is another paragraph. It contains <span>some other tags,</span> <a href="http://www.google.com/">as well.</a></p>
      <script>
var firstPar = document.getElementById("firstParagraph"),
  secondPar = document.getElementById("secondParagraph"),
  targetLink = document.querySelector("#secondParagraph a"),
  myNewList = document.createElement("ul"),
  myNewListItemTemplate = document.createElement("li"),
  myNewListItem = myNewListItemTemplate.cloneNode();

myNewListItem.classList.add("menuitem");
myNewListItem.innerText = "One";
myNewList.appendChild(myNewListItem);
myNewListItem = myNewListItemTemplate.cloneNode();
myNewListItem.appendChild(targetLink);
myNewList.appendChild(myNewListItem);
firstPar.appendChild(myNewList);
      </script>
    </body>
</html>

在这个例子中,我们首先使用createElement()方法创建一个无序列表和一个列表项模板。然后我们克隆模板,使用innerText属性给克隆一个新的 CSS 类和一些文本,并将其添加到无序列表中。然后,我们再次克隆模板,并将第二段中的链接添加到模板中。这将从该位置移除该链接,并将其插入列表项中。最后,我们将列表项添加到列表中,并将列表添加到第一段。

删除元素

DOM 为我们提供了几种删除目标节点的方法:

  • parentNode.removeChild(targetNode):从parentNode中删除targetNode
  • parentNode.innerHTML:通过将一个节点的innerHTML设置为空字符串,我们可以一次移除它的所有子节点。

但是,在从 DOM 中删除元素时,有一些注意事项。如果元素附加了事件处理程序,特别是自由使用闭包来维护其状态的事件处理程序,简单地删除那些处理程序所绑定的元素并不一定会将它们从内存中清除。这是动态应用中内存泄漏的主要原因。在从 DOM 中移除元素(当然还有它们的子元素)之前,一定要明确地移除事件处理程序。

Internet Explorer 的旧版本(主要是 6 和 7)在从 DOM 中移除元素时不释放内存是非常糟糕的。事实上,即使元素已经从 DOM 中删除,这些旧版本的 Internet Explorer 仍会保留每个元素的一些内存,从而导致内存泄漏。高度动态的页面,其中大量的元素被添加到 DOM 中或者从 DOM 中删除,在 IE 中只会变得越来越大。有一个简单的技巧可以解决这个问题:IE 专有财产outerHTML

outerHTML属性引用元素的父元素的 HTML 当用作 setter 并给定一个空字符串时,它从 DOM 中有效地移除元素。它不会完全从内存中清除该元素,但确实有所帮助。

因此,对于一个普通的元素删除,你应该遵循类似的步骤(如清单 3-16 所示):

  1. 从目标元素及其子元素中删除任何事件处理程序。

  2. 使用removeChild()删除元素。

  3. Check if outerHTML is available and, if so, use it to clear the memory in IE.

    清单 3-16。 有效地从 DOM 和内存中删除一个元素

    var myTarget = document.getElementById("deleteme");
    deleteme.removeEventListener("click", clickHandler, false);
    
    myTarget.parentNode.removeChild(myTarget);
    
    if (typeof document.outerHTML !== "undefined") {
      myTarget.outerHTML = "";
    }
    

在清单 3-16 所示的人为例子中,我们首先获取对目标元素的引用,然后移除它的事件处理程序,然后从 DOM 中移除它。然后,我们检查我们是否在 Internet Explorer 中操作,如果是,则清除与该元素相关的内存。

如果您使用的是 JavaScript 库,它可能会为您管理这个过程,特别是当它公开自己的 API 来从 DOM 中删除元素时。

DOM 事件

除了提供对元素的访问,DOM 还指定了处理用户与元素交互的框架。当用户与页面上的元素交互时——鼠标悬停、单击、选择、拖动、输入等等——浏览器会将这些交互转换为元素中的事件。然后,您可以将事件处理程序附加到特定事件的元素;一个事件处理程序本质上是一个当事件发生时浏览器将执行的代码块。

DOM 为处理事件提供了一个简单但健壮的框架。。。而 IE9 之前的 Internet Explorer 几乎完全忽略了它。在 IE9 之前的版本中,Internet Explorer 使用不同的方法绑定事件处理程序,为事件提供不同的执行上下文,甚至没有事件模型的完整阶段。

从积极的方面来看,处理 Internet Explorer 的不同事件模型是一项常见的任务,因此解决方案非常丰富和健壮。我们将在本节的最后讨论其中的一些解决方案,但是现在我们只关注事件模型是如何工作的。

事件阶段

当用户与某个元素交互时,浏览器会检查该元素上是否注册了该事件类型的事件处理程序。如果是,浏览器将执行该处理程序。

因为 HTML 是结构化的,并且标签可以嵌套,所以 DOM 规定,从一个元素开始的事件将“冒泡”到它的父元素——毕竟,子元素中的事件可能也需要计入父元素中。在目标元素上发生事件后,浏览器会将事件“冒泡”到目标元素的父元素。然后,它将执行相同的检查,以确定是否为事件类型注册了处理程序,如果有,则执行该处理程序。然后它会向上冒泡到下一个父节点,依此类推。最终,事件将到达 DOM 树的主干 body 元素,它是文档中任何元素的最终祖先。此时,事件将沿着它刚刚到达原始目标的路径返回,再次在每个元素上检查并执行注册的事件处理程序,一直返回到原始目标元素。一旦往返完成,事件终止。

事件沿着 DOM 树向上移动的阶段称为冒泡阶段,事件沿着 DOM 树向下返回的阶段称为捕获阶段。您可以指定希望事件处理程序在哪个阶段执行,这为您处理嵌套元素上的事件提供了极大的灵活性。例如,如果希望在父元素上执行 click 事件,然后再在原始子目标上执行,可以在冒泡阶段注册父事件处理程序,在捕获阶段注册子事件处理程序。

事件有第三个阶段,称为目标处的,此时事件当前位于目标元素处。当绑定事件处理程序时,没有办法直接指向这个阶段,但是您可以通过Event对象中的一个属性来访问它(参见下面的“事件对象”)。

事件执行上下文

当浏览器执行一个事件处理程序时,它必须为该函数提供一个执行上下文。DOM 标准规定这个上下文应该是事件处理程序绑定到的元素。因此,在事件处理函数中,this关键字将是一个指针,指向事件正在其上执行的 DOM 元素。

此外,当执行事件处理程序时,浏览器将向其传递一个Event对象(作为参数),该对象包含几个有用的属性,提供关于事件的详细信息:事件起源的原始目标元素、鼠标在目标中的位置、鼠标在页面中的位置等等。此外,Event对象有一些用于修改事件传播行为的有用方法(我们将在下面的“事件对象”一节中介绍这些方法)。

不同的事件

DOM 标准提供了大量的事件。它们可以分为六个基本组:

  • 鼠标事件:click``mousedown``mouseup``mousemove等。这些都包含在 DOM MouseEvents 模块中。
  • 键盘事件:keypress``keydown``keyup。由 DOM KeyboardEvents 模块覆盖。
  • 对象事件 : loaderrorresizescroll等。由 DOM HTMLEvents 模块覆盖。
  • 表单事件 : selectchangesubmitresetfocus等。也包含在 DOM HTMLEvents 模块中。
  • 用户界面事件 : focusinfocusout。由 DOM UIEvents 模块覆盖。
  • 突变事件:DOM 内部发生变化时触发的事件,如DOMNodeInsertedDOMAttrModified等。由 DOM 变异事件模块覆盖。

此外,移动设备上的浏览器还可能暴露与触摸交互相关的其他事件(tapdoubletaptapholdswipe等)。)或移动设备特有的其他事件(方向改变、摇动、移动、位置等)。).

绑定事件处理程序

为了让浏览器对元素上的事件做出反应,您必须首先将该事件的处理程序绑定到元素。绑定一个事件处理程序本质上等同于说“当用户以这种方式与这个元素交互时,当事件经过这个阶段时执行这个代码。”DOM 为此提供的方法是addEventListener()方法,它有三个参数:一个event type参数(clickkeypress等)。)、一个listener参数(事件发生时要执行的代码)、一个可选的布尔型phase参数,该参数指示事件处理程序在捕获阶段(如果设置为true)或冒泡阶段(如果设置为false,这是默认值)执行。(在一些较旧的浏览器版本中,用于采集的布尔值并不总是可选的,所以总是包含它被认为是一种良好的做法。)事件处理程序可以将事件对象作为参数,然后您可以在处理程序中访问该事件对象。你可以在清单 3-17 中看到一个绑定点击事件处理程序的例子。

清单 3-17。 将点击事件处理程序绑定到一个元素

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
      <p id="firstParagraph">Click Me!</p>
      <script>
var firstPar = document.getElementById("firstParagraph");

function myEventHandler(event) {
  alert("You clicked me!");
}

firstPar.addEventListener("click", myEventHandler, false);
      </script>
    </body>
</html>

当您单击该段落时,警告框将会打开。

您可以向单个对象上的一个事件添加多个事件处理程序:

firstPar.addEventListener("click", myFirstEventHandler, false);
firstPar.addEventListener("click", mySecondEventHandler, false);
etc.

当事件被触发时,事件处理程序将按照绑定的顺序执行。

这是将事件处理程序绑定到元素的基本模式。如果你愿意,你可以使用一个内嵌匿名函数来代替一个命名函数,如清单 3-18 所示,这也是一个相当常见的模式。

清单 3-18。 使用匿名内联函数作为事件处理程序

<!DOCTYPE html>
<html>
  <head>
    <title>JavaScript Programmer's Reference</title>
  </head>
  <body>
    <p id="firstParagraph">Click Me!</p>
    <script>
var firstPar = document.getElementById("firstParagraph");

firstPar.addEventListener("click", function(event) {
  alert("You clicked me!");
}, false);

    </script>
  </body>
</html>

这个例子的行为与清单 3-17 中的例子完全相同。区别仅在于命名的函数。请注意,如果您将有一个复杂的事件处理程序,那么为它创建一个命名函数并将其作为参数传递可能是值得的。如果您的内联事件处理程序变得太长(尤其是当它长到足以跨越多个屏幕时),您的代码可能会令人费解。

解除事件处理程序的绑定

要解除事件处理程序的绑定,使用removeEventListener()方法,如清单 3-19 所示。就像addEventListener()removeEventListener()有三个参数:一个事件类型,要移除的处理函数(在命名函数的情况下),以及布尔参数phase

清单 3-19。 删除事件处理程序

<!DOCTYPE html>
<html>
  <head>
    <title>JavaScript Programmer's Reference</title>
  </head>
  <body>
    <p id="firstParagraph">Click Me!</p>
    <script>
var firstPar = document.getElementById("firstParagraph");

function eventHandler(event) {
  alert("I'm unbinding the event handler!");
  firstPar.removeEventListener("click", eventHandler, false);
}
firstPar.addEventListener("click", eventHandler, false);

    </script>
  </body>
</html>

在本例中,当您单击段落时,它将执行处理程序,然后处理程序将解除自身绑定。还要注意,这个例子使用了一个闭包:firstPar变量和eventHandler函数仍然可用,即使在事件处理程序已经被绑定并且脚本已经完成执行之后。这样,当目标对象上的事件调用事件处理程序时,它将能够成功执行。维护事件处理程序的状态是闭包在 JavaScript 开发中最常见的用法之一。(参见第一章了解更多关于闭包的细节。)

如果调用removeEventListener()时使用的参数组合与添加到对象中的任何事件处理程序都不匹配,那么这个方法就会终止。它不会抛出错误,也不会给出任何无效的指示。

无法解除使用匿名内联函数的事件处理程序的绑定。您需要能够引用removeEventListener()的函数名。

事件对象

Event对象被传递到事件处理程序中,所以如果你愿意,你可以在你的脚本中访问它。Event对象有几个有用的属性和方法,最值得注意的是:

  • event.clientXevent.clientY:事件相对于浏览器窗口的鼠标坐标(如果是鼠标事件)。
  • event.offsetXevent.offsetY:事件相对于目标元素的鼠标坐标(如果是鼠标事件)。
  • event.keyCode:被按下的键的 ASCII 码(如果是键盘事件)。
  • event.target:一个指向事件起源的 DOM 元素的指针。
  • event.currentTarget:一个指针,指向事件当前冒泡(或捕获)到的 DOM 元素。例如,如果您有一个无序列表,该列表由一个包含 LI 标签的 UL 标签组成,当您单击一个 LI 标签时,click 事件将冒泡到父 UL 标签,然后到父 UL 标签,依此类推。当事件冒泡时,currentTarget属性将改变值以反映事件在冒泡过程中的位置。
  • event.eventPhase:表示事件当前所处阶段的整数代码:1 表示捕获,2 表示到达目标,3 表示冒泡。
  • event.type:事件的类型(“点击”、“按键”等)。).
  • event.relatedTarget:用在一些特定的事件中(比如 mouseout),指向事件发起的元素(或者在 mouseout 的情况下,指向接收事件的元素)。
  • 当这个方法被调用时,它阻止事件在 DOM 中进一步传播。但是,如果为此事件向此元素注册了多个事件处理程序,则任何剩余的事件处理程序仍将执行。
  • event.stopImmediatePropagation():类似于stopPropagation(),但是当这个方法被调用时,它也将停止当前元素上任何剩余的事件处理程序的执行,并阻止任何进一步的传播。
  • event.preventDefault():如果有一个默认的动作与事件相关联,调用这个方法将阻止它执行。例如,如果你注册了一个点击事件处理程序到一个锚标记,在其中调用preventDefault()将会阻止浏览器跟随链接。在 Internet Explorer 中,此方法不存在。取而代之的是一个布尔属性returnValue,当设置为 false 时,将取消默认操作。

这些属性告诉我们很多关于事件的信息,并在编写事件处理程序时给予我们很大的灵活性。举例来说,清单 3-20 提供了一个简单的营救小猫的游戏。

清单 3-20。 营救小猫!

<!DOCTYPE html>
<html>
  <head>
    <title>JavaScript Programmer's Reference</title>
    <style>
.basket {
  width: 300px;
  height: 300px;
  position: absolute;
  top: 100px;
  right: 100px;
  border: 3px double #000000;
  border-radius: 10px;
}
    </style>
  </head>
  <body>
    <h3>Rescue the kittens!</h3>
    <p>Click on them to put them in their basket!</p>
    <ul id="kittens">
      <li>Rowly</li>
      <li>Fred</li>
      <li>Mittens</li>
      <li>Lenore</li>
    </ul>
    <ul class="basket"></ul>
    <script>
var basket = document.querySelector(".basket"),
  kittens = document.querySelectorAll("li"),
  kittensLength = kittens.length,
  i;

for(i = 0; i < kittensLength; i++) {
  kittens[i].addEventListener("click", function(event) {
    basket.appendChild(event.target);
  }, false);
}
    </script>
  </body>
</html>

在本例中,我们为每只小猫注册了一个 click 事件处理程序,这样当您单击一只小猫时,它就会被神奇地转移到安全的篮子中(或者,在我们的例子中,我们只需将它附加到目标 DOM 节点,该节点会自动将它从 DOM 中的原始位置移除)。

在这个游戏中,效率有点低:我们分别为每个项目分配一个事件处理程序。我们实际上不必这样做;如果我们愿意,我们可以使用一种叫做事件委托 的方法来利用事件在 DOM 中冒泡的事实。

事件委托

事件处理中常见的模式是事件委托。基本上,委托事件意味着允许事件由 DOM 树中比原始目标更高的元素来处理。这可以减少您必须使用的事件处理程序的数量,从而对效率产生显著的影响。

作为一个例子,让我们使用事件委托重做我们的游戏。让我们将事件处理程序委托给包含它的元素,而不是给每只小猫应用一个单独的事件处理程序,如清单 3-21 所示。

清单 3-21。 小猫营救,事件代表团版

<!DOCTYPE html>
<html>
  <head>
    <title>JavaScript Programmer's Reference</title>
    <style>
.basket {
  width: 300px;
  height: 300px;
  position: absolute;
  top: 100px;
  right: 100px;
  border: 3px double #000000;
  border-radius: 10px;
}
    </style>
  </head>
  <body>
    <h3>Rescue the kittens!</h3>
    <p>Click on them to put them in their basket!</p>
    <ul id="kittens">
      <li>Rowly</li>
      <li>Fred</li>
      <li>Mittens</li>
      <li>Lenore</li>
    </ul>
    <ul class="basket"></ul>
    <script>
var basket = document.querySelector(".basket"),
  kittens = document.getElementById("kittens");

kittens.addEventListener("click", function(event) {
  basket.appendChild(event.target);
}, false);

    </script>
  </body>
</html>

在这个例子中,我们让包含无序列表的元素处理点击事件。现在我们只有一个事件处理程序,代码也简单多了。

手动点火事件

DOM 还允许您手动触发事件。当您在代码中手动触发事件时,它的行为与用户调度的事件完全一样。

手动触发事件包括三个步骤:

  1. 创建适当类型的事件对象。
  2. 适当地配置对象。DOM 提供了几种正确初始化事件对象的方法,以便它们拥有所有必需的参数。DOM 还提供了一种更简单的方法来初始化事件对象,这种方法适用于只需要最少的事件信息的情况。
  3. 调度元素上的事件。

让我们详细看一下这些步骤。

即将推出新的手动赛事

HTML5 规范的最新版本提出了一种使用全局Event对象建模事件的新方法。在这个提议中,这个Event对象可以像ObjectArray一样作为一个构造器,你可以用这种方式创建和配置你的事件对象。虽然这还没有被批准,但是已经在一些浏览器中实现了。关于这个即将到来的功能的详细信息,请参见 W3C DOM4 标准中的“接口客户事件”部分,网址为https://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-customevent.

创建事件对象

要创建一个事件对象,可以使用document.createEvent()方法。这个方法接受一个参数,这个参数是一个字符串,表示您将要使用的 DOM 事件模块。以下是最常用的模块:

  • MouseEvents:处理鼠标交互的事件,如clickmousedownmousemove等。
  • UIEvents:用于焦点事件,当元素被聚焦并接收键盘输入时发生,如在表单域或内容编辑的情况下。
  • HTMLEvents:用于面向浏览器的事件,如文档加载和卸载,以及内容选择、调整大小和滚动。
  • MutationEvents:处理 DOM 变化的事件,如DOMNodeInsertedDOMAttrModified等。
  • KeyboardEvents:处理按键的事件:keyupkeydownkeypress
  • Event:这是一个通用的事件模块,可用于发送任何事件。

一旦有了适当类型的事件对象,就可以根据需要对其进行配置。

配置事件对象

DOM 提供了方便的方法来帮助您适当地配置新的事件对象。应该使用哪种方法取决于事件是哪个模块的成员。

MouseEvents 模块中的事件使用Event.initMouseEvent(type, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget),其中属性如下:

  • type:实际事件类型,如clickmousedown等。
  • canBubble:一个布尔值,表示事件是否应该在 DOM 中冒泡。
  • cancelable:一个布尔值,表示事件的默认动作是否可以使用event.preventDefault取消。
  • view:事件的元上下文,在 JavaScript 中总是全局上下文,所以总是在这里传递对window对象的引用。
  • detail:事件的具体细节。对于 MouseEvents,它是在同一位置单击鼠标的次数(因此,如果detail = 2,则是双击事件)。
  • screenXscreenY:相对于事件主体的 x 和 y 坐标。
  • clientXclientY:相对于事件目标元素的 x 和 y 坐标。
  • ctrlKeyaltKeyshiftKeymetaKey (Mac OS X):布尔值,表示事件发生时这些键是否被按下。
  • button:表示点击了哪个按钮的数字:0 表示左键,1 表示中键(通常是现代鼠标上的鼠标滚轮),2 表示右键。
  • relatedTarget:事件的相关目标(如果适用)。

UIEvents 模块的事件使用Event.initUIEvent(type, canBubble, cancelable, view, detail),其中:

  • type:实际事件类型。
  • canBubble:一个布尔值,表示事件是否应该在 DOM 中冒泡。
  • cancelable:一个布尔值,表示事件的默认动作是否可以使用event.preventDefault取消。
  • view:事件的元上下文,在 JavaScript 中总是全局上下文,所以总是在这里传递对window对象的引用。
  • detail:事件的具体细节。对于 UIEvents,通常是鼠标被点击的次数作为事件的一部分,所以通常设置为 1。

HTMLEvents 模块的事件使用Event.initEvent(type, canBubble, cancelable),其中:

  • type:实际事件类型。
  • canBubble:一个布尔值,表示事件是否应该在 DOM 中冒泡。
  • cancelable:一个布尔值,表示事件的默认动作是否可以使用event.preventDefault取消。

MutationEvents 模块的事件使用Event.initMutationEvent(type, canBubble, cancelable, relatedTarget, previousValue, newValue, attributeName, attributeChange),其中:

  • type:实际事件类型。
  • canBubble:一个布尔值,表示事件是否应该在 DOM 中冒泡。
  • cancelable:一个布尔值,表示事件的默认动作是否可以使用event.preventDefault取消。
  • relatedTarget:事件的相关目标(如果适用)。
  • previousValue:修改节点的前一个值。
  • newValue:修改节点的新值。
  • attributeName:被修改属性的名称。
  • attributeChange:表示属性如何改变的整数:1 =修改,2 =添加,3 =删除。

当初始化 KeyboardEvents 事件时,使用Event.initKeyboardEvent(type, canBubble, cancelable, view, ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode),其中:

  • type:实际事件类型。
  • canBubble:一个布尔值,表示事件是否应该在 DOM 中冒泡。
  • cancelable:一个布尔值,表示事件的默认动作是否可以使用event.preventDefault取消。
  • view:事件的元上下文,在 JavaScript 中总是全局上下文,所以总是在这里传递对window对象的引用。
  • ctrlKeyaltKeyshiftKeymetaKey (Mac OS X):布尔值,表示按下这些键时是否发生了虚拟按键。
  • keyCode:密钥的 ASCII 码。
  • charCode:密钥的 Unicode 字符。

注意,这里的非标准浏览器是 Firefox,它调用方法initKeyEvent()。最初,KeybordEvent模块是在 DOM Level 2 规范的早期版本中定义的,但是它从该规范中被删除了。

扩展事件对象

如果您愿意,可以随意扩展这些事件对象。它们是对象,就像 JavaScript 中的任何其他对象一样,所以您可以向它们添加自己的属性和方法。我们最喜欢的技术之一是为事件提供一个appDetail属性,它包含了关于事件的有用信息以及事件被触发的原因。这也便于确定哪些事件是手动触发的,哪些事件是由用户直接触发的。它也可以很好地处理自定义事件(在本章的后面会有描述)。

现在您已经有了一个配置好的事件,您只需要分派它。

调度事件

分派您的事件非常简单。如清单 3-22 所示,你在目标元素上调用dispatchEvent()方法。

清单 3-22。 击发自定义事件

<!DOCTYPE html>
<html>
  <head>
    <title>JavaScript Programmer's Reference</title>
  </head>
  <body>
    <p id="clickme">Click me to see an alert!</p>
    <script>
var myPar = document.getElementById("clickme");
myPar.addEventListener("click", function(event) {
  alert('This is your alert!');
}, false);

// Create and dispatch a new click event
var myClickEvent = document.createEvent("MouseEvents");
myClickEvent.initMouseEvent("click", true, true, window, 0, 0, 0, 0, false, false, false, false, 1, null);
myPar.dispatchEvent(myClickEvent);
    </script>
  </body>
</html>

在这个例子中,我们手动触发一个点击事件,这样当你加载这个页面时,你会立即看到一个警告,就像你点击了这个段落一样。您可以单击该段落再次查看该警告。

在清单 3-22 中,我们实际上并不需要所有这些参数——我们将坐标设置为 0(尽管这一点都不准确),我们并不关心 Cntrl、Shift 或 meta 键等等。如果不需要所有这些属性,可以使用由通用事件模块生成的更简单的事件对象。例如,清单 3-23 ,它自动化了我们的小猫营救游戏——因为没有什么比制作一个自我终结的游戏更有趣了。

清单 3-23。 自动化小猫救助

<!DOCTYPE html>
<html>
  <head>
    <title>JavaScript Programmer's Reference</title>
    <style>
.basket {
  width: 300px;
  height: 300px;
  position: absolute;
  top: 100px;
  right: 100px;
  border: 3px double #000000;
  border-radius: 10px;
}
    </style>
  </head>
  <body>
    <h3>Rescue the kittens!</h3>
    <p>Click on them to put them in their basket!</p>
    <ul id="kittens">
      <li>Rowly</li>
      <li>Fred</li>
      <li>Mittens</li>
      <li>Lenore</li>
    </ul>
    <ul class="basket"></ul>
    <script>
var basket = document.querySelector(".basket"),
  kittens = document.getElementById("kittens");

kittens.addEventListener("click", function(event) {
  basket.appendChild(event.target);
}, false);

// Make JavaScript rescue the kittens!
var allKittens = document.querySelectorAll("#kittens li"),
  allKittensLength = allKittens.length,
  i,
  clickKittenEvent = document.createEvent("Event");

clickKittenEvent.initEvent("click", true, true);

for (i = 0; i < allKittensLength; i++) {
  allKittens[i].dispatchEvent(clickKittenEvent);
}
    </script>
  </body>
</html>

在清单 3-23 中,我们遍历每只小猫,并对它们触发一个最小配置的点击事件,这是我们使用通用事件模块创建的。这提供了一种快速触发事件的方法,如果你需要的话。

但是,请注意,有些事件需要更复杂的事件对象。键盘事件似乎对此特别敏感。您可能需要进行实验,找出您可以使用的内容。

自定义事件

您可以使用 DOM 事件模型来调度您想要的任何类型的事件!是的,你没看错:你不局限于点击和按键。如果你想定义你自己的事件,你可以这样做。当您使用addEventListener()方法附加事件处理程序时,只需指定您的事件类型,然后使用通用事件模块创建并触发您自己的事件。

想想我们的小猫拯救游戏。想象一下,我们不是监听点击事件,而是监听“营救”事件,如清单 3-24 中的所示。然后,我们可以手动生成救援事件,并救援所有的小猫。

清单 3-24。 定制事件救援

<!DOCTYPE html>
<html>
  <head>
    <title>JavaScript Programmer's Reference</title>
    <style>
.basket {
  width: 300px;
  height: 300px;
  position: absolute;
  top: 100px;
  right: 100px;
  border: 3px double #000000;
  border-radius: 10px;
}
    </style>
  </head>
  <body>
    <h3>Rescue the kittens!</h3>
    <p>Click on them to put them in their basket!</p>
    <ul id="kittens">
      <li>Rowly</li>
      <li>Fred</li>
      <li>Mittens</li>
      <li>Lenore</li>
    </ul>
    <ul class="basket"></ul>
    <script>
var basket = document.querySelector(".basket"),
  kittens = document.getElementById("kittens");

kittens.addEventListener("rescue", function(event) {
  basket.appendChild(event.target);
}, false);

// Make JavaScript rescue the kittens!
var allKittens = document.querySelectorAll("#kittens li"),
  allKittensLength = allKittens.length,
  i,
  clickKittenEvent = document.createEvent("Event");

clickKittenEvent.initEvent("rescue", true, true);

for (i = 0; i < allKittensLength; i++) {
  allKittens[i].dispatchEvent(clickKittenEvent);
}
    </script>
  </body>
</html>

在本例中,我们只需将 click 事件处理程序更改为 rescue 事件处理程序,然后我们只需创建一个 rescue 事件并从每只小猫中调度它,就像我们对 click 事件所做的那样。

创建自定义事件是一项强大的技术,它允许您在代码中创建解耦的组件。每个组件只需要在事情发生时发布事件,然后其他组件可以监听这些事件,也可以不监听。这样,所有的组件都是完全解耦的:组件 A、B 和 C 不需要知道彼此的任何事情,甚至不需要知道它们是否存在,但是它们仍然可以使用事件相互通信。

跨浏览器策略

正如我们在本节开始时提到的,直到版本 9,Internet Explorer 几乎忽略了 DOM 事件的标准。最值得注意的是,Internet Explorer 使用了方法attachEvent()removeEvent(),而不是addEventListener()removeEventListener()。此外,Internet Explorer 没有为正在执行的事件处理程序设置适当的上下文;IE 没有将其设置为注册处理程序的元素,而是将其设置为window对象。IE 也不支持事件的捕获阶段。最后,IE 不会将一个Event对象传递给它的事件处理程序;相反,它把它附加到window对象上。

幸运的是,使用一点 JavaScript 就可以很容易地解决这些问题。缺少捕获阶段是很难克服的,但是捕获阶段没有被广泛使用,所以如果我们关注注册方法和上下文问题,我们可以提出一个相当简单的解决方案。

我们要做的是创建两个新的函数来注册我们的事件:addEventHandler()removeEventHandler(),如清单 3-25 所示。如果我们在一个支持 DOM 标准的浏览器中工作,我们只需将我们的函数别名化为 DOM 函数,然后就这样了。但是,如果我们在 IE 中,我们需要做更多的工作来解决我们的上下文问题。

清单 3-25。 创建跨浏览器事件绑定方法

if (document.addEventListener) {
  // DOM events available, so just use them.
  window.addEventHandler = function(targetEl, eventType, handler) {
    targetEl.addEventListener(eventType, handler, false);
    return handler;
  };
  window.removeEventHandler = function(targetEl, eventType, handler) {
    targetEl.removeEventListener(eventType, handler, false);
  }
} else {
  // Internet Explorer. Fix context problems as well as create alias.
  window.addEventHandler = function(targetEl, eventType, handler) {
    var fixContext = function() {
      return handler.apply(targetEl, arguments);
    };
    targetEl.attachEvent("on" + eventType, fixContext);
    return fixContext;
  }

  window.removeEventHandler = function(targetEl, eventType, handler) {
    targetEl.detachEvent("on" + eventType, handler);
  }
}

在这个例子中,我们向全局上下文添加了两个新方法:addEventHandler()removeEventHandler()。在 IE 中,对于addEventHandler(),我们通过创建一个名为fixContext()的虚拟函数并将其绑定为事件处理程序来解决上下文问题。当事件调用fixContext()时,它使用apply()方法手动调用处理程序,这使我们能够强制目标元素成为执行上下文。

我们还提到,Internet Explorer 不会将一个Event对象作为参数传递给它的事件处理程序。也有一个简单的方法,如清单 3-26 所示:在事件处理程序中,只需检查是否有事件传入,如果没有,就从window对象中取出它(这是 IE 放置它的地方)。

清单 3-26。 修复其他 IE 问题

function clickHandler(event) {
  if(!event) {
    event = window.event;
  }
  // continue...
}

解决缺少捕获阶段的问题更加困难,但是这些修复解决了最糟糕的问题。如果您发现您需要一个更健壮的解决方案,许多 JavaScript 库可以很好地处理这个问题。特别是 jQuery 修复了所有问题,并为事件模型提供了许多有用的扩展。

摘要

在这一章中,我们介绍了浏览器中的一个重要特性,DOM,它为 JavaScript 提供了一个访问和操作已经加载到浏览器中的文档的接口。以下是本章的重点:

  • DOM 是一个独立的标准;它不受 JavaScript 标准的控制。
  • 随着时间的推移,DOM 不断发展,浏览器合规性是一个持续的过程。
  • DOM 的结构就像一棵树,HTML 文档中的所有内容都用树来表示。
  • 可以使用父/子/兄弟关系和方便的方法来遍历 DOM。
  • DOM 中的节点可以使用像getElementById()querySelector()这样的方法直接访问。
  • DOM 提供了几个操作其成员的重要方法,包括改变其属性和内容的方法。
  • DOM 允许您根据需要创建节点,并像处理文档一样处理它们。
  • DOM 拥有丰富灵活的事件模型。。。哪个 Internet Explorer 不遵循。
  • 事件处理程序可以添加到任何元素中,也可以很容易地删除。
  • 事件处理可以委托给 DOM 中更高层的元素,因为事件通过 DOM 结构向上冒泡,最终到达 body 标记。
  • 可以手动触发事件。
  • 您可以创建自定义事件。

这一章标志着讨论章节的结束。在第四章中,我们将在一些实际项目中应用前三章中所涉及的所有内容。

四、JavaScript 实战

现在我们已经介绍了 JavaScript 和 DOM 的基础知识,让我们使用新工具。在这一章中,我们挑选了七个项目,它们将帮助您构建自己的项目,并举例说明我们在其他章节中介绍过的许多技术和 JavaScript 特性:

  • 使用 JavaScript
  • 高效加载脚本
  • 使用 XMLHttpRequest 的异步通信
  • 跨领域技术
  • 数据缓存
  • 选择 JavaScript 库
  • 使用 jQuery
  • 构建自己的 JavaScript 库

使用 JavaScript

虽然从技术上来说不是一个“项目”,但我们想讨论使用 JavaScript 的一些重要方面。我们从 JavaScript 新手那里得到的最常见的问题可能与使用 JavaScript 有关:哪些编辑器是好的?你如何调试?最好的工作环境是什么?使用这种语言有什么诀窍吗?我们想借此机会回答这些问题。

多年来,我们已经在几乎所有可以想象的环境中编写了 JavaScript。JavaScript 的一大优点是,你不需要很多工具来使用它。一个简单的文本编辑器和一个浏览器就足以让你开始,对于基本的项目,这就是你所需要的。但是,一旦您开始从事稍微复杂的项目,您会很快发现自己想要更高级的工具。

在这一节中,我们将介绍 JavaScript 交易的基本工具。首先,我们想了解 JavaScript 开发工具的三位一体:集成开发环境、浏览器和个人 web 服务器。

我们先来谈谈一些比较流行的支持 JavaScript 的集成开发环境(ide)。拥有一个具有语法突出显示、代码完成、重构支持和协作功能等特性的坚实的 IDE 可以帮助驯服一个复杂的项目。可供选择的有很多,很难知道选哪个。

我们还想介绍 web 浏览器提供的开发人员支持。现代 web 浏览器提供了各种非常有用的工具来监控和调试它们运行的 JavaScript。

最后,我们将介绍最常用的个人 web 服务器。您可以使用浏览器的打开文件功能来测试您的脚本,这对于基础工作来说是没问题的。然而,异步通信是构建 JavaScript 应用的基石之一,它需要一个 web 服务器。(我们将在下面的与 XMLHttpRequest 的异步通信一节中介绍异步通信。)

一旦我们介绍了交易工具,我们将讨论如何使用它们。我们将提供一些使用 JavaScript 时常见工作流程的见解,然后谈一谈调试脚本的方法。

JavaScript ides〔??〕

因为 JavaScript 本质上是文本,所以编写它真正需要的是一个文本编辑器。任何文本编辑器都可以满足这个目的,甚至像 Windows 上的记事本应用这样简单的东西。还有几个代码编辑器可以很好地处理 JavaScript,有些甚至提供了语法高亮等基本功能。我们已经使用 vi 创建项目,并且知道几个同事是 emacs 的死忠用户。我们也非常喜欢 TextMate 和 Sublime Edit,这两个伟大的编辑程序支持多种语言。

当您开始处理包含许多 JavaScript 文件的复杂项目时,您会很快发现您需要比简单的代码编辑器所能提供的更多的特性。这就是集成开发环境(ide)发挥作用的地方。(如果您已经熟悉其他语言的代码编辑环境,那么您的 JavaScript 项目也需要相同的特性。)

集成开发环境将代码编辑器提升到了一个新的层次。典型的 IDE 将提供管理多个文件和文件类型的功能,在项目或应用中将它们组合在一起(确切的术语各不相同),并且通常提供与其他开发人员协作的功能,例如与源代码控制系统的集成。

有几种支持 JavaScript 开发的 ide。通常,它们都提供一组基本的编辑功能(例如,文件创建、删除、重命名、移动;在单个文件或多个文件中查找/替换;自动缩进)。更好的 ide 将提供更高级的功能,如 代码辅助(一个充当动态助手的功能,根据您键入的内容提供建议或自动完成)。

此外,很多时候你不会只使用 JavaScript。如果您正在处理一个典型的 web 项目,您还将处理 HTML 和 CSS,因此您将希望 IDE 也支持这些。然而,我们发现,如果 JavaScript 支持特别好,我们可以接受 IDE 中 HTML 和 CSS 功能较少的情况。

如果您完全是使用 ide 的新手,我们建议您浏览一下这个列表,并尝试几个选项。许多开发人员热衷于他们的 ide,并声称他们的选择是唯一符合逻辑的,但实际上这个选择是个人偏好。尝试几个 ide 将帮助你找出你的偏好——哪些特性是你真正喜欢的,哪些是你可以不要的——然后你就可以做出选择了。

aptana 工作室

http://www.aptana.com

Aptana Studio 是我们首选的 IDE。它构建于 Eclipse 之上(见下文),既可以作为独立安装,也可以作为 Eclipse 插件。Aptana 具有基本 Eclipse IDE 的所有特性(构建集成、跨平台、可脚本化等)。此外,Aptana 还有几个对 web 开发非常有用的特性,特别是 JavaScript 开发:

  • JavaScript、HTML 和 CSS 的代码辅助。
  • 代码辅助 jQuery(jQuery 有自己的语法,请参见下面的使用 jQuery)。
  • 开箱即用的 Git 集成(Git 是一个非常流行的源代码控制系统;详见http://git-scm.com/)。
  • 内置的 CLI,用于与 node(或者 ruby,如果您正在使用 Rails)之类的脚本解释器一起工作。
  • JSLint 集成(JSLint 是一个 JavaScript 语法和样式检查器;详见http://www.jslint.com/lint.html)。
  • 开源,有活跃的社区。通过内部更新系统,Bug 报告会得到快速回复,补丁也会定期推送。
  • 因为它是建立在 Eclipse 上的,所以许多现有的 Eclipse 插件将与 Aptana 一起工作(例如 SVN 插件)。
  • Aptana 是免费的。

Aptana 还拥有 Eclipse 的完全可配置的 UI,并且还提供了一个很好的预定义主题库,可以根据您的喜好进行尝试和更改。(我们非常喜欢“自由浓缩咖啡”这个主题。)

Aptana 由 Appcelerator 支持,Appcelerator 是一家专注于为构建移动应用创建工具的公司。详见http://www.appcelerator.com

月食

http://www.eclipse.org

Eclipse 是一个开源的 IDE。Eclipse 是为 Java 开发而构建的(它本身也是使用 Java 构建的),但现在包括了对许多其他语言的支持:JavaScript、C/C++、Ruby、Python、PHP 等。Eclipse 是一个成熟的 IDE,因此它可以处理整个项目,甚至可以与 Ant 等流行的构建系统集成。此外:

  • Eclipse 是跨平台的。因为 Eclipse 是用 Java 构建的,所以它可以在许多操作系统上运行。这意味着您可以一次性学习 Eclipse,而不必担心从 Windows 转换到 Mac,也不必学习全新的 IDE。
  • Eclipse 支持插件。任何人都可以创建插件来扩展 Eclipse 的功能,从浏览器调试到单元测试,再到 SVN 和 Git 集成,都有许多插件。
  • Eclipse 是开源的,有一个活跃的社区。
  • Eclipse 是免费的。

Eclipse 为使用 JavaScript(或 HTML 或 CSS)提供了很少的支持。你可以通过插件增加不同程度的支持。支持 JavaScript 的最著名的 Eclipse 插件是 Aptana(见上文),但是如果太多的话,还有两个其他的选择。

Amateras http://amateras.sourceforge.jp/cgi-bin/fswiki_en/wiki.cgi?page=EclipseHTMLEditor给我们带来了好运,它提供了基本的功能,包括代码高亮、内容辅助、概述和验证。如果你正在寻找轻量级插件中的基本编码支持,Amateras 是一个不错的选择。

Eclipse Web 工具平台是一个旨在为 Web 开发的所有方面提供工具的项目。可以在http://www.eclipse.org/webtools/了解更多。他们在http://www.eclipse.org/webtools/jsdt/有一个 JavaScript 开发者工具(JSDT)子项目,运行得非常好,提供了基本的代码编辑功能以及集成调试、浏览器依赖的代码辅助、代码概述和透视图等。当我们使用简单的 Eclipse 安装并且不能使用 Aptana 时,Eclipse Web Tools 平台是我们支持 JavaScript 的首选插件。

微软 Visual Web Developer 和 Visual Studio Express

http://www.microsoft.com/visualstudio/eng/products/visual-studio-express-products

微软的 Visual Studio 是一个优秀的 IDE,但是对于一个完整的许可来说相当昂贵。为了使他们的工具(以及他们的平台)更容易访问,微软已经创建了一系列 Visual Studio 的“express”版本。Visual Studio Express for Web IDE 是创建 HTML/CSS/JavaScript 应用的绝佳环境。我们使用的是 2010 版本(名为 Visual Web Developer ),它提供了集成基于 Web 的 UI 工作所需的所有功能。NET 后端工作。特点:

  • 集成了 Visual Studio 完整版本的工作流,允许您将昂贵的许可证保持在最低水平,而不会限制协作。
  • JavaScript 和 HTML 的代码突出显示和语法检查。
  • SVN 和 Git 集成。
  • 单元测试集成。
  • 活跃的用户社区,他们乐于回答问题并提供建议。
  • 免费。

如果您将为 Windows 构建项目,或者将与这样做的人合作,Visual Studio 系列将是一个很好的选择。

网络形态

http://www.jetbrains.com/webstorm

我们知道许多 JavaScript 开发者对 WebStorm 深信不疑,甚至不会考虑使用另一个 IDE。其特点包括:

  • 代码突出显示和完成。
  • 单元测试集成。
  • JSlint 集成。
  • 内部调试器。
  • 30 天免费试用,个人许可证 49 美元。

我们自己没有使用过 WebStorm,但是我们已经在同事们的肩膀上看到了足够多的 web storm,足以给我们留下深刻的印象。

浏览器

可能与您选择的 IDE 一样重要的是您选择的用于进行开发的浏览器。当然,您将在所有的目标浏览器中进行测试,但是您将有一个主浏览器,它将是您正在进行的工作的首选,它将始终在后台运行,等待您切换回它并点击刷新以查看您的最新更改。您选择哪个浏览器作为忠实的伙伴将在很大程度上取决于它对 JavaScript 开发的支持程度。

不久前,浏览器对开发者的支持还很少。你可以查看网页的来源,但仅此而已。JavaScript(或 HTML 或 CSS)中的错误会被忽略,除非会在应用中引发不可预知的行为。调试一个复杂的脚本既要遵循任何一种固定的模式,也要有防御性的编码和对浏览器神秘特质的了解。

今天,所有现代浏览器(Safari、Chrome、Firefox 和 Internet Explorer)都有支持开发的工具。一些浏览器提供了非常高级的特性,但是所有的浏览器都至少提供了一个 JavaScript 控制台和查看生成的源代码的能力。

  • JavaScript 控制台:它们都提供了一个控制台,浏览器可以在这里输出错误信息。浏览器还提供对脚本控制台的访问(请参见下面的使用控制台)。
  • 查看生成的源代码:所有现代浏览器都为您提供了一种查看页面中所有 JavaScript 运行后生成的文档源代码的方式。如果 JavaScript 修改了 DOM,这些修改将会反映在标记中。这个特性对于确定您的 DOM 操作是否如您所期望的那样运行非常有帮助。每个浏览器对此功能的实现是不同的;请查阅您的浏览器文档以了解如何使用它。

大多数浏览器都有其他非常有用的功能,包括在发生时监控 HTTP 流量的能力(当您发出异步请求,并且想知道您查询的服务器是否返回了您期望的响应时,这很有帮助),逐行遍历代码的能力,在应用运行时直接操作 HTML、CSS 甚至 JavaScript 的能力,等等。

除了本地开发支持之外,一些浏览器(最著名的是 Firefox)有一个丰富的第三方插件库,可以提供更多的功能。事实上,第三方插件是第一批可用的开发工具。它们的流行让浏览器制造商相信,提供本地工具是吸引开发者使用他们浏览器的好方法。

开发人员支持功能不断发展,因为现代浏览器也在发展,并推动定期和频繁的更新。例如,Firefox 最近在他们的开发工具中添加了一个 3d 视图,它提供了 DOM 的三维视图,你可以从不同的角度查看。

铬合金

谷歌浏览器是我们网络开发的首选浏览器。开发人员工具是健壮的,具有将断点应用于 JavaScript 代码、运行堆栈跟踪、分析效率等功能。我们每天都在使用 Chrome,还没有发现它的开发工具有所欠缺。

Firefox

Firefox 可能是最具扩展性的网络浏览器。最新版本包括一套令人印象深刻的开发工具,其中包括非常有用的 Scratchpad,它使您能够输入 JavaScript 代码并在当前选项卡的上下文中运行它。

然而,Firefox 并不总是内置如此强大的开发工具,因此多年来,web 开发人员不得不依赖插件来提供这种功能。可能最流行的是 Firebug ( http://www.getfirebug.com),它提供了在 Firefox 中进行 web 开发所需的所有功能:DOM 检查器、脚本和网络监视器、JavaScript 控制台等等。

另一个受欢迎的 Firefox 扩展是 Web 开发者工具栏(https://addons.mozilla.org/en-US/firefox/addon/web-developer/?redirectlocale=en-US&redirectslug=Web_Developer_Extension_%28external%29)。这个扩展为 Firefox 添加了一个工具栏,具有许多有用的功能:能够选择性地启用或禁用脚本、图像显示或 Firefox 的本机弹出窗口阻止程序等功能;DOM、cookies、表单的检查员;验证工具等。

网络浏览器

Internet Explorer 的开发工具是一个相对较新的补充,但是从版本 10 开始已经相当广泛了。要访问 Internet Explorer 的开发工具,请在查看页面时按 F12。这将打开一个包含可用工具的窗口。

Internet Explorer 的工具包括所有的基本工具:控制台、向 JavaScript 添加断点的能力、效率分析、网络监控等。此外,它还提供了一些有用的特性,比如标准验证,将当前页面提交给各种验证器进行检查。IE 的开发者工具还包括一个可访问性验证器,它在www.contentquality.com将当前页面提交给验证器。

IE 的开发工具也可以帮助你管理一些 IE 浏览器更奇怪的方面。例如,使用条件注释,您可以编写针对特定版本的 Internet Explorer 的代码,这些工具提供了一种查看您当前使用的模式的方法。您还可以更改模式,以便在不同的查看模式下测试您的作品。(解释 IE 中的文档模式有点超出本节范围;要得到一个真正好的解释,请看http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/。)

野生动物园

Safari 还有一套丰富的 web 开发工具。要使用它们,请进入首选项images高级并选中“在菜单栏中显示开发菜单”选项。这将在主菜单栏中添加一个开发项目,这是您访问 Safari 开发工具的方式。

Safari 的开发者工具与 Chrome 非常相似,这是有意义的,因为它们共享一个共同的代码库。然而,两种浏览器的开发者工具确实不同。例如,Safari 有一个非常有用的代码片段编辑器:从“开发”菜单中选择“显示代码片段编辑器”来打开编辑器。代码片段编辑器实质上为您提供了一个精简的浏览器。在顶部窗格中键入 HTML、CSS 和 JavaScript,它会立即呈现在底部窗格中。Snippets 编辑器非常适合测试样式和原型交互,它为您提供了一个快速尝试代码的地方,而不必经历创建完整 HTML 文件的额外步骤。

网络服务器

现在您已经有了一个用于创建 JavaScript 的编辑器和一个运行它的浏览器,您需要一种方法将您的脚本放入浏览器。本书中的许多示例可以作为简单文件保存在硬盘上,然后直接在浏览器中打开,但是对于真正的 web 开发,您将希望运行自己的本地服务器来测试您的工作。每个操作系统都有自己的 web 服务器选项,甚至还有一个跨平台选项。

柔软

MacOS 内置了一个 Apache web 服务器,在 Mountain Lion 之前,你可以通过控制面板中的共享面板来激活它。苹果移除了控制面板界面,但保留了服务器的完整。你可以从命令行管理服务器,或者我们能够找到至少一个人张贴了一个自定义控制面板窗格,据称恢复了该功能。(我们没有尝试过,但是如果你想试试的话,去你最喜欢的搜索引擎,搜索“替换系统偏好设置面板网络共享”,你应该马上就能找到。)

Windows

Windows 有自己的 web 服务器,称为 Internet 信息服务,简称 IIS。默认情况下,Windows 7 和 Windows 8 的大多数版本都没有安装或启用 IIS,但您可以轻松地将其添加到您的安装中。我们不能在这里涵盖细节(这本身就是一个完整的章节),但搜索“windows 7 IIS 安装”或“windows 8 IIS 安装”应该可以让你开始。

Xampp

http://www.apachefriends.org/en/xampp.html

Xampp 是一个易于安装的跨平台版本的 Apache web 服务器(用 PHP 配置),以及 MySQL 数据库和其他几个有用的工具。它适用于 Windows、MacOS、Linux 和 Solaris。Xampp 在 Windows 和 MacOS 上的表现都非常好,我们推荐它。

IDE 调试服务器

许多 ide 都有内置的调试服务器。这些将允许您从 IDE 中提供单个页面,或者可能是整个项目。除了简单地服务于文件之外,IDE 通常还提供用于调试代码的集成功能,如断点、堆栈跟踪和单步执行代码,所有这些都在同一个环境中。我们发现用于集成调试的 Visual Studio 工具特别好,但 Aptana 的工具也相当有用。有关如何配置和使用 IDE 集成服务器的详细信息,请参考工具的文档。

JavaScript 开发工作流程

现在,您已经选择了编辑器、浏览器和提供文件的方式,您已经准备好了。最简单地说,一个典型的 JavaScript 开发工作流看起来像任何其他语言的工作流(编写、测试、重复):

  1. 写点代码。
  2. 将其载入浏览器并观察结果。
  3. 重复,修复出现的问题或添加更多功能。

当您编写更复杂的 JavaScript 时,您将需要在脚本执行时检查脚本的方法。一些 ide 和浏览器开发工具提供了一些有帮助的特性,比如检查器或断点,但是有一些基本的技术可以让你得到很多你需要的东西。

使用浏览器控制台

在本书中,我们一直使用警报来监控脚本的进度。警告是好的,但是它的缺点是在等待某人点击 OK 按钮时暂停脚本的执行。

幸运的是,在浏览器的 JavaScript 控制台中有一个强大的警报替代方案。所有现代浏览器都有一个你可以访问的 JavaScript 控制台,通常作为浏览器内置开发工具的一部分,见图 4-1 的例子。大多数浏览器都有一个键盘快捷键来调出控制台:对于 Chrome 是 Shift-Control-I,对于 Firefox 是 Shift-Control-J,IE 也有一个控制台;点击 F12 调出开发者工具,点击控制台标签。

9781430246299_Fig04-01.jpg

图 4-1 。Chrome 浏览器控制台的截图

现代浏览器以 window.console对象的形式将控制台的接口暴露给 JavaScript。每个浏览器的控制台不一样,所以每个浏览器的控制台对象也不一样。但是它们都提供了将您自己的文本输出到控制台的方法:

  • console.log(strText):将指定的文本以简单日志的形式输出到控制台。
  • console.warn(strText):将指定的文本输出到控制台作为警告。
  • console.error(strText):将指定的文本作为错误输出到控制台。

Firefox 和 Chrome 都提供了过滤控制台输出的方法,这样你就可以只查看你想要的输出类型。

向控制台输出文本的优点是不会像警报那样中断脚本的执行。现在您已经了解了控制台,您可以执行本书中的任何示例,将 alert 调用替换为console.log,并在控制台上看到相同的输出,而不是警报。

将日志记录到控制台提供了一种很好的方式来监视脚本的状态。最常见的用途之一是在脚本执行的不同点输出变量值,以便您可以看到变量是如何变化的。在 Chrome 和 IE 中,你可以向控制台输出任何东西,而不仅仅是字符串。如果你输出一个对象,你会在控制台上看到它被枚举(见图 4-2 ):

9781430246299_Fig04-02.jpg

图 4-2 。在 Chrome 浏览器控制台显示一个对象

在 Chrome 中,你甚至可以深入到一个对象的任意深度,包括它的原型继承。这是学习更多关于 JavaScript 和 DOM 的好方法;尝试将window对象输出到控制台,并在其中进行探索。

此外,还可以直接与控制台进行交互(参见图 4-3 )。Firefox 控制台的顶部是一个输入框,有一个标签为“评估”的按钮在 IE 的控制台中,是窗口底部标有>?? 符号的输入框。在 Chrome 中,你只需点击控制台窗口。

9781430246299_Fig04-03.jpg

图 4-3 。在 Chrome 浏览器控制台输入 JavaScript 命令

我们正在获取对当前文档主体的引用,然后通过将其 display 属性设置为 none 来隐藏它。

您可以在这里输入任何有效的 JavaScript,它将在已经加载到浏览器窗口的页面的上下文中执行。作为一个实验,转到任何页面,在控制台中键入alert(document.title)。(通常你只需按下回车键就可以让控制台评估你的代码,但有时你必须在 Firefox 中点击评估按钮。)浏览器窗口应该提醒已加载页面的标题。如果在控制台中输入window.document并按 enter(或 evaluate)键,document对象应该会出现在控制台中。在 Chrome 中,当你深入这个对象时,你会看到 HTML 标记。。。当你将鼠标放在元素上时,Chrome 会高亮显示窗口中相应的元素。IE 只会枚举一个对象的前几个属性,Firefox 只会输出任何对象的 toString()方法的结果。

断点

调试脚本最有用的工具之一是在代码中设置断点的能力:指定浏览器停止执行脚本的点,并允许您检查脚本的当前状态,甚至更改内容。如果您从未在调试过程中使用过断点,我们强烈建议您尝试一下。它对于检查脚本以了解它们是如何工作的也很有用。

Chrome 的开发工具支持断点和逐行遍历 JavaScript 代码。优秀教程见https://developers.google.com/chrome-developer-tools/docs/scripts-breakpoints

Firebug 还支持将断点作为脚本调试的一部分。详见https://getfirebug.com/wiki/index.php/Script_Debugging

高效加载脚本

当您开始编写复杂的 JavaScript 应用时,您会很快发现最大的性能问题之一是应用第一次加载和初始化时速度很慢。有时这些问题是由于技术问题,如脚本阻塞或低效的下载顺序,有时这些问题纯粹是感性的:您可以从技术方面优化您的应用加载过程,但这有副作用(如未修改内容的短暂闪现),导致您的应用看起来缓慢、低效或未经修饰。

在这一节中,我们将讨论一些技术,您可以使用这些技术来优化实际速度和感知速度。第一步是讨论浏览器如何下载和解析它们的资源,然后我们将提供四个常用技巧来提高应用的加载效率。

浏览器如何下载和处理内容

任何标准都没有规定下载和处理内容的细节,因此浏览器制造商可以自由地实施他们喜欢的任何方法。因此,不同的浏览器(甚至不同版本的浏览器)在实现方式上会有所不同。不过,总的来说,还是有一些共性的:

  • 浏览器按顺序解析 HTML 文档。当浏览器解析文档时,它所解析的元素将在 DOM 中可用,并且它将开始下载指定的资源(图像、外部脚本、样式表等)。
  • 浏览器可以并行下载多个资源。旧的浏览器只能并行下载两个资源,但是新的浏览器可以处理多达六个。
  • 加载外部 JavaScript 文件将阻止其他资源下载。这是因为脚本可能会修改 DOM,甚至将浏览器重定向到不同的页面,因此为了避免不必要的下载,浏览器不会开始任何其他并行下载,直到脚本被加载、解析和执行。

在本书中,我们的例子通过总是将 JavaScript 放在 HTML 标记的末尾,就在</body>标签之前,来说明文档解析的顺序。这保证了浏览器在脚本试图访问元素之前已经解析了 HTML。如果您颠倒了顺序,您将最终得到一个试图访问尚不存在的元素的脚本,这通常会导致错误。为了演示,考虑第三章的中的清单 3-3 :

清单 3-3。 使用 getElementById()

<!DOCTYPE html>
<html>
        <head>
               <title>JavaScript Developer’s Guide</title>
        </head>
        <body>
            <p id="myParagraph">This is my paragraph! <span class="hideme">Lorem ipsum</span> dolor            sit amet.<span></span></p>
            <p class="hideme">Another paragraph!</p>
            <script>
var myPar = document.getElementById("myParagraph"); // myPar is now a pointer to the paragraph.
myPar.innerText = "I have changed the content!";    // Change the text of the paragraph to "I have changed the content!"
            </script>
        </body>
</html>

这个简单的例子演示了如何使用getElementByID()方法来改变一个段落的文本。如果我们把<script>标签放在<p>标签之前,这个例子就不起作用了,如清单 4-1 所示:

清单 4-1。 试图在 DOM 元素可用之前访问它

<!DOCTYPE html>
<html>
        <head>
               <title>JavaScript Developer's Guide</title>
        </head>
        <body>
            <script>
var myPar = document.getElementById("myParagraph"); // myPar is now a pointer to the paragraph.
myPar.innerText = "I have changed the content!"; // Change the text of the paragraph to "I have changed the content!"
            </script>
            <p id="myParagraph">This is my paragraph! <span class="hideme">Lorem ipsum</span> dolor             sit amet.<span></span></p>
            <p class="hideme">Another paragraph!</p>
        </body>
</html>

脚本将无法访问该段落,因此getElementById()将返回 null。当我们试图改变nullinnerText属性时,JavaScript 引擎会抛出一个错误,因为null没有属性。

此外,在我们所有的例子中,我们的脚本是内联的,这意味着它们实际上在文档本身中。当您编写更长更复杂的脚本时,您会发现将它们保存在单独的文件中比将它们内联更容易。单独的文件使维护和与其他程序员的协作更容易,并允许您利用内容分发网络等部署选项。

不幸的是,当您将脚本作为单独的文件加载时,这意味着它们会阻止文档中跟在它们后面的资源的并行下载。这让我们想到了优化应用的第一个技巧:在文档末尾加载脚本。

优化技巧 1:在文档末尾加载脚本

优化应用加载过程的一个关键技术(如果这么简单的想法也可以称为技术的话)是,在所有其他内容都已加载之后,在文档末尾加载脚本。那么他们就不能阻止任何其他的下载,脚本可能需要访问的任何元素都会出现在 DOM 中。

这种技术有时会产生不希望的副作用:假设您有一个简单的 HTML 文档,但您用复杂的 JavaScript 对其进行了大量修改。您已经设置了您的文档结构,这样您的所有脚本都在文档的末尾加载。这会导致一个简单的 HTML 文档加载并出现在浏览器中,然后浏览器开始加载、解析并执行您的脚本。当浏览器这样做的时候,你未修改的内容被显示给用户,甚至更糟,因为脚本加载是一个阻塞过程,如果用户试图与页面交互,什么也不会发生。然后,浏览器将完成加载、解析和执行您的脚本,突然之间,浏览器将显示您想要的应用。这种“未修改(或未样式化)内容的闪现”虽然短暂,但会使您的应用显得粗糙和低效,即使从技术上讲,您的应用已经针对加载效率进行了优化。

如果你发现自己在处理未经修改的内容,请记住这是一个感知问题。用户看到了一些他们没有预料到的东西,并且对他们没有帮助,这给人一种你的应用有问题的感觉。在感知效率低下的情况下,你通常可以通过向用户提供一些有用的东西,比如加载屏幕,将它转化为你的优势。加载屏幕告诉用户应用仍在运行;只是还没准备好。如果您的脚本加载和执行效率很高,这通常足以为您的应用赢得运行所需的时间。

创建负载指示器很容易;只需在文档的最开始包含它的标记,这样它就是第一个被解析和显示的东西。它可以是屏幕中间的一个简单的方框,显示“正在加载…”的信息,也可以是一个小图片。通常,加载指示器绝对位于屏幕中间,并阻止对其下方元素的访问。要消除它,您只需在最后一个脚本的末尾进行一个简单的调用,以访问 DOM 中的加载程序,或者删除它,或者,如果您以后想再次使用它,只需隐藏它。

这里有一个用定时器模拟加载延迟的例子。在清单 4-2 中,我们有一个简单的加载指示器,它覆盖了屏幕上的所有内容,然后当应用“就绪”时(在本例中,当定时器到时),脚本隐藏它:

清单 4-2。 简单装载指示器

<!DOCTYPE html>
<html>
        <head>
               <title>JavaScript Developer's Guide</title>
               <style>
body, h1 {
    margin: 0;
    padding: 0;
}
#container-loading {
    position: absolute;
    /*
     * Radial gradient CSS generated by CSS Background Maker
     * http://ie.microsoft.com/testdrive/graphics/cssgradientbackgroundmaker/default.html
     */
    /* IE10 Consumer Preview */
    background-image: -ms-radial-gradient(center, circle farthest-corner, #9E9E9E 0%, #141414 100%);
    /* Mozilla Firefox */
    background-image: -moz-radial-gradient(center, circle farthest-corner, #9E9E9E 0%, #141414 100%);
    /* Opera */
    background-image: -o-radial-gradient(center, circle farthest-corner, #9E9E9E 0%, #141414 100%);
    /* Webkit (Safari/Chrome 10) */
    background-image: -webkit-gradient(radial, center center, 0, center center, 506,     color-stop(0, #9E9E9E), color-stop(1, #141414));
    /* Webkit (Chrome 11+) */
    background-image: -webkit-radial-gradient(center, circle farthest-corner, #9E9E9E 0%, #141414 100%);
    /* W3C Markup, IE10 Release Preview */
    background-image: radial-gradient(circle farthest-corner at center, #9E9E9E 0%, #141414 100%);
}

#container-loading div {
    width: 200px;
    height: 100px;
    border: 2px solid #ccc;
    background-color: #fff;
    text-align: center;
    line-height: 100px;
    font-family: arial, helvetica, sans-serif;
    font-weight: bold;
    font-size: 1.5em;
    border-radius: 20px;
    position: absolute;
}
               </style>
        <script>
function showLoading(boolShow) {
    var loadingIndicator = document.getElementById("container-loading"),
        loadingMessage = loadingIndicator.querySelector("div");
    if (boolShow === true) {
        // Show the loading indicator.
        // Position everything
        loadingIndicator.style.width = window.innerWidth + "px";
        loadingIndicator.style.height = window.innerHeight + "px";
        loadingMessage.style.left = ((window.innerWidth - 200) / 2) + "px";
        loadingMessage.style.top = ((window.innerHeight - 100) / 2) + "px";
        loadingIndicator.style.display = "block";
    } else {
        loadingIndicator.style.display = "none";
    }
}
        </script>
        </head>
        <body>

            <!-- Begin: Loading Indicator -->
            <div id="container-loading">
                <div>
                    Loading...
                </div>
            </div>
        <script>
// Initialize and show the loading indicator.
showLoading(true);
        </script>
            <!-- End: Loading Indicator -->

            <!-- Begin: Rest of document -->
            <h1>Hello World!</h1>
            <!-- Pretend complex markup goes here -->
            <!-- End: Rest of document -->

        <!-- Begin: loading scripts -->
        <!-- Script(s) loaded here -->
        <!-- End: loading scripts -->
        <script>
// Simulate a loading delay with a timer
setTimeout(function() {
    showLoading(false);
}, 2000);
        </script>
        </body>
</html>

请注意,在这个加载指示器中,我们包含了两位内联 JavaScript 来处理定位和布局,以及隐藏和显示指示器。内联 JavaScript 的第一部分在文档的头部,它设置了一个showLoading()函数,我们可以用它来显示或隐藏加载对话框。我们可以很容易地在主体中包含函数定义,在加载对话框标记之后,但是函数不需要跟随标记来定义,因为它在被调用之前不会访问元素。如果我们在头部定义了函数,但直到它访问的元素被解析后才调用它,它将按预期工作。如果我们愿意,我们甚至可以将这个简单的脚本提取到一个单独的文件中,并使用非阻塞技术加载它;参见提示 3。

内联 JavaScript 的另一部分只是调用showLoading()函数来初始化加载器并正确显示它。因为这个脚本只做一件事,为了提高效率,让它内联在文档中是有意义的,而不是把它提取到一个单独的文件中(把它提取到一个单独的文件中也会导致它阻止加载后面的复杂文档)。

加载屏幕是阻止脚本问题的一个简单解决方案,但是它们有几个警告。首先,它们并不真的适合所有情况。例如,如果你正在建立一个信息网站,你肯定不想让人们等待你的网站加载。另一方面,如果您正在构建一个复杂的数据驱动的 web 应用,加载指示器会非常有用。

其次,你应该尽量减少加载延迟,这样你就永远不需要加载指示器,或者它只在屏幕上出现尽可能短的时间。我们的下一个优化技巧将帮助您实现这一目标。

优化技巧#2: 合并、缩小和 GZip

提高 JavaScript 应用效率的一种常用技术是将脚本合并到尽可能少的文件中。这减少了浏览器必须发出的 HTTP 请求的数量,并且对应用的加载速度有很大的影响。即使您只有十几个 JavaScript 文件,浏览它们并决定哪些可以合并也是一个好主意。

如果您不愿意将脚本合并到一个文件中,因为这会使它们难以维护,这是一个合理的担忧。这就是为什么许多项目都有一个发布步骤,即获取应用的开发版本,并将 JavaScript 文件组合成一个文件。这样,开发人员可以保留更易于管理的独立文件,但用户不必承受额外的加载时间。请注意,当您这样做时,这意味着您和您的程序员同事工作的环境与您的用户将会经历的环境是不同的。用户将体验到一个更加优化的环境,这可能会导致一些您不会看到的问题。您应该在“编译”的产品版本中定期测试您的应用,以避免在部署时被错误吓到。

另一种常见的部署优化技术是缩小组合的 JavaScript 文件。缩小是一种方法,其中自动解析程序加载一个 JavaScript 文件,然后尝试通过删除注释、删除不必要的空格以及用较小的名称替换长的变量、属性和方法名称来减小文件的整体大小。结果是 JavaScript 对人来说很难阅读,但哪些浏览器理解起来没有问题,哪些文件大小明显更小,从而减少了下载时间。

最后,JavaScript 文件通常在部署之前使用 GZip 压缩算法进行压缩。浏览器能够接受 GZipped JavaScript 文件,并在处理它们之前在内部解压缩。尽管这为浏览器执行带来了额外的步骤,但它所花费的时间通常比将未压缩文件传输到浏览器所花费的时间要少得多,从而提高了整体性能。GZip 只适用于 100 字节左右以上的文件;小于这个值的文件实际上会变大,因为 gzip 压缩确实会增加文件的开销。但是,如果文件足够大,与节省的空间相比,开销并不明显。

您可以缩小或 GZip 您的 JavaScript 文件,但是最节省大小(以及网络传输时间)的方法是两者都做。有几个工具可用于缩小 JavaScript:

  • 谷歌的闭包编译器:谷歌的闭包编译器不只是缩小你的 JavaScript,它还会优化它。它删除未使用的代码路径,重写低效代码,然后缩小结果。输出通常比简单的缩小要小得多,而且运行速度也快得多。参见https://developers.google.com/closure/compiler/了解在项目中使用闭包编译器的细节。
  • JSMin :最古老的迷你相机之一,但仍然非常好,是道格拉斯·克洛克福特的 JSMin,在http://www.crockford.com/javascript/jsmin.html有售。它只是缩小了代码,并没有像闭包编译器那样重写低效的代码,所以提供了一种稍微不那么粗暴的方法。它是用 C 编写的,但是很容易构建,并且可以很好地集成到部署脚本中。
  • YUI 压缩器:由雅虎构建,是他们 YUI 图书馆的一部分,YUI 压缩器是缩小你的 JavaScript 的一个很好的选择。作为一个额外的奖励,它也可以缩小 CSS 文件。YUI 压缩器也可以包含在节点脚本中,这使得使用 JavaScript 编写自己的部署脚本变得非常容易!关于 YUI 压缩机的详细信息,请参见http://yui.github.com/yuicompressor/

gzip 文件可以在部署时手动压缩,也可以指示大多数现代 web 服务器在传输文件之前自动压缩某些类型的文件。Apache 模块mod_deflate(见http://httpd.apache.org/docs/2.2/mod/mod_deflate.html)使得指定要压缩的文件类型,或者甚至要为哪些浏览器压缩变得容易。如果您正在使用 Xampp,可以通过在配置文件中取消注释适当的指令,将其配置为使用mod_deflate,详细信息请参见http://stackoverflow.com/questions/6993320/how-to-enable-gzip-compression-in-xampp-server。对于其他服务器,查看文档,看看有什么可能。

优化技巧 3:使用非阻塞技术在文档头中加载脚本

在技巧 2 中,我们在文档的头部有一小段内联脚本,它定义了隐藏和显示加载指示器的函数。包含在文档的头部是安全的,因为它只是一个函数定义,并不将 DOM 作为其定义的一部分来访问。当您编写脚本时,您会发现它们中的很大一部分属于 JavaScript 类,可以加载到文档的头部,因为它在被调用之前不会访问 DOM。JavaScript 库通常属于这一类。

如果我们可以在头部加载这些脚本,我们就不必在文档的末尾加载它们,这将减少用户查看加载对话框的时间,甚至可能完全消除对加载对话框的需求。但是如果我们把它们作为单独的文件加载到头部,它们会阻止后面的任何东西。

幸运的是,有一种异步加载脚本的方法。脚本阻止加载其他素材的原因是因为它们是常规文档流的一部分。如果我们将脚本注入到文档中,它们将不会成为常规文档流的一部分。浏览器仍然会加载、解析和执行它们,但是它们不会阻止其他素材的加载。

脚本标签可以像任何其他 DOM 元素一样注入到文档中。您只需创建一个新的<script>标签,设置它的src属性,然后将它附加到 DOM 中。当您将它添加到 DOM 中时,浏览器将开始下载脚本。

举个例子,假设我们有几个想要定义的函数——可能是我们自己的 JavaScript 库。我们希望创建几个预定义的数据对象,这些对象在一个单独的文件中定义,我们可能希望加载一个分析包以及一个由内容分发网络提供的第三方库。这些脚本都不需要 DOM 就可以执行(虽然它们中的任何一个都可能定义了在被调用时访问 DOM 的方法——但是只要我们在 DOM 准备好之前不调用这些方法,我们就没事了),所以我们可以在头部加载它们。清单 4-3 是一个名为 manifest-loader.js 的示例脚本,它可以完成所有这些工作:

清单 4-3。 manifest-loader.js

// Define a manifest array of scripts to load
var defaultManifest = [
  "scripts/js-lib.js",
  "scripts/js-objects.js",
  "scripts/third-party/omniture.js",
  "http://big.cdn.com/useful-library.js"
]

// Define a function to load a manifest array.
function loadManifest(arrManifest) {
    var i,
        arrManifestLength = arrManifest.length;

    for (i = 0; i < arrManifestLength; i++) {
        var newScript = document.createElement("script");
        newScript.src = arrManifest[i];
        document.getElementsByTagName("head")[0].appendChild(newScript);
    }
}

在这个例子中,我们首先创建一个包含所有想要异步加载的 URL 的清单数组。然后我们定义一个函数,当给定一个清单数组时,该函数将遍历数组,创建一个新的脚本标记,设置src属性,然后将脚本标记附加到文档的头部,从而导致脚本异步加载。

现在我们可以将这个脚本加载到我们假设的文档的头部。我们还可以包含一个更小的调用loadManifest(defaultManifest)的内联脚本,我们所有的脚本将开始加载而不会阻塞文档的其余部分,如清单 4-4 所示:

清单 4-4。 使用载货单装载器

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Developer's Guide</title>
           <script src="scripts/js-loader.js"></script>
        <script>
loadManifest(defaultManifest);
        </script>
        </head>
        <body>

            <h1>Hello World!</h1>

        </body>
</html>

清单中的这些脚本都不需要准备好 DOM,这一点非常重要,因为当它们被加载时几乎肯定是不可用的。请注意,确定文档何时准备好是可能的,但是不同浏览器之间的细节差异很大,甚至给定浏览器的不同版本之间的细节差异也很大。然而,这是许多 JavaScript 库提供的一个常见功能(如 jQuery,见下文)。

如果您不想自己构建这种基础设施,您可以在现有的 JavaScript 库中寻找您需要的特性。例如,RequireJS ( http://requirejs.org/)将这种技术向前推进了一步,允许您在 JavaScript 文件中指定依赖关系,这些文件可以根据需要动态加载。

优化技巧 4: 适度是好的

不要陷入思维的陷阱,认为你绝对必须使用任何(或所有)这些技术。当您优化应用加载过程时,您应该始终考虑什么对您的特定情况最有意义。在上面的加载指示器示例中,我们将一些脚本内嵌在文档中,而不是从外部文件中加载。我们这样做是因为将它们放在它们应该在的地方保证了它们在我们需要的时候可用(就在浏览器解析文档的必要部分之后),并且因为它们足够小以至于没有必要对它们进行优化。

在你的项目中,你应该进行实验,看看什么样的技术组合能提供最好的结果。您可能会发现,您将使用这些技术的组合来平衡所有目标浏览器的开发需求和部署需求。

正如技巧 3 中提到的,如果你不想自己构建这种优化基础设施,你可以在 JavaScript 库或框架中寻找你需要的特性。

使用 XMLHttpRequest 的异步通信

除了基于用户请求(或 JavaScript 命令)加载和显示页面之外,浏览器还提供了另一种访问服务器的方法:XMLHttpRequest 对象。这个对象为 JavaScript 提供了一个接口,用于向 web 服务器发出请求,并根据需要处理响应。该界面与简单加载页面的主要区别在于,这些请求可以异步发出——它们在后台运行,不会阻止用户与主页面的交互。此外,这些请求的结果可以由 JavaScript 处理,并根据需要合并到当前文档中。因此,使用这种技术可以创建不需要页面刷新的用户交互,为 web 应用提供更像应用的外观和感觉。

从名称 XMLHttpRequest 可以看出,请求可能涉及 XML。事实上,当该技术首次实现时,XML 是编码信息的首选方式。但是,这种技术可以包含任何可以通过 HTTP: HTML、JSON 甚至纯文本传输的编码数据。这项技术还有另一个名字:异步 JavaScript 和 XML,简称 AJAX。

XMLHttpRequest 技术最初是由微软实现的,它非常有用,以至于其他浏览器很快也实现了它,现在它已经成为 DOM 标准的一部分

它是如何工作的

XMLHttpRequest 对象为您执行所有的网络访问,通过所有必要的步骤来联系服务器并与之正确通信。当它完成工作时,它会在不同的点分派事件——例如,当它完成与服务器的通信时,或者如果发生了错误。您为这些事件提供事件处理程序,以正确处理结果。

基本步骤如下:

  • 创建所需的事件处理函数。
  • 创建 XMLHttpRequest 对象的新实例,并对其进行配置以供使用(包括提供事件处理程序)。
  • 指示 XMLHttpRequest 对象发出请求。在它工作的时候,它会在适当的时候分派事件。

让我们一次一个地检查这些步骤。

步骤 1: 事件处理程序

当 XMLHttpRequest 对象完成它的工作时,它将分派您可以为其提供处理程序的事件。与 DOM 交互事件(如 click 或 keypress)一样,事件处理程序将在事件发生时被调用,并将被传递一个包含事件信息的事件对象。事件类型包括:

  • readystatechange:这个事件在 XMLHttpRequest 对象工作时被多次触发。触发时,该事件将在事件对象上提供一个readyState属性,该属性将具有下列值之一:

  • 0: Unsent,表示 XMLHttpRequest 对象已创建,但请求尚未发送。

  • 1:已打开,意味着open()方法已被调用并成功完成(因此请求正在进行中)。

  • 2: Headers received,表示已从服务器接收到所有头(这包括重定向头,如果有的话)。此时,事件对象的 status 属性现在是可用的,并将拥有来自服务器的 HTTP 响应代码。

  • 3:正在加载,意味着正在加载响应。

  • 4: Done,表示响应已成功加载。来自服务器的响应现在位于事件对象的responseText属性中。

  • abort:当请求被中止时触发(如调用XMLHttpRequest.abort()方法时)。

  • error:发生错误情况时触发。

  • load:当请求成功完成时触发。

  • loadend:当请求完成时触发,不管成功与否。

  • loadstart:发送请求时触发。

  • progress:加载过程中,随着加载的进行而触发。

  • timeout:如果在请求完成之前经过了指定的超时时间,则触发。

除了readystatechange事件之外,所有这些事件对于最新版本的 XMLHttpRequest 规范来说都是新的,因此它们可能无法在所有目标浏览器中完全实现。(比如 Webkit,只刚刚实现了timeout事件;参见https://bugs.webkit.org/show_bug.cgi?id=74802。)事件readystatechange是由原始规范指定的遗留事件,并被广泛实现,它提供了我们处理大多数成功和错误情况所需的所有信息。

一个好的readystatechange事件处理程序使用readyState属性来查看请求进展到什么程度,并据此采取相应的行动。它还将使用status属性来检查 HTTP 状态消息(比如文件未找到的状态 404)。我们可以在清单 4-5 中看到这一点:

清单 4-5。 简单的 readystatechange 事件处理程序

function handleReadyStateChange(objXHR) {
  if (objXHR.readyState === 4) {
    // Request is done.  Was the requested file found?
    If ((objXHR.status !== 200) && (objEvent.status !== 304)) {
      // Something happened..possibly the requested file wasn't found?
      // Tell the user that something went wrong.
      console.error("An error occurred while processing the request.");
    } else {
      // The requested file was found and sent and the content is now available in
      // the objXHR.responseText property:
      console.log(objXHR.responseText);
    }
  }
}

我们假设随着请求的进行,这个事件处理程序将被多次调用,但是我们唯一想做的事情是当readyState属性为 4 时,这意味着请求已经完成。然后我们检查服务器返回的 HTTP 状态,它位于status属性中。我们将假设除了 200(成功)或 304(资源未修改)之外的任何状态都是错误情况;但是,如果您愿意,您可以根据不同的状态代码进行更详细的错误处理。(参见http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html了解状态码的完整列表、含义以及何时提供。)假设请求成功,我们简单地将结果输出到控制台。(显然,除了将结果发送到控制台,您还可以做更多的事情;这是一个简单的例子来说明这个模式。)

步骤 2: 创建和配置 XMLHttpRequest 对象

浏览器的XMLHttpRequest对象是一个抽象对象,这意味着它不能被直接访问,而是被用作创建新实例的模式。您在脚本中使用这些新实例来执行异步请求。你甚至可以有不止一个。

要创建一个新的XMLHttpRequest对象,只需像使用任何 JavaScript 构造函数一样使用new关键字:

var myXHR = new XMLHttpRequest();

此语法在所有支持此功能的浏览器中都有效,只有 Internet Explorer 6 例外。对于 IE6,这个特性仍然可用,但是需要调用一个新的 ActiveX 对象。

一旦有了新对象,就需要对其进行配置。至少,XMLHttpRequest对象需要知道要请求的 URL,使用什么 HTTP 方法来发出请求(通常是GETPOST,但是如果您只想检查 URL 的头而不实际获取它,您也可以发出HEAD请求),任何需要的支持数据(例如,POST的表单数据),以及至少一个在成功时要执行的事件处理程序。

首先使用open()方法打开对象:

myXHR.open(strMethod, strURL, boolAsynchronous);
  • strMethod:请求使用的 HTTP 方法
  • strURL:想要使用的 URL
  • boolAsynchronous:一个布尔值,表示是否异步执行请求。该参数是可选的;如果省略,则默认为true。(如果设置为false,这将导致请求被同步执行,并且它将在请求期间暂停浏览器,就像常规的页面加载一样。)

然后,一旦打开了对象,就可以注册事件处理程序。最常见的选择是在readystatechange事件上注册一个事件处理程序:

myXHR.onreadystatechange = function() {
    handleReadyStateChange(myXHR);
};

这将我们之前定义的handleReadyStateChange()函数注册为readystatechange事件的事件处理程序。(如果您只使用实现了最新版本标准的浏览器,您也可以使用addEventListener()方法,就像这是任何其他 DOM 事件一样。)

步骤 3: 发送请求

发送请求很容易。您所要做的就是在配置的对象上调用send()方法:

myXHR.send(postData);
  • postData:来自作为请求的一部分被发布的表单的数据。在GET请求的情况下,这将总是空的。

此时,对象将执行请求并触发事件处理程序,如上所述。

image 注意你可以向任何服务器发送一个 XHR 请求,甚至是一个与你的页面源在不同域的服务器。但是,对请求结果的访问受到同源策略的限制。除非响应的来源与您的脚本的来源匹配,否则您的脚本将无法访问响应。本章的“跨域方法”一节讨论了一些安全避开这些限制的技术。

把这一切放在一起

配置和发送 XMLHttpRequest 非常简单,但是如果我们创建一个函数来完成所有的工作,我们可以在脚本中使它变得更简单。

一个健壮的XMLHttpRequest对象需要很多东西:URL、方法、任何 post 数据,以及成功、错误和超时的回调。我们可以在一个单独的函数中提供每一个单独的参数,但是把它们都聚集到一个单独的对象中,然后把那个对象作为一个单独的参数提供给函数,会更有条理,如清单 4-6 所示:

清单 4-6。XMLHttpRequest 函数的配置对象

var myXhrDefs = {
    strMethod : "GET",
    strUrl : "http://myhost.com/ajax-test.txt",
    intTimeout: 3000,
    postData: null,
    boolAsync: true,
    successCallback: function(objXHR) {
        // Do things when the request is successful
        console.log(objXHR.responseText);
    },
    errorCallback: function(objXHR) {
        // Do things when there is an error in the request.
        console.error("The XHR failed with error ", objXHR.status);
    },
    timeoutCallback: function() {
        // Do things when a timeout occurs.
        console.error("The XHR timed out.");
    }
}

这里我们把所有的东西都聚集到一个对象中,甚至是对象上的方法回调。在清单 4-7 的中,我们可以构建一个函数,使用这个对象作为参数来处理我们的XMLHttpRequests:

清单 4-7。一个 XMLHttpRequest 函数

function doXHR(myXhrDefs) {

    // Create and configure a new XMLHttpRequest object
    var myXhr = new XMLHttpRequest(),
        myTimer = null;
    myXhr.open(myXhrDefs.strMethod, myXhrDefs.strUrl, myXhrDefs.boolAsync);

    // Register the error and success handlers
    myXhr.onreadystatechange = function() {

        // If readyState is 4, request is complete.
        if (myXhr.readyState === 4) {

            // Cancel the timeout timer if we set one.
            if (myTimer !== null) {
                clearTimeout(myTimer);
            }

            // If there's an error, call the error callback,
            // Otherwise call the success callback.
            if ((myXhr.status !== 200) && (myXhr.status !== 304)) {
                if (myXhrDefs.errorCallback != null) {
                    myXhrDefs.errorCallback(myXhr);
                }
            } else {
                myXhrDefs.successCallback(myXhr);
            }
        }
    }

    // Handle timeouts (set myXhrDefs.intTimeout to null to skip)
    // If we're working with a newer implementation, we can just set the
    // timeout property and register the timeout callback.
    // If not, we have to set a start running that will execute the
    // timeout callback. We can cancel the timer if/when the server responds.
    if (myXhrDefs.intTimeout !== null) {
        if (typeof myXhr.ontimeout !== "undefined") {
            myXhr.timeout = myXhrDefs.intTimeout;
            myXhr.ontimeout = myXhrDefs.timeoutCallback;
        } else {
            myTimer = setTimeout(myXhrDefs.timeoutCallback, myXhrDefs.intTimeout);
        }
    }

    // Send the request
    myXhr.send(myXhrDefs.postData);
}

在这个例子中,我们创建了一个可以处理所有 XMLHttpRequest 需求的函数。它创建并配置一个新的XMLHttpRequest对象,然后根据需要注册所提供的事件处理程序。对于尚未实现 XMLHttpRequest 的timeout特性的浏览器,它使用setTimeout()实现了一个计时器。然后它为我们发送请求。要使用该函数,我们只需设置一个对象,然后调用该函数:

doXHR(myXhrDefs);

将我们所有的 XMLHttpRequest 代码放入一个函数中使得维护和升级更加容易;我们只在一个地方调用它,而不是在代码中可能的多个地方手动管理XMLHttpRequest对象。

跨领域技术

所有浏览器都实现了一种称为同源策略的安全措施。该策略允许页面上的脚本访问彼此的属性和方法,只要它们都来自相同的来源:相同的协议(http 或 https)、相同的主机名和相同的端口号(如果指定的话)。不同来源的脚本不允许相互交互。如果没有这个策略,恶意脚本可能会访问我们的页面和数据。

不幸的是,这种策略也阻碍了应用轻松处理来自多个来源的数据。但是,有一些方法可以绕过这个限制,因为这是 JavaScript 开发中的一个常见障碍,所以我们想在这里简单介绍一下。

请注意,所有这些技术都包含潜在的安全风险,所包含的风险将根据您如何使用它们而有所不同。在使用这些技术之前,您应该仔细评估它们的安全性问题,以避免恶意脚本访问您的页面或数据。

服务器端代理

最容易实现的解决方案之一是创建一个在您的域上运行的简单代理服务,并使用它来完成所有跨域查询。从浏览器的角度来看,所有请求都来自同一个来源,所以一切正常。

您可以用任何语言实现服务器端代理,包括 JavaScript(多亏了 node.js)。事实上,已经有几个使用 node 构建的代理服务器。我们已经使用 PHP 和 Java 构建了简单的代理。一个很好的例子,见 Ben Alman 的简单 PHP 代理,可在http://benalman.com/projects/php-simple-proxy/获得

JSONP

JSONP 代表“带填充的 JSON”,这是一种通过使用脚本注入绕过相同起源策略的技术的一个相当容易混淆的名称。

同源策略的一个例外是,通过带有src属性的<script>标签加载的脚本是豁免的,并且可以访问来自另一个源的脚本以及被来自另一个源的脚本访问。有关这方面的示例,请参见下面使用 jQuery 一节中的示例,我们从一个内容分发网络加载 jQuery,这个网络肯定与示例页面的来源不同。如果不是这个例外,我们不可能做到这一点。

通常,当您查询服务时,会得到一个 JSON 字符串作为响应。然后,您可以使用JSON.parse()将字符串反序列化为一个对象,然后访问对象的属性来做您需要的事情。如果服务与查询页面的来源不同,则同源策略会阻止这种情况。

但是,如果服务不返回简单的 JSON 字符串,而是返回一个可以注入查询页面的脚本,那该怎么办呢?如果我们这样做了,注入的脚本将不受同源策略的约束,我们将能够根据需要访问它的属性和方法。这是 JSONP 的关键:服务不直接返回 JSON 进行解析,而是返回 JSON 填充在注入主机文档的脚本中。

JSONP 中通常使用两种不同的填充。一种是返回的脚本简单地进行变量赋值,就像这样:

myResponse = { "foo" : bar, "serial" : 238 };

如果通过脚本标签注入到宿主文档中,变量myResponse将在全局范围内可用,使您能够根据需要访问属性。

另一种方法是让返回的脚本执行函数调用,如下所示:

responseHandler({ "foo" : bar, "serial" : 238 });

这将执行responseHandler()函数,将所需数据作为对象文字。(这假设您已经定义了一个函数responseHandler(),这样注入的脚本当然可以调用它。)

您需要的填充类型通常由目标服务指定。反过来,在注入脚本标记时,您将在对目标服务的查询中指定变量或函数的名称,如下所示:

<script src="http://www.service.com/getserial?jsonp=myResponse"></script>
<script src="http://www.service.com/getserial?jsonp=responseHandler">>

确切的语法将由服务提供;可能不是“jsonp=varname”也不是“jsonp=functionname”。(比如 Twitter 规定格式应该是“callback=functionname”——见https://dev.twitter.com/docs/things-every-developer-should-know#jsonp。)

一步一步来看,事情是这样的:

  • 如果服务指定用函数调用填充 JSON,那么创建一个可以用来处理数据的函数。如果服务指定它将为 JSON 填充一个变量赋值,如果您愿意,您可以命名该变量。
  • 创建一个新的<script>标签。更新它的src属性,将目标 URL 格式化为服务指定的格式。
  • <script>标签附加到文档中。这将导致浏览器获取其 URL,从而导致跨域调用。

注意,这意味着您将在每次 JSONP 调用时创建一个新的<script>标记。如果你只是打几个电话(可能十几个或更少),那可能没问题。但是如果你非常依赖 JSONP,你会想要重用你的<script>标签,这样你就不会以一个臃肿的 DOM 和随之而来的内存问题而告终。最简单的方法是编写一个简单的函数来处理所有的 JSONP 调用,如清单 4-8 所示:

清单 4-8。 执行 JSONP 的函数调用并回收脚本标签

function executeJSONPQuery(strUrl) {
    // Check to see if a jsonp script tag has already been injected.
    // Also, create a new script tag with our new URL.
    var oldScript = document.getElementById("jsonp"),
        newScript = document.createElement("script");
    newScript.src = strUrl;
    newScript.id = "jsonp";

    // If there is already a jsonp script tag in the DOM we'll
    // replace it with the new one.
    // Otherwise, we'll just append the new script tag to the DOM.
    if (oldScript !== null) {
        document.body.replaceChild(newScript, oldScript);
    } else {
        document.body.appendChild(newScript);
    }
}

这个示例函数将一个 URL 作为参数。它创建一个新的脚本标签,将 URL 作为其属性,将“jsonp”作为其 ID。然后,它会查看是否存在另一个具有该 ID 的脚本标签。如果找到一个,就用新的替换它,否则就将新的脚本标签注入 DOM。无论哪种方式,向 DOM 添加新的脚本标记都会导致浏览器加载指定的 URL 作为脚本。

Twitter 就是一个很好的例子。它们为搜索 tweets 提供了一个健壮但简单的 API,该 API 将以 JSONP 格式提供响应。举个简单的例子,我们只展示作者乔恩·里德最近的 20 条推文。我们将构建一个名为handlejsonpresults() 的函数,它将接收一个对象文字作为其参数。根据 Twitter API 文档,该对象将拥有一个名为“results”的数组作为其属性之一。每个结果都是一个表示一条 tweet 的对象,并且有一个名为“text”的属性,包含 tweet 的文本。在清单 4-9 中,我们将遍历数组,为每条 tweet 创建一个列表项,然后将结果追加到文档中:

清单 4-9。 从 Twitter API 获取 tweets 并显示在文档中

<!DOCTYPE html>
<html>
        <head>
               <title>JavaScript Developer's Guide</title>
               <script src="http://code.jquery.com/jquery-1.9.1.min.js">>
        </head>
        <body>
               <h1>Hello World</h1>
               <p id="clickme">Click here for tweeting goodness!</p>
               <script>
// Attach a click event handler to the paragraph so that it will load
// the tweets.
var clickme = document.getElementById("clickme");
clickme.addEventListener("click", function(event) {
executeJSONPQuery("http://searcexact syntax will be provided by the servim:jreid01&callback=handlejsonpresults ");
});

// Execute a JSONP query, reusing the script tag.
function executeJSONPQuery(strUrl) {
    // Check to see if a jsonp script tag has already been injected.
    // Also, create a new script tag with our new URL.
    var oldScript = document.getElementById("jsonp"),
        newScript = document.createElement("script");
    newScript.src = strUrl;
    newScript.id = "jsonp";

    // If there is already a jsonp script tag in the DOM we'll
    // replace it with the new one.
    // Otherwise, we'll just append the new script tag to the DOM.
    if (oldScript !== null) {
        document.body.replaceChild(newScript, oldScript);
    } else {
        document.body.appendChild(newScript);
    }
}

// This function is called by the injected scripts.
function handlejsonpresults(objData) {
    var arrTweets = objData.results,
        arrTweetsLength = objData.results.length,
        i,
        myNewList = document.createElement("ul");

    // Loop through the results array and create a list item for
    // each tweet containing its text.
    for (i = 0; i < arrTweetsLength; i++ ) {
        var myLi = document.createElement("li"),
            myTextNode = document.createTextNode(arrTweets[i].text);
        myLi.appendChild(myTextNode);
        myNewList.appendChild(myLi);
    }

    // Now that all of the tweets have been compiled, append the list to the DOM.
    document.body.appendChild(myNewList);
}
               </script>
        </body>
</html>

在这个例子中,每次你点击段落时,脚本将使用我们的函数执行一个 JSONP 调用(从而回收脚本标签)并将结果附加到页面上。

JSONP 很有用,但是它也有缺点。最大的问题是,它必然涉及安全风险。您相信目标 URL 不会发送恶意脚本来响应您的请求。如果目标 URL 用一个恶意脚本来响应,你会把它注入到你自己的页面中,而你自己却不知道。目前没有有效的方法来关闭这个特殊的安全漏洞,所以一定要打开它。

颜色

CORS 是为了应对 JSONP 的安全问题而创建的,是一个正在获得一些关注的提议标准。CORS 是跨源资源共享的首字母缩略词,它规定了新的 HTTP 报头,浏览器和服务器必须提供这些报头作为它们相互通信的一部分。尽管许多最新版本的浏览器支持 CORS 标题,但旧版本不支持,这对于那些目标浏览器稍微有点过时的项目来说是个糟糕的选择。

发布消息

HTML 5 提供了一个可用于跨域通信的新特性:Post 消息标准。这个标准指定了一个新的 DOM 方法window.postMessage()和一个新的 DOM 事件message,用于 iframes 之间的通信。

iframe 可以从不同于宿主文档的来源加载文档,但是相同来源的策略会阻止任何一个文档的脚本相互交互。然而,Post 消息提供了一种在它们之间传输字符串的安全方法。

在发送文档中,您的脚本可以在指向目标文档的指针上调用postMessage()方法,目标文档可以是子文档,也可以是父文档。postMessage()方法将待传输到目标文档的字符串以及目标文档的期望来源作为参数。

当目标文档收到消息时,它触发一个 DOM message事件。您可以为该事件创建一个处理程序,并将其注册到window对象。传递到处理程序中的事件对象将包含与指定的原点一起发送的字符串。然后,您可以确保从预期的来源收到消息。

Post 消息方法在现代浏览器中得到广泛支持。旧的浏览器也支持它;Internet Explorer 8 及更高版本支持它,Firefox 16+、Chrome 23+和 Safari 5.1+也支持它。Post Message 在移动浏览器上享有更多支持:iOS 上的 Safari Mobile 从 3.2 开始支持它,Android 浏览器从 2.1 开始支持它。

我们将在window.postMessage()下的第八章中详细介绍postMessage()方法。有关语法和示例的详细信息,请参见该部分。

数据缓存

随着您使用XMLHttpRequest进行更多的异步编程,您经常会发现自己想要在本地缓存数据以提高速度和效率。对于移动应用来说尤其如此,在移动应用中,拥有本地数据缓存不仅可以加快应用的速度(因为访问缓存的数据比通过可能相当慢的网络更快),还可以使应用使用更少的电池电量(因为访问网络需要电力)。即使对于桌面应用,想要缓存不经常更改的常用信息也是很常见的。

数据缓存非常简单,通常由代表服务或数据源的标识符、上次访问服务的时间戳以及上次访问服务时返回的数据组成。每次看缓存,都可以看到想要的数据是否缓存;如果不是,您可以简单地调用服务并用新的时间戳缓存它。如果缓存中有数据,就检查它的时间戳。如果它在很久以前被访问,您将知道您需要访问服务并缓存新的结果。但是如果它在很久以前没有被访问过,您可以只使用缓存的数据。

我们已经在上面的使用 XMLHttpRequest 的异步通信小节中构建了一个doXHR()函数。当我们调用该方法时,我们提供一个对象,该对象指定要调用的 URL 以及其他信息(例如要调用的成功方法或要调用的错误方法)。更好的是,如果我们指定一个时间长度作为属性, doXHR()函数将知道我们想要缓存来自该 URL 的数据,它可以如下执行:

  • 检查我们假设的缓存中是否已经有数据。

  • 如果缓存中不存在该数据,则从 URL 获取数据,并使用当前时间戳将其保存在缓存中。

  • 如果数据确实存在于缓存中,检查时间戳。

  • 如果时间戳太旧,从 URL 获取数据,并使用当前时间戳将其保存在缓存中。

  • 如果时间戳不是太旧,就使用缓存中的数据。

我们可以很容易地扩展我们的doXHR()函数来处理缓存。在我们的例子中,我们将使用 HTML 5 定义的 localStorage 特性,因为它得到了广泛的支持(尤其是在移动浏览器中)。正如在第八章中详细介绍的那样,window.localStorage获取键/值对并将它们存储在一个缓存中,即使用户关闭浏览器或重启计算机,该缓存仍然存在,这使得它成为我们数据缓存的理想选择。

我们将从扩展清单 4-10 中的 XHR 定义对象开始,以包括两个新属性:我们正在缓存的服务的名称,以及缓存有效的持续时间:

清单 4-10。XHR 定义对象的更新版本

var myXhrDefs = {
    intCacheDuration: 14400,
    cacheName: "ajax-test",
    strMethod : "GET",
    strUrl : "http://127.0.0.1:8020/developers-guide/chapter-4/ajax-test.txt",
    intTimeout: 3000,
    postData: null,
    boolAsync: true,
    successCallback: function(objEvent) {
        // Do things when the request is successful
        console.log(objEvent.responseText);
    },
    errorCallback: function(objEvent) {
        // Do things when there is an error in the request.
        console.error("The XHR failed with error ", objEvent.status);
    },
    timeoutCallback: function() {
        // Do things when a timeout occurs.
        console.error("The XHR timed out.");
    }
}

这两个新属性是 intCacheDuration(我们将以秒为单位指定;14400 秒是四个小时),还有 cacheName,为缓存的数据提供一个基名。当我们缓存信息时,我们将把“-timestamp”附加到键的cacheName上,当我们缓存数据时,我们将把“-data”附加到键的cacheName上。(所以在这个例子中,ajax-test.txt 的示例 URL 将使用“ajax-test-timestamp”作为时间戳键,使用“ajax-test-data”作为数据键。)

为了利用这个新对象,在清单 4-11 的中,我们将把现有的doXHR()方法包装在另一个方法中,我们称之为doCachedXHR()。这将采用相同的 XHR 定义对象,要么从缓存中读取,要么使用doXHR()获取新信息并缓存:

清单 4-11。doCachedXHR()

// Either perform an asynchronous call to a service and cache it with a timestamp,
// or if the service has already been called and cached, just use that if the data isn't
// too old.  If the data is too old, perform the asynchronous call again and cache the
// results with a new timestamp.
function cachedXHR(myXhrDefs) {
    var fetchNewData = false,
        now = new Date(),
        lastTimeStamp = localStorage.getItem(myXhrDefs.cacheName + "-timestamp");

    // Does the cache even have the specified item?
    if (lastTimeStamp == null) {
        fetchNewData = true;
    } else {
        // We've cached the service at least once. Check the last timestamp.
        var timeStamp = new Date(lastTimeStamp);
        if ((timeStamp.getTime() + (myXhrDefs.intCacheDuration * 1000)) < now.getTime()) {
          fetchNewData = true;
        }
    }

    // If we need to fetch new data, we need to extend the existing successCallback method
    // to cache the new results with a new timestamp.
    if (fetchNewData) {
        myXhrDefs.successCallback = (function(oldCallback) {
            function extendedCallback(objEvent) {
                localStorage.setItem(this.cacheName + "-data", objEvent.responseText);
                localStorage.setItem(this.cacheName + "-timestamp", now.toISOString());
                oldCallback(objEvent);
            }
            return extendedCallback;
        })(myXhrDefs.successCallback);

        // Perform the XHR request.
        doXHR(myXhrDefs);
    } else {
        // Just use the cached data.
        var cachedData = localStorage.getItem(myXhrDefs.cacheName + "-data"),
            fakeEvent = {
                responseText : cachedData
            };
        myXhrDefs.successCallback(fakeEvent);
    }
}

这个方法中有很多东西,包括一个扩展方法的技巧,所以让我们一步一步地来看。

该方法的核心是一个简单的检查:数据是否被缓存?如果它没有被缓存,该方法需要获取数据并缓存它。如果它被缓存但太旧,它需要刷新缓存。如果数据不是太旧,我们可以使用它。这个简单的逻辑构成了这个方法的框架。

我们需要做的第一件事是创建一个代表当前时间的新对象。然后,我们通过查找时间戳来检查数据是否已经被缓存。如果时间戳不存在,我们知道需要获取并缓存数据。

如果时间戳确实存在于缓存中,我们需要检查它是否太旧。首先,我们使用时间戳创建一个新的Date对象。然后我们可以使用 date 对象的 getTime()方法来执行我们的年龄比较——getTime()方法返回从 1970 年 1 月 1 日午夜开始的毫秒数,因此我们必须在将它们添加到时间戳之前将我们的intCacheDuration时间从秒转换成毫秒。(这是比较Date对象的值的一个很好的实际例子。)

如果数据不太旧,我们可以直接使用它。但是如果它太旧,我们必须获取新的数据。我们已经在 XHR 定义对象上定义了我们的 successCallback()方法,但是我们想要扩展该方法,以便它缓存 XHR 请求的结果。基本上,我们所做的是用从立即执行的函数表达式(IEFE) 返回的结果覆盖旧的successCallback()方法。我们将旧的successCallback()方法作为oldCallback参数传递给 IEFE。因此,在 IEFE 中,我们创建了一个缓存数据的新函数,然后调用oldCallback函数。然后,IEFE 返回这个新函数,它接管了successCallback()方法。

我们可以在我们的 XHR 定义对象中重写successCallback()方法,但是使用这种技术,您可以将新的cachedXHR() 方法添加到现有的代码中,而不必为了缓存而修改一堆方法。新方法将为您处理这些问题。

我们鼓励您测试此功能,以验证它是否按您预期的方式工作。您需要设置自己的个人 web 服务器——在本例中,我们只是使用 Aptana 附带的调试服务器,它运行在端口 8020 上。我们还使用了一个简单的 ajax-test.txt 文件,其中包含文本“hello world”但是您可以轻松地传输 JSON 格式的数据,或者 XML 格式的数据。

这个函数非常简单,可以使用更多的功能。比如浏览器不支持 localStorage 怎么办?在这种情况下,您可以很容易地扩展这个函数来使用document.cookie;有关使用 cookies 的详细信息,请参见第八章。

JavaScript 库和框架

像任何语言一样,JavaScript 有大量可以在项目中使用的库和框架。这些库可以是为做一件事而设计的高度专用的库,也可以是执行自己的语法的更通用的库,还可以是在现代浏览器中创建基于 web 的应用的完整框架。

一般来说,库是一个可重用代码的集合,通常很小并且经过优化,专注于一个特定的任务,比如为复杂的任务提供方便的例程,或者用新的特性扩展基本语言。常见的例子包括数学库、访问数据库的库和访问文件系统的库。

在 JavaScript 的具体例子中,库经常用新的特性(比如动画)以及更复杂任务的便利例程(比如异步通信)来扩展 JavaScript。此外,JavaScript 库还可以专注于克服各种标准实现中的不一致性,允许您编写在跨多个平台的多个浏览器中工作的代码。

框架这个术语有点难以确定,因为没有像库那样的传统定义。因此,“框架”可以有许多不同的含义,这取决于所讨论的语言和上下文。就 JavaScript 而言,我们喜欢将框架视为库和例程的复杂集合,它们提供了一个预定义的结构,供您根据需要填充。我们听到的一种解释是,“库是你的代码调用的东西,而框架是调用你的代码的东西。”

选择图书馆

说到选择库,第一步很简单:找到能满足你需要的库。但是很多库都有重叠的特性,那么如何选择哪个最适合你的项目呢?答案不是一成不变的,但是有一些简单的问题可以问自己来帮助你选择:

  • 许可证:该库是否有符合您需求的许可证?大多数库是开源的,允许再分发,但其他的不允许。您应该仔细检查许可证,以确保它为您的项目提供了所需的自由。
  • 支持:图书馆有什么样的支持?正在积极开发中吗?如果你需要帮助,你能得到帮助吗?一些库是由出售支持契约的公司支持的,但大多数是具有不同程度文档的开源项目。
  • 风格:这个库适合你的编程风格吗?在您的项目中设置和维护有多容易?一些库(和许多框架)强制使用它们自己的语法和风格,您需要确保自己对此感到满意。
  • 大小:图书馆有多大?图书馆可以缩小和压缩吗?库加载的时候有多贵,有没有造成明显的延迟?
  • 安全性:该库满足您的安全性需求吗?如果它是一个开源库,你在这个库的代码中发现什么问题了吗?安全性经常被忽视,但它和其他任何考虑因素一样重要。
  • 测试:这个库有自己的单元测试吗?您将如何为使用该库的代码编写测试?这对你现有的测试实践有用吗?

并非所有这些考虑都与你的情况相关。或许最重要的是许可和支持,但考虑所有这些因素将有助于您做出明智的选择。

下面是常见 JavaScript 库和框架的简要列表。这个列表并不是决定性的,甚至也不具有代表性,JavaScript 库的生态系统太庞大了,无法做出这样的声明。然而,这个列表确实代表了我们经常遇到和使用的库,并且是您探索 JavaScript 库的起点。

原型和脚本

http://www.prototypejs.org/http://script.aculo.us/

Prototype 是最早的 JavaScript 库之一。它提供了对类、事件委托、AJAX 便利例程和 DOM 扩展的支持。Scriptaculous 建立在 Prototype 之上,为用户界面和交互提供了丰富的框架。它支持动画效果、DOM 元素的拖放和各种 DOM 实用工具。这两个库都得到了活跃社区的大力支持,并且正在积极开发中。这两个框架都有自己的单元测试,Scriptaculous 支持测试您自己的代码。

道场工具包

http://www.dojotoolkit.org/

Dojo Toolkit 是一个轻量级的库,它提供了对事件规范化、简化的 AJAX 交互和 DOM 操作的支持。此外,Dojo 内置了对模块的支持,允许您封装代码以便重用。Dojo 还支持动态加载模块,因此应用可以在需要时加载模块,而不是一次性加载。

jQuery

http://www.jquery.org

jQuery 可能是世界上使用最广泛的 JavaScript 库。jQuery 强制执行基于选择器的语法,并包含跨平台规范化事件、简化 AJAX 交互、基本动画和各种便利例程的特性。有关使用 jQuery 的详细信息,请参见下一节。

【sencha ext js】

http://www.sencha.com/products/extjs/

Sencha 的 ext JS 是一个专注于为 JavaScript 应用创建类似应用的用户界面的框架。它有一组丰富的 UI 小部件,并支持图表和绘图。该框架还包括一个高级布局引擎,允许您使用停靠和其他功能创建复杂的交互式布局。

YUI

http://www.yuilibrary.com/

YUI 是由雅虎建立和维护的一套开源 JavaScript 库。雅虎在他们的产品中使用了 YUI,因此它是高性能和久经考验的。它包括事件规范化、动画、一个轻量级应用框架、几个 UI 交互(如拖放和排序)和小部件(如富文本编辑器和数据网格)。YUI 有几个图书馆,加在一起相当广泛,但你可以挑选你需要的图书馆,并创建一个定制的较小的图书馆。YUI 也包括 CSS 库;我们在他们的字体和 CSS 标准化库方面运气非常好。

关闭

https://developers.google.com/closure/

Closure 是 Google 的 JavaScript 应用框架。它包括闭包 JavaScript 库、闭包编译器和闭包模板。Closure JavaScript 库包括 JavaScript 库的所有标准特性,以及各种各样的 UI 交互和小部件。Closure 使用严格命名空间的面向对象语法,对于从 C#、Java 或 C++等语言迁移到 JavaScript 的开发人员来说,这可能是最容易理解的。Closure 还包括 Closure 编译器,我们在本章前面已经提到过。它可以编译、优化和缩小任何 JavaScript,但是特别适合使用闭包库的 JavaScript。最后,闭包模板为 HTML 和 JavaScript 提供了模板化解决方案。整个框架是完全单元测试的,支持(并鼓励)你自己代码的单元测试。

Node.js

http://www.nodejs.org

Node 是一个服务器端的 JavaScript 平台。它基于 Chrome 的 V8 JavaScript 引擎,因此不仅非常符合 ECMA-262 标准,而且性能也很高。因为 Node 在浏览器上下文之外运行,所以它没有 JavaScript 库中的任何常见特性:它没有事件规范化或 DOM 操作特性。相反,它有一组用于构建服务器特性(如 web 服务器、访问文件系统、网络操作(包括原始套接字管理)和模块系统)的 API。

装配

http://www.montagejs.org

作为该领域的相对新人,Montage 是一个令人印象深刻的框架,它是围绕 JavaScript、HTML 和 CSS 的最新特性构建的。它专注于构建复杂的应用,包括围绕 HTML 和 CSS 构建的自己的模板系统,并有一系列令人印象深刻的预建 UI 组件,可在移动和桌面环境中工作。蒙太奇本身是使用 MVC 模式构建的,当您使用蒙太奇时,您会很自然地陷入这种模式,所以这是对用 JavaScript 构建的 MVC 应用的一个很好的介绍。最后,因为它在幕后为您做了大量繁重的工作,所以 Montage 允许您在没有任何强制语法(如选择器、任意名称空间或其他语法糖)的情况下用纯 JavaScript 进行编程。

微 JS

http://www.microjs.com/

MicroJS 实际上是小型 JavaScript 库的交换所,您可以根据需要组合这些库。该网站允许您根据需要的功能搜索库。所有的库都很小,但是它们的许可、支持、风格、安全性和测试各不相同,所以您必须自己探索各个库。我们在这里提到它是因为它是寻找专注于单一任务的库的一个很好的起点。

使用 jQuery

jQuery 由 John Resig 于 2006 年创建,是世界上使用最广泛的 JavaScript 库之一。有许多可用的库,但是 jQuery 是我们的首选。它为从动画到 AJAX 的所有内容提供了一个健壮的跨浏览器 API。

jQuery 最大的好处是:

  • jQuery 很小。只有 32kb (minified 和 GZipped ),对于这样一个全功能的库来说是非常小的。
  • jQuery 修复了许多依赖于浏览器的问题。创建 jQuery 的主要原因之一是帮助克服 JavaScript 和 DOM 标准的浏览器实现中的差异。如果您希望解决脚本中的跨浏览器问题,jQuery 是一个很好的选择。
  • jQuery 经过了很好的测试。jQuery 不仅通过自己的单元测试套件得到了很好的测试,而且 jQuery 在如此多的站点上被如此多的人使用,以至于错误很快被报告。
  • jQuery 很快。jQuery 的内部编码是高度优化和高效的。
  • 编写 jQuery 代码很快。因为 jQuery 为如此多的常见任务提供了方便的速记方法,所以用 jQuery 编写代码比用普通 JavaScript 编写等价代码花费的时间更少。
  • jQuery 很容易使用。您所要做的就是将 jQuery 脚本包含在文档的头中,它的所有功能都将可供随后的任何脚本使用。

使用 jQuery 需要记住的一个主要注意事项是,因为它抽象掉了太多的 JavaScript 和 DOM,所以它倾向于执行自己的做事方式。这很好,但是这不一定适合用纯 JavaScript 处理同样的问题。我们通常建议 JavaScript 开发新手在使用 jQuery 时记住这一点,以避免潜在的有害习惯。(许多 JavaScript 库强制使用自己的语法,所以这个建议不仅仅适用于 jQuery。)

在本节中,我们不打算涵盖 jQuery 的所有内容——已经有整本书都在讨论这个问题。幸运的是,jQuery 的在线文档是最好的,你可以在http://docs.jquery.com/找到它,你会在那里找到完整的 jQuery API 的详细文档,以及几乎所有东西的例子。

它是如何工作的

jQuery 的工作方式是在全局范围内创建一个jQuery()函数对象(为了简洁起见,别名为$();您可以使用这两种方法中的任何一种),然后您可以在自己的脚本中使用它们。jQuery()函数将 DOM 中元素的 CSS 选择器作为参数(就像 DOM 标准方法querySelector()querySelectorAll()一样,并返回对包装在jQuery对象中的元素的引用(类似于 JavaScript 如何用String对象包装 string 原语以提供所需的功能)。产生的对象通常被称为“jQuery 选择器”,它有一组令人印象深刻的方法和属性,您可以访问这些方法和属性来影响它所引用的元素。

举个例子,假设你想要隐藏页面上的一个元素,如清单 4-12 中的所示。在常规 JavaScript 中,您可能会编写如下代码:

清单 4-12 。使用 JavaScript 隐藏元素

<!DOCTYPE html>
<html>
        <head>
               <title>JavaScript Developer's Guide</title>
        </head>
        <body>
               <h1>Hello World</h1>
               <script>
var myHeadline = document.querySelector("h1");
myHeadline.style.display = "none";
               </script>
        </body>
</html>

在这个例子中,我们使用querySelector()方法来获取对标题的引用,然后我们在标题上设置一个display: none的内联样式属性。一旦示例加载到您的浏览器中,标题就会消失(在标题消失之前,您甚至可能看不到它)。

在 jQuery 中,您将为jQuery()函数提供选择器(在这些例子中我们将使用$()),然后对结果调用hide()方法,如清单 4-13 中的所示:

清单 4-13。 使用 jQuery 隐藏一个元素

<!DOCTYPE html>
<html>
        <head>
               <title>JavaScript Developer's Guide</title>
               <script src="http://code.jquery.com/jquery-1.9.1.min.js">>
        </head>
        <body>
               <h1>Hello World</h1>
               <script>
$("h1").hide();
               </script>
        </body>
</html>

仅用一行代码就得到与上一个示例相同的结果。请注意,我们已经在示例的开头包含了 jQuery 库;在尝试使用该库之前,您必须将其包含在内。由于 MediaTemple 的慷慨,jQuery 可以通过内容分发网络(CDN)获得。您可以轻松下载 jQuery 并提供您自己的私有副本。详情和示例见http://www.jquery.com/download

除了hide()方法,jQuery 还提供了一个fadeOut()方法,它可以让目标元素淡出人们的视线,如清单 14 所示:

清单 4-14。jQueryfadeOut()方法

<!DOCTYPE html>
<html>
        <head>
               <title>JavaScript Developer's Guide</title>
               <script src="http://code.jquery.com/jquery-1.9.1.min.js">>
        </head>
        <body>
               <h1>Hello World</h1>
               <script>
$("h1").fadeOut();
               </script>
        </body>
</html>

当你加载这个例子时,你会看到标题很快淡出。jQuery 还提供了一个fadeIn()方法,如清单 4-15 所示:

清单 4-15。jQueryfade in()方法

<!DOCTYPE html>
<html>
        <head>
               <title>JavaScript Developer's Guide</title>
               <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
        </head>
        <body>
               <h1>Hello World</h1>
               <script>
$("h1").fadeOut();
$("h1").fadeIn();
               </script>
        </body>
</html>

在这个例子中,我们首先淡出标题,然后再淡入。您会注意到我们调用了两次jQuery()函数来选择元素,这有点低效。jQuery()函数有点贵;它必须去获取元素,然后将它包装在一个jQuery对象中。如果你要多次使用同一个选择器,你可以给它起一个变量的别名。同样,几乎所有的 jQuery 方法都返回原始的选择器,所以你可以像清单 4-16 中的那样链接命令:

清单 4-16。 链接 jQuery 方法获得效率

<!DOCTYPE html>
<html>
        <head>
               <title>JavaScript Developer's Guide</title>
               <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
        </head>
        <body>
               <h1>Hello World</h1>
               <script>
$("h1").fadeOut().fadeIn();
               </script>
        </body>
</html>

这个例子的结果与上一个例子完全相同,但是它更有效(因为我们只调用了一次jQuery()函数来选择元素,然后使用链接),而且更小——少了 8 个字符。这并不是一个很大的节省,但是平均来说,您会发现 jQuery 代码非常简洁,从而产生更小的脚本。

jQuery 中的事件

jQuery 最初是作为一种为 JavaScript 开发人员提供跨浏览器功能的方式出现的,其中最重要的一种方式是提供一个事件系统,该系统在所有受支持的浏览器中都可以正常工作。如果你还记得第三章的话,DOM 事件模型在低于 9 版本的 ie 浏览器中不能正确实现。jQuery 为您处理这些问题,因此您的事件处理程序将在所有受支持的浏览器中按照您的预期执行。

从 1.7 版本开始,jQuery 使用 on()方法附加事件处理程序,使用off()方法移除事件处理程序。jQuery 的on()方法有一个简单的语法:

$(targetSelector).on(events, filterSelector, data, handler);

  • targetSelector:您希望将事件处理程序附加到的元素的选择器。
  • events:一个或多个事件类型;多个事件类型可以用空格分隔。例如,“click keydown”将指定每当用户单击目标元素时,或者每当用户在目标元素具有键盘焦点时按下某个键时,都应触发事件处理程序。您还可以通过将.namespace附加到事件类型(例如click.kittenRescue)来将名称空间应用到您的事件。这允许您在给定的targetSelector中添加和删除多个相同类型的事件处理程序。
  • filterSelector:一个 jQuery 选择器,指定只有当指定的事件类型源自匹配filterSelector(并且是targetSelector的子元素)的元素时,该事件处理程序才应该执行。此功能允许您在委派事件时有很大的灵活性。但是,请注意,每次指定的事件被分派到targetSelector时,都会检查选择器。对于大多数不经常发生的事件来说,这不是问题,但是对于很多次快速触发的事件(比如 mouseover 事件)来说,这可能会导致性能下降。
  • data:对象引用或对象文字。该对象将作为event.data属性在处理程序中的结果事件对象上可用。此功能允许您将任意数据传入事件处理程序。
  • handler:接受事件对象作为参数的函数表达式(可以是匿名内联函数),当指定事件在targetSelector上触发时执行。

关于使用 jQuery 事件系统的例子,考虑我们在第三章中的小猫营救游戏。在普通的 JavaScript 中,它看起来像这样:

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Developer's Guide</title>
        <style>
.basket {
    width: 300px;
    height: 300px;
    position: absolute;
    top: 100px;
    right: 100px;
    border: 3px double #000000;
    border-radius: 10px;
}
        </style>
    </head>
    <body>
        <h3>Rescue the kittens!</h3>
        <p>Click on them to put them in their basket!</p>
        <ul id="kittens">
            <li>Rowly</li>
            <li>Fred</li>
            <li>Mittens</li>
            <li>Lenore</li>
        </ul>
        <ul class="basket"></ul>
        <script>
var basket = document.querySelector(".basket"),
    kittens = document.getElementById("kittens");

kittens.addEventListener("click", function(event) {
    basket.appendChild(event.target);
}, false);

        </script>
    </body>
</html>

这里,我们将 click 事件处理程序委托给无序列表,这样我们就不必为每个单独的列表项附加一个事件处理程序。

用 jQuery 编写,这个游戏看起来会像清单 4-17 中的:

清单 4-17。 用 jQuery 写的小猫营救

<!DOCTYPE html>
<html>
        <head>
               <title>JavaScript Developer's Guide</title>
               <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
        <style>
.basket {
    width: 300px;
    height: 300px;
    position: absolute;
    top: 100px;
    right: 100px;
    border: 3px double #000000;
    border-radius: 10px;
}
        </style>
    </head>
    <body>
        <h3>Rescue the kittens!</h3>
        <p>Click on them to put them in their basket!</p>
        <ul id="kittens">
            <li>Rowly</li>
            <li>Fred</li>
            <li>Mittens</li>
            <li>Lenore</li>
        </ul>
        <ul class="basket"></ul>
        <script>
var $basket = $(".basket");
$("#kittens").on("click", function(event) {
   $basket.append(event.target);
});
        </script>
        </body>
</html>

在 jQuery 版本中,我们首先保存一个对jQuery(".basket")选择器的引用,这样我们就可以在每次事件处理程序触发时重用它——这样我们就不必在每次事件触发时都获取对购物篮的引用。然后,我们使用on()方法将事件处理程序附加到无序列表。我们可以指定一个“li”的filterSelector,但是在这个例子中没有必要,因为事件只能在列表项上产生。

使用 off()方法移除事件处理程序:

$(targetSelector).off(events, filterSelector, handler);
  • targetSelector:指定要从中移除事件处理程序的元素的选择器。
  • events:要删除的事件处理程序的事件类型或名称空间的列表。
  • filterSelector:使用on()方法委托事件时指定的选择器。
  • handler:事件绑定时指定的函数。

注意,您可以只指定一个名称空间来删除所有在targetSelector上共享该名称空间的事件处理程序。例如,如果您添加了以下具有相同命名空间的假设事件处理程序:

$(targetSelector).on("click.myEventHandler", "li", handleClickEvent);
$(targetSelector).on("focus.myEventHandler", handleFocusEvent);

您可以通过仅指定它们的名称空间来删除它们:

$(targetSelector).off(".myEventHandler");

这将同时移除 click 事件处理程序和 focus 事件处理程序。

jQuery 提供的一个方便的事件是文档就绪事件,当给定文档的 DOM 已经被加载和解析并可用于操作时,就会触发该事件。此事件为您的脚本提供了额外的灵活性;有了它,你就不需要在文档末尾加载你的脚本了(详见本章前面关于优化技术的章节)。您监听文档上的ready事件,就像任何其他目标元素上的任何其他事件一样,如清单 4-18 中的所示:

清单 4-18。 使用 jQuery 中的文档就绪事件

<!DOCTYPE html>
<html>
        <head>
               <title>JavaScript Developer's Guide</title>
               <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
               <script>
$(document).on("ready", function() {
  $("h1").fadeOut().fadeIn();
});
               </script>

        </head>
        <body>
               <h1>Hello World</h1>
        </body>
</html>

在这个例子中,我们在头部有一个访问 DOM 元素的脚本。通常,元素在脚本执行时不会准备好,所以我们为文档的ready事件创建一个事件处理程序。当该事件触发时,我们知道 DOM 已被解析并可用于操作,因此我们可以执行代码。

jQuery UI

jQuery UI 是由 jQuery group 维护的用户界面交互和主题小部件的库。它提供了一组易于使用的交互方式:

  • Draggable:允许您将元素指定为可拖动的,这意味着用户可以在页面上单击并拖动它们。
  • Droppable:允许您将元素指定为可拖放的,这意味着可以将可拖动的元素拖放到元素上。
  • Resizable:允许您将元素指定为 resizable,这意味着用户可以“抓取”一条边(您指定的边)并调整元素的大小。
  • 可选择:允许您选择元素,使用修饰键进行多重选择,以及通过在目标元素周围拖动和绘制一个框来选择框。
  • Sortable:允许您指定一组可以使用拖放交互进行排序的元素。元素可以是简单的列表或网格。

jQuery UI 还有一整套有用的用户界面小部件,包括一个日期选择器和一个能够生成弹出窗口和模态的对话框系统。所有的部件在外观上都是完全可定制的。您可以使用 jQuery UI 站点上可用的 themeroller 来滚动自定义主题,或者您可以编写自定义 CSS 来使小部件完全按照您想要的方式出现。

有关 jQuery UI 的更多信息,请参见http://jqueryui.com

jQuery 移动

jQuery Mobile 为开发移动 web 应用提供了一个基本框架。它提供了一系列事件,重点关注点击和滑动等触摸交互,以及移动设备特有的交互,如方向变化。jQuery Mobile 还提供了一组移动优化的用户界面小部件,这些小部件提供了移动应用中常见的 UI 特性,比如工具栏、可展开和可折叠的内容区域、滑块和列表视图。

jQuery Mobile 的工作方式与其他 jQuery 产品略有不同。jQuery Mobile 没有直接在 jQuery 选择器上操作,而是定义了一组可以应用于标记的数据属性。这些数据属性为您提供了一种方式来指定元素应该是什么:页眉、页脚、列表视图、面板、按钮、滑块等等。创建语义标记,将 jQuery Mobile 数据属性应用于元素,然后加载到 jQuery Mobile 中,它会为您初始化元素。

例如,考虑清单 4-19 中非常基本的两页移动应用:

清单 4-19。 一个使用 jQuery Mobile 构建的两页移动应用

<!DOCTYPE html>
<html>
        <head>
               <title>jJavaScript Developer's Guide</title>
        <link rel="stylesheet" href="http://code.jquery.com/mobile/1.3.0/jquery.mobile-1.3.0.min.css" />
        <script src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
        <script src="http://code.jquery.com/mobile/1.3.0/jquery.mobile-1.3.0.min.js"></script>
        </head>
        <body>
               <section id="page1" data-role="page">
                       <header data-role="header"><h1>JavaScript Developer's Guide</h1></header>
                       <div class="content" data-role="content">
                              <p>Here is a sample jQuery Mobile application.</p>
                              <p><a href="#page2" data-transition="slide">Go to next page</a></p>
                       </div>
                       <footer data-role="footer" data-position="fixed"><h1>Apress</h1></footer>
               </section>

               <section id="page2" data-role="page">
                       <header data-role="header"><h1>JavaScript Developer's Guide</h1></header>
                       <div class="content" data-role="content">
                              <p>This is the second page.</p>
                              <p><a href="#page1" data-transition="slide"                               data-direction="reverse">Go to previous page</a></p>
                       </div>
                       <footer data-role="footer" data-position="fixed"><h1>Apress</h1></footer>
               </section>
        </body>
</html>

在这个例子中,我们从基本的语义 HTML 5 标记开始,包括节、页眉、页脚等。然后,使用 jQuery Mobile 预定义的数据属性,我们指定了页面、标题、内容区域等。我们甚至指定页面之间的过渡应该是从第 1 页到第 2 页的幻灯片过渡,以及从第 2 页回到第 1 页的反向幻灯片过渡。

当您将这个应用加载到浏览器中并初始化 jQuery Mobile 时,它会扫描数据属性,对它们进行必要的修改,并为您处理所有的页面管理。jQuery Mobile 通过数据属性公开其所有功能,因此只需语义 HTML 而无需编写一行 JavaScript 就可以创建相当复杂的移动应用。

这只是 jQuery Mobile 的皮毛。有关更多详细信息,请参见位于http://jquerymobile.com的 jQuery Mobile 文档。

建造图书馆

与任何语言一样,您使用 JavaScript 越多,您就会发现您在项目中重新创建的代码越多。也许你有你喜欢的枚举对象的特定方式。也许您有一种处理 DOM 事件的特殊方式。不管它们是什么,将这些常用的代码放到您自己的 JavaScript 库中可能是有意义的。到目前为止,在本章中,我们已经提供了一组有用的方法来执行异步请求、缓存数据和执行跨域请求。

在本章中,我们还向您介绍了 jQuery ,这是最常见的 JavaScript 库之一,因此您已经对一个好的库能为您做些什么有了一些了解。如果我们想创建自己的类似 jQuery 的库,包含我们在本章中构建的所有方法,会怎么样呢?我们希望我们的新库具有基于选择器的语法,并支持命令链,就像 jQuery 一样。

这实际上很容易做到。在这一节中,我们将创建一个名为“jkl”的新库(发音为“Jekyll”,以同名的好医生命名),因为“jkl”很容易键入。创建我们的库的基本模式类似于清单 4-20 :

清单 4-20。 基本库模式

(function() {
    var window = this,
        undefined;

    jkl = window.jkl = function(selector) {
        return new jkl.jklm.init(selector);
    }

    jkl.jklm = jkl.prototype = {
        init: function(selector) {
            this.selector = selector;
            return this;
        }
    }
    jkl.jklm.init.prototype = jkl.jklm;
})();

这里,我们再次使用一个直接调用的函数表达式来创建一个闭包,我们可以将它用作我们自己的私有游乐场。我们希望我们的库完全存在于自己的名称空间中,并且在全局名称空间中只公开一个方法:jkl()函数。要做到这一点,我们必须做一点花哨的步法:

  1. 我们在全局上下文中定义了jkl()函数(也就是window对象)。这个函数调用一个名为init()的构造函数,它位于jkl的子属性上。这个jkl的子属性,我们称之为jklm(也是为了方便输入)是我们将要添加方法的名称空间。
  2. 到目前为止,我们所做的一切都相当简单,但这里是第一点令人费解的代码:我们将jkl.jklm设置为对jkl原型的引用。因此,我们添加到jkl.jklm的任何属性或方法都将被添加到jkl的原型中,从而可供jkl()使用。另一种方式是,我们在jkl内创建一个私有名称空间,它的属性和方法在jkl上公开,就像它们最初是在那里定义的一样。
  3. jkl.jklm名称空间中,我们创建了我们的init()函数,它将作为我们库的构造函数。每次有人呼叫jkl(selector)就相当于呼叫new jkl.jklm.init(selector)。我们的init()函数将选择器添加到新构建的jkl副本中,然后将结果返回到全局范围。
  4. 最后,我们将jkl.jklm.init的原型设置为对jkl.jklm(从而对jkl.prototype)的引用,这就结束了原型圈。

这个库目前不做任何事情,因为除了init()之外,它没有任何方法。我们通过扩展jkl.jklm来添加方法,如清单 4-21 所示:

清单 4-21。 向库中添加几个基本方法

(function() {
    var window = this,
        undefined;

    jkl = window.jkl = function(selector) {
        return new jkl.jklm.init(selector);
    }

    jkl.jklm = jkl.prototype = {
        init: function(selector) {
            this.selector = selector;
            return this;
        },

        // Hide the target element.
        hide : function() {
            document.querySelector(selector).style.display = "none";
            return this;
        },

        // Show the target element.
        show : function() {
            document.querySelector(selector).style.display = "inherit";
            return this;
        },

        // Make the target element red
        enredden : function() {
            document.querySelector(this.selector).style.backgroundColor = "#F00";
            return this;
        }
    }
    jkl.jklm.init.prototype = jkl.jklm;
})();

现在我们的库会有三种方法:hide(), show()enredden()。注意,在每个方法的末尾,我们返回this,这允许方法链接,就像 jQuery 一样。我们可以像这样使用清单 4-22 中的库:

清单 4-22。 使用 jkl 库

<!DOCTYPE html>
<html>
    <head>
        <title>jJavaScript Developer's Guide</title>
        <script src="jkl-0.0.1.js"></script>
    </head>
    <body>
        <h1>Testing the jkl Library</h1>
        <script>
jkl("h1").enredden();
        </script>
    </body>
</html>

在这个简单的例子中,我们使用 enredden()方法将标题的背景改为红色。现在,在清单 4-23 中,我们可以向库中添加其他方法(执行 XHR 请求、执行 JSONP 查询和数据缓存):

清单 4-23。 向 jkl 库中添加其他方法

(function() {
    var window = this,
        undefined;

    jkl = window.jkl = function(selector) {
        return new jkl.jklm.init(selector);
    }

    jkl.jklm = jkl.prototype = {
        init: function(selector) {
            this.selector = selector;
            return this;
        },
        // Hide the target element.
        hide : function() {
            document.querySelector(selector).style.display = "none";
            return this;
        },

        // Show the target element.
        show : function() {
            document.querySelector(selector).style.display = "inherit";
            return this;
        },

        // Make the target element red
        enredden : function() {
            document.querySelector(this.selector).style.backgroundColor = "#F00";
            return this;
        },

        // Perform an asynchronous request defined by myXhrDefs object.
        doXHR : function(myXhrDefs) {
            // Create and configure a new XMLHttpRequest object
            var myXhr = new XMLHttpRequest(),
                myTimer = null;
            myXhr.open(myXhrDefs.strMethod, myXhrDefs.strUrl, myXhrDefs.boolAsync);
            // Register the error and success handlers
            myXhr.onreadystatechange = function(objEvent) {
                // If readyState is 4, request is complete.
                if (myXhr.readyState === 4) {
                    // Cancel the timeout timer if we set one.
                    if (myTimer !== null) {
                        clearTimeout(myTimer);
                    }
                    // If there’s an error, call the error callback,
                    // Otherwise call the success callback.
                    if ((myXhr.status !== 200) && (myXhr.status !== 304)) {
                        if (myXhrDefs.errorCallback != null) {
                            myXhrDefs.errorCallback(myXhr);
                        }
                    } else {
                        myXhrDefs.successCallback(myXhr);
                    }
                }
            }

            // Handle timeouts (set myXhrDefs.intTimeout to null to skip)
            // If we're working with a newer implementation, we can just set the
            // timeout property and register the timeout callback.
            // If not, we have to set a timer running that will execute the
            // timeout callback.
            if (myXhrDefs.intTimeout !== null) {
                if (typeof myXhr.ontimeout !== "undefined") {
                    myXhr.timeout = myXhrDefs.intTimeout;
                    myXhr.ontimeout = myXhrDefs.timeoutCallback;
                } else {
                    myTimer = setTimeout(myXhrDefs.timeoutCallback, myXhrDefs.intTimeout);
                }
            }

            // Send the request
            myXhr.send(myXhrDefs.postData);
            return this;
        },

        // Execute a cross-domain JSONP query.
        executeJSONPQuery : function(strUrl) {
            // Check to see if a jsonp script tag has already been injected.
            // Also, create a new script tag with our new URL.
            var oldScript = document.getElementById("jsonp"),
                newScript = document.createElement("script");
            newScript.src = strUrl;
            newScript.id = "jsonp";

            // If there is already a jsonp script tag in the DOM we’ll
            // replace it with the new one.
            // Otherwise, we'll just append the new script tag to the DOM.
            if (oldScript !== null) {
                document.body.replaceChild(newScript, oldScript);
            } else {
                document.body.appendChild(newScript);
            }
            return this;
        },

        // Perform a cached XHR request.
        cachedXHR : function(myXhrDefs) {
            var fetchNewData = false,
                now = new Date(),
                lastTimeStamp = localStorage.getItem(myXhrDefs.cacheName + "-timestamp");

            // Does the cache even have the specified item?
            if (lastTimeStamp == null) {
                fetchNewData = true;
            } else {
                // We've cached the service at least once. Check the last timestamp.
                var timeStamp = new Date(lastTimeStamp);
                if ((timeStamp.getTime() + (myXhrDefs.intCacheDuration * 1000)) < now.getTime()) {
                  fetchNewData = true;
                }
            }

            // If we need to fetch new data, we need to extend the existing successCallback method
            // to cache the new results with a new timestamp.
            if (fetchNewData) {
                myXhrDefs.successCallback = (function(oldCallback) {
                    function extendedCallback(objEvent) {
                        localStorage.setItem(this.cacheName + "-data", objEvent.responseText);
                        localStorage.setItem(this.cacheName + "-timestamp", now.toISOString());
                        oldCallback(objEvent);
                    }
                    return extendedCallback;
                })(myXhrDefs.successCallback);

                // Perform the XHR request.
                doXHR(myXhrDefs);
            } else {
                // Just use the cached data.
                var cachedData = localStorage.getItem(myXhrDefs.cacheName + "-data"),
                    fakeEvent = {
                        responseText : cachedData
                    };
                myXhrDefs.successCallback(fakeEvent);
            }
            return this;
        },

        // Perform an asynchronous call to the specified URL and load the results into
        // the target element.
        load : function(strUrl){
            var ptrTarget = document.querySelector(this.selector),
                myXhrDefs = {
                    strMethod : "GET",
                    strUrl : strUrl,
                    intTimeout: 3000,
                    postData: null,
                    boolAsync: true,
                    successCallback: function(objEvent) {
                        // Do things when the request is successful
                        ptrTarget.innerHTML = objEvent.responseText;
                    },
                    errorCallback: function(objEvent) {
                        // Do things when there is an error in the request.
                        console.error("The XHR failed with error ", objEvent.status);
                    },
                    timeoutCallback: function() {
                        // Do things when a timeout occurs.
                        console.error("The XHR timed out.");
                    }
                };

            this.doXHR(myXhrDefs);
            return this;
        }
    }
    jkl.jklm.init.prototype = jkl.jklm;
})();

这里我们添加了之前定义的our doXHR(), cachedXHR()executeJSONPQuery()方法,基本上是通过将它们的代码复制并粘贴到我们的库中,只做了很少的修改。唯一的区别是,在每一个结束时,我们返回this,这样我们就可以链接方法。

此外,我们还添加了一个新方法:一个名为load()的简写方法,它将对指定的 URL 执行异步调用,并将结果作为 HTML 放入目标选择器中。假设在我们的本地开发服务器上,我们有一个名为ajax-test.txt的简单文本文件,其中包含文本“hello world”,我们可以在清单 4-24 中使用我们的新load()方法:

清单 4-24。 使用新的加载()方法

<!DOCTYPE html>
<html>
    <head>
        <title>jJavaScript Developer's Guide</title>
        <script src="jkl-0.0.1.js"></script>
    </head>
    <body>
        <h1>Testing The jkl Library</h1>
        <script>
jkl("h1").load("http://127.0.0.1:8020/developers-guide/chapter-4/ajax-test.txt").enredden();
        </script>
    </body>
</html>

当您加载这个示例时,标题将立即被替换为“hello world ”,然后其背景色将设置为红色。load()方法是一个很好的例子,说明了一个库可以帮你省去很多麻烦;想要从服务器获取信息并将其直接插入 DOM 中的目标元素是很常见的,现在我们的库将通过一个函数调用为我们做到这一点。我们甚至可以创建一个 cachedLoad()方法,它将使用我们的cachedXHR()方法来缓存结果。

摘要

在这一章中,我们试图解决使用 JavaScript 的实际问题。

  • 我们首先概述了可用于处理 JavaScript 的流行工具,包括 ide 和个人服务器。我们还讨论了一些调试脚本的基本技术。
  • 我们讨论了如何有效地加载脚本。我们还讨论了感知延迟和实际延迟之间的差异,并讨论了处理这两种延迟的技术。
  • 我们讨论了使用 XMLHttpRequest 对象的异步通信,这种技术构成了许多 JavaScript 应用的基础。我们甚至构建了一个可重用的方法,在您自己的项目中轻松地使用 AJAX。
  • 因为异步通信受到同源策略的限制,所以我们讨论了一些常见的跨域技术,这些技术将允许您相对安全地绕过该策略。作为该过程的一部分,我们构建了一个简单的方法来有效地执行 JSONP 请求。
  • 为了提高效率和速度,我们探索了一种在浏览器中缓存数据的方法,并构建了一个函数来缓存您指定的异步请求。
  • 我们讨论了如何选择 JavaScript 库,并回顾了一些更常见的 JavaScript 库。
  • 我们快速概述了 jQuery 及其工作原理。我们不能深入研究 jQuery,因为它是一个非常有特色的库,但是我们希望我们激起了您的兴趣。我们还简要介绍了 jQuery UI 和 jQuery Mobile。
  • 最后,我们讨论了如何构建自己的库。通过遵循一个简单的模式,很容易构建自己的常用方法库。

这些项目中的每一个都提供了本书涵盖的许多主题的具体例子:闭包、事件、原型继承、日期对象等。

本书的讨论章节到此结束。本书接下来的几章是参考章节,从 JavaScript 对象的完整参考开始。

五、JavaScript 全局对象参考

因为 JavaScript 的基本特性之一是原型继承,所以它没有数组或字符串之类的基类。相反,JavaScript 有一组基本对象,相同类型的其他对象可以从这些对象继承它们的属性和方法。这些基本对象位于全局范围内(在浏览器中是window对象),因此可以随时访问。

本章为主要的 JavaScript 全局对象ArrayBooleanDateNumberRegExpString提供参考。它还包含了针对Math对象的文档,与其他对象不同,它不作为继承的基础对象(你永远不会创建新的Math对象),而是作为数学方法和函数的库。该引用按对象名称的字母顺序排列,每个对象按属性和方法组织。此外,还有其他几个保存在全局范围内的变量和函数非常有用,我们将在本章的最后一节“其他全局变量和函数”中介绍它们

排列

JavaScript Array对象是数组的全局构造函数。数组是数字索引值的简单数据结构。在 JavaScript 中,使用熟悉的方括号符号来访问数组;例如,arrayName[12]将访问存储在索引 12 处的数组的值。JavaScript 数组有以下基本属性:

  • JavaScript 数组是零索引的,因此数组中的第一个元素位于索引 0 处,而不是索引 1 处。
  • JavaScript 数组具有动态长度,因此您不必定义一个数组将有多少个元素。
  • 当您访问不存在的元素时,JavaScript 数组不会抛出越界错误。相反,JavaScript 返回值undefined
  • JavaScript 数组可以索引任何类型的有效 JavaScript 元素:字符串、数字、其他数组(JavaScript 就是这样实现多维数组的),甚至对象。JavaScript 数组也可以包含多种类型的元素,因此可以将对象、字符串和布尔值作为不同的元素放在一个数组中。
  • JavaScript 数组本身就是对象——它们从全局Object对象继承而来,可以访问对象的所有特性。

Array对象有几个管理数组的有用方法。关于 JavaScript 数组的详细讨论,请参见第二章中的“数组”一节。

构造新数组有两种主要方法:直接使用Array对象作为构造函数,或者使用文字符号。

如果使用Array对象作为构造函数,该方法将接受一个可选参数,该参数是一个表示数组长度的整数。该整数应介于 0 和 2³²¹ 之间;任何其他值都会抛出一个RangeError异常。如果提供了参数,新数组将用指定数量的元素初始化(每个元素将被设置为undefined)。从实践的角度来看,因为 JavaScript 数组的长度是动态的,并且 JavaScript 在试图访问不存在的元素时不会抛出越界错误,所以创建预定义长度的数组没有多大意义。

要用数据 构造一个数组,只需将数据作为逗号分隔的列表包含在构造函数或文字符号中。

语法

var exampleArray = new Array(length);             // Create an array using the Array constructor.
var exampleArray = [];                            // Create a new array literal.

例题

// Initialize an empty array
var myArray = new Array();                        // myArray will be empty--have a length of 0.
var myOtherArray = [];                            // Literal notation
var myLongArray = new Array(100);                 // length is 100.
alert(myLongArray[47]);                           // will alert "undefined"

// Initializing new arrays with values
var myFilledArray = new Array("this", "and", "the", "other");
var myIntegerArray = new Array(202, 53, 12, 0);
var myOtherFilledArray = ["more", "like", "this"];
var mySmallArrayOfDecimals = [0.1, 0.4];

// Constructing multidimensional arrays
var row1 = [1, 2, 3];
var row2 = [4, 5, 6];
var row3 = [7, 8, 9];
var array3by3 = [row1, row2, row3];               // array3by3 is now a multidimensional array
alert(array3by3[1][1]);                           // alerts 5

// Constructing arrays of objects
var object1 = {
    "myName" : "object1"
};
var object2 = {
    "myName" : "object2"
};
var object3 = {
    "myName" : "object3"
};

var arrayOfObjects = [object1, object2, object3]; // arrayOfObjects now contains the objects
alert(arrayOfObjects[1].myName);                  // alerts "object2".

数组属性

JavaScript Array全局对象只有几个默认属性,其中大部分都是从Object继承的。Array自己定义的唯一默认属性是length属性。

长度

每个数组都有一个length属性,对应于数组中元素的总数。空数组的length为 0。如果一个数组有元素,那么最后一个元素的索引将是length–1,因为 JavaScript 数组是零索引的。

语法

exampleArray.length;

例题

var myArray = ["this", "is", "an", "array"];
alert(myArray.length);              // alerts 4
alert(myArray[myArray.length]);     // alerts "undefined"; there is no such element with index of 4.
alert(myArray[myArray.length -1]);  // alerts "array".

数组方法

对象提供了几个非常有用的方法来操作数组。

concat( )

Array.concat()方法将两个(或更多)数组连接成一个新数组。它不会影响目标阵列。

语法

var newArray = exampleArray.concat(array1, array2, ..., arrayN);

例题

var array1 = ["bunnies", "kittens", "puppies"];
var array2 = ["velociraptors"];
var array3 = array1.concat(array2); // array3 will now contain ["bunnies", "kittens", "puppies", "velociraptors"].

索引( )

Array.indexOf()提供了一种简单的方法来搜索数组中的特定值。如果提供的元素出现在数组中,此方法将返回其第一个匹配项的索引。如果提供的元素不在数组中,此方法将返回–1。

该方法还采用一个可选的整数参数,该参数被解释为搜索的起始索引。正整数被解释为从数组开头开始的起始索引,负整数被解释为从数组结尾开始的起始索引。在这两种情况下,搜索都进行到数组的末尾。

语法

exampleArray.indexOf(element, startingIndex);

例题

var myArray = ["bunnies", "kittens", "puppies", "ponies", "polar bear cubs"];
var indexOfKittens = myArray.indexOf("kittens");             // 1
var indexOfVelociraptors = myArray.indexOf("velociraptors"); // -1
var indexOfPuppies = myArray.indexOf("puppies", -1);         // this will return -1 because puppies is not contained within the subset specified by the starting index.
var indexOfPuppiesForReal = myArray.indexOf("puppies", -3);  // 2

join( )

Array.join() 将一个数组的所有元素串联成一个字符串。该方法采用一个可选参数作为分隔符;如果未指定参数,则默认使用逗号。

语法

exampleArray.join(separator);

例题

var myArray = ["bunnies", "kittens", "puppies", "ponies", "polar bear cubs"];
var strCuteThings = myArray.join();                          // strCuteThings now contains "bunnies,kittens,puppies,ponies,polar bear cubs"
var sentence = myArray.join(" are cute and ") + "are cute!"; // sentence now contains "bunnies are cute and kittens are cute and puppies are cute and ponies are cute and polar bear cubs are cute!"

lastIndexOf( )

Array.lastIndexOf()提供了一个有用的方法来查找一个数组中最后一个出现的元素。如果找到该元素,此方法将返回最后一个匹配项的索引;如果没有找到该元素,它将返回 1。

Array.indexOf()一样,这个方法采用一个可选的整数参数作为起始索引。正整数被解释为从开始的起始索引,搜索将进行到结尾。负整数将被解释为从末尾开始的索引,搜索将继续到数组的开头。

语法

exampleArray.lastIndexOf(element, startingIndex);

例题

var myArray = ["bunnies", "kittens", "puppies", "ponies", "polar bear cubs", "bunnies", "kittens"];
alert(myArray.lastIndexOf("bunnies"));                       // alerts 5
alert(myArray.lastIndexOf("ponies", 4));                     // alerts -1
alert(myArray.lastIndexOf("kittens", -2));                   // alerts 1

pop( )

Array.pop() 从数组中移除最后一个元素,并返回该元素。对比Array.shift()

语法

exampleArray.pop();

例题

var myArray = ["bunnies", "kittens", "puppies", "ponies", "polar bear cubs"];
var cubs = myArray.pop(); // myArray is now ["bunnies", "kittens", "puppies", "ponies"] and cubs is now "polar bear cubs"

push( )

Array.push()方法提供了一种向数组添加新元素的方法。新元素需要使用文字符号来指定。方法返回数组的新长度。

请注意,一个数组可以被推入另一个数组。这是创建多维数组的一种方式。(要将数组连接在一起,请使用Array.concat()。)

语法

exampleArray.push(element1, element2, ..., elementN);

例题

var myArray = ["bunnies", "kittens", "puppies", "ponies", "polar bear cubs"];
var newLength = myArray.push("chinchillas", "sugar gliders"); // newLength is 7
var predators = ["velociraptors", "wolves"];
newLength = myArray.push(predators);                          // newLength is 8 (not 9!)
alert(myArray[7]);                                            // alerts "velociraptors,wolves"
alert(myArray[7][1]);                                         // alerts "wolves"

反向()

Array.reverse() 反转数组中元素的顺序。它“就地”执行此操作,这意味着当调用此方法时,数组本身会反转。

语法

exampleArray.reverse();

例题

var myArray = ["bunnies", "kittens", "puppies", "ponies", "polar bear cubs"];
myArray.reverse(); // myArray is now  ["polar bear cubs", "ponies", "puppies", "kittens", "bunnies"];

shift( )

Array.shift() 从数组中移除第一个元素并返回该项。对比Array.unshift()Array.pop()

语法

exampleArray.shift();

例题

var myArray = ["bunnies", "kittens", "puppies", "ponies", "polar bear cubs"];
var bunnies = myArray.shift(); // myArray is now ["kittens", "puppies", "ponies", "polar bear cubs"] and bunnies is now "bunnies"

slice( )

方法根据指定的索引范围从数组中移除元素。该方法将两个整数作为参数:开始索引和结束索引。该方法将选择从起始索引开始到结束索引结束的元素(但是它将而不是包括由结束索引指定的元素)。正整数被解释为从数组开始的索引,负整数被解释为从数组结束的索引。方法将指定的元素作为新数组返回,并且不影响目标数组。

语法

exampleArray.slice(startingIndex, endingIndex);

例题

var myArray = ["bunnies", "kittens", "puppies", "ponies", "polar bear cubs"];
var commonPets = myArray.slice(1, 2); // commonPets is now ["kittens", "puppies"];
var carnivores = myArray.slice(4, 5); // carnivores is now ["polar bear cubs"];

sort( )

Array.sort() 将对一个数组的元素进行排序。如果没有指定参数,数组将按字母升序排序。

如果字母升序没有用,该方法可以将排序函数作为参数,或者作为内联函数,或者作为命名函数。排序函数必须期望两个参数,ab(将是数组元素),并比较它们。如果a是比b更低的索引,该函数应该返回 1。如果两者相等,函数应该返回 0。如果b是一个比a更低的索引,函数应该返回 1。这使得对数组进行复杂的排序成为可能。

但是,请注意,在大型数组上运行复杂的排序函数可能会很昂贵,因为Array.sort()将在每个元素上运行该函数。“复杂”和“大”的确切构成取决于您的具体情况,因此如果您发现您的应用运行缓慢,并且它正在进行复杂排序,这是寻找优化性能机会的合理位置。

语法

exampleArray.sort(sortMethod);

例题

var myArray = ["bunnies", "kittens", "puppies", "ponies", "polar bear cubs"];
myArray.sort(); // myArray is now ["bunnies", "kittens", "polar bear cubs", "ponies", "puppies"]
var arrayOfIntegers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
arrayOfIntegers.sort(); // arrayOfIntegers is now [0, 1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9], which is possibly not useful
function mySortingFunction(a, b) {
    return a-b;
}
arrayOfIntegers.sort(mySortingFunction); // arrayOfIntegers is now [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]

拼接()

Array.splice() 提供了操作数组的通用方法。该方法可以在数组中添加或移除元素,并且目标数组被就地操作。该方法将以数组对象的形式返回从数组中移除的任何元素。

该方法有三个参数。第一个参数是一个整数,是操作的起始索引,并且是必需的。和其他以索引为参数的Array方法一样,正整数被解释为从数组开头开始的索引,负整数被解释为从数组结尾开始计数的索引。

第二个参数是必需的,可以是 0 或正整数。它指定要从数组中移除多少个元素。如果设置为 0,则不会删除任何元素。

第三个参数是可选的,它是通过串联添加到数组中的新项。这可以是一个文字列表或另一个数组对象。

语法

exampleArray.splice(startingIndex, numberOfElements, item1, item2, item3, ..., itemN);

或者

exampleArray.splice(startingIndex, numberOfElements, arrayOfNewItems);

例题

var myArray = ["bunnies", "kittens", "puppies", "ponies", "polar bear cubs"];
var arrayOfPuppies = myArray.splice(2, 1);         // This just removes "puppies" from myArray. arrayOfPuppies  is now ["puppies"]
var arrayOfKittens = myArray.splice(1, 1, "maru"); // arrayOfKittens is now ["kittens"] and myArray is now ["bunnies", "maru", "ponies", "polar bear cubs"];
var arrayOfPredators = ["velociraptors", "wolves"];
myArray.splice(2, 0, arrayOfPredators);            // myArray is now ["bunnies", "maru", "ponies", ["velociraptors", "wolves"], "polar bear cubs"]

toString( )

Array.toString()将数组的所有元素连接成一个逗号分隔的字符串。这个方法和不带参数调用Array.join()完全一样。

语法

exampleArray.toString();

例题

var myArray = ["bunnies", "kittens", "puppies", "ponies", "polar bear cubs"];
var strCuteThings = myArray.toString(); // strCuteThings now contains "bunnies,kittens,puppies,ponies,polar bear cubs"

unshift( )

Array.unshift() 将指定的元素添加到一个数组的开头。对比Array.shift()Array.push()

语法

exampleArray.unshift(element1, element2, ..., elementN);

例题

var myArray = ["bunnies", "kittens", "puppies", "ponies", "polar bear cubs"];
myArray.unshift("chinchillas"); // myArray is now ["chinchillas", "bunnies", "kittens", "puppies", "ponies", "polar bear cubs"]

布尔代数学体系的

JavaScript Boolean是布尔(真或假)数据类型的对象包装器。JavaScript 也有布尔原语truefalse;如果您试图访问一个布尔原语的Boolean属性或方法,JavaScript 会悄悄地用一个Boolean对象包装它,以提供所请求的功能。

您可以使用Boolean对象作为构造函数来构造一个新的布尔对象,它带有一个可选参数。如果参数为0null""falseundefinedNaN(或者根本不指定),那么新的Boolean对象的 value 属性将为 false。否则,它将被设置为 true(即使在指定字符串值为“false”的情况下)。请记住,当用构造函数创建Boolean对象时,结果是一个对象,而不是原始值。注意,从实际角度来看,您很少需要创建Boolean对象,因为 JavaScript 会为您包装布尔原语。

语法

var exampleBoolean = new Boolean(parameter);

例题

var newBooleanObject = new Boolean(false);  // create new Boolean object with a value of false
var newBooleanPrimitive = false;            // create new primitive boolean
alert(typeof newBooleanObject);             // will alert "object"
alert(typeof newBooleanPrimitive);          // will alert "boolean"

// This alert will happen because objects cast to true, even though the value is false
if (newBooleanObject) {
  alert("newBooleanObject is true!");
}

// This alert will not happen because it is a primitive set to false.
if (newBooleanPrimitive) {
  alert("newBooleanPrimitive is true!");
}

布尔方法

布尔对象为确定它们的值提供了两种有用的方法。这些最常用于铸造的情况。

toString( )

Boolean.toString()方法将返回一个表示布尔值的字符串(“真”或“假”)。

语法

exampleBoolean.toString();

例题

var myBoolean = false;              // Create a new boolean with the false primitive
alert(myBoolean.toString());        // alerts "false"
var myOtherBoolean = new Boolean("false");
alert(myOtherBoolean.toString());   // alerts "true"
var myLiteralBoolean = true;
alert(myLiteralBoolean.toString()); // alerts "true"

valueOf( )

Boolean.valueOf()方法将布尔值作为原始值返回(与Boolean.toString()相反,后者将布尔值作为字符串返回)。

语法

exampleBoolean.valueOf();

例题

var newBool = false                 // Create a new boolean with the false primitive
var otherBool = newBool.valueOf();
alert(typeof otherBool);            // will alert "boolean"
alert(otherBool.toString());        // will alert "false"

日期

Date对象用于返回日期,日期的形式由它的许多构造函数生成。对象的每个实例都是一个以毫秒为单位的值,并且相对于 1970 年 1 月 1 日的零点。所有正值都表示 1970 年 1 月 1 日之后的日期。所有负值都表示 1970 年 1 月 1 日之前的日期。

在对Date对象使用参数时,请遵循年、月、日、小时、分钟、秒和毫秒的顺序。浏览器在 UTC()方法中将时间存储为毫秒值。UTC,也简称为世界时,代表协调世界时,实质上是从 1970 年 1 月 1 日开始的格林威治标准时间。返回给浏览器的时间取自您的操作系统,所以请记住,不正确的系统时间可能会导致您在使用Date对象时遇到任何问题。

对象很复杂,但是提供了许多方便的方法来管理、比较和格式化日期。稍加练习,它们会变得更容易使用。

使用Date对象作为构造器来构造日期。构造函数接受一个可选参数,该参数可以是一个整数(表示自 1970 年 1 月 1 日零时起的毫秒数,可以是正数、负数或 0)、一个根据Date.parse()中的要求格式化的信息字符串或一组表示所需年、月、日等的整数。如果没有指定参数,则返回当前日期。

语法

var exampleDate = new Date();
var exampleDate = new Date(milliseconds);
var exampleDate = new Date(string);
var exampleDate = new Date(year, month, day, hours, minutes, seconds, milliseconds);

例子

var currentDate = new Date();  // No arguments so the current date is returned
alert(currentDate.getDate() ); // alerts the current day of the month (see getDate() method, below).

日期方法

对象提供了几个非常有用的方法来操作和比较日期。

get data()键

Date.getDate()方法用于返回Date对象的月份日期。

语法

exampleDate.getDate();

例子

var dateObject = new Date();   // Create new date object with today's date.
alert(dateObject.getDate());   // alerts today's day of the month.

getDay( )

Date.getDay()方法用于返回Date对象的星期几。天是零索引的,星期日是 0。

语法

exampleDate.getDay();

例子

var dateObject = new Date();     // Create new date object for today
alert(dateObject.getDay());      // Alerts today's day of the week

getFullYear( )

Date.getFullYear()方法用于返回代表年份的四位数,这是从您的操作系统指定的日期获取的,并由Date对象使用。

语法

exampleDate.getFullYear();

例子

var dateObject = new Date();     // Create new date object with today's date
alert(dateObject.getFullYear()); // Alerts the year

getHours( )

Date.getHours()方法用于返回一天中的小时,该小时取自操作系统指定的日期,并由Date对象使用。返回的小时基于 24 小时制。

语法

exampleDate.getHours();

例子

var dateObject = new Date();     // Create new date object with today's date
alert(dateObject.getHours());    // Alerts the current hour.

getMilliseconds( )

Date.getMilliseconds()方法用于返回一个 0 到 999 之间的数字,代表Date对象的毫秒数。

语法

exampleDate.getMilliseconds();

例子

var dateObject = new Date();         // Create new date object as of that moment
alert(dateObject.getMilliseconds()); // Alerts the milliseconds at the time when dateObject was formed.

get inuts()

Date.getMinutes()方法用于返回一天中的分钟。

语法

exampleDate.getMinutes();

例子

var dateObject = new Date();         // Create new date object with today's date
alert(dateObject.getMinutes());      // Alerts the minutes of the hour

getMonth( )

Date.getMonth()方法用于返回一年中的月份,该月份取自操作系统指定的日期,并由Date对象使用。返回值是月份的数字表示,而不是月份名称。月份是零索引的,因此一月是 0,十二月是 11。

语法

exampleDate.getMonth();

例子

var dateObject = new Date();         // Create new date object with today's date
alert(dateObject.getMonth());        // Alerts the month

getSeconds()

Date.getSeconds()方法 用于返回分钟的秒,该秒取自操作系统指定的日期,由Date对象使用。

语法

exampleDate.getSeconds();

例子

var dateObject = new Date();         // Create new date object with today's date
alert(dateObject.getSeconds());      // Alerts the seconds

getTime()

Date.getTime()方法 用于返回自 1970 年 1 月 1 日以来经过的毫秒数,该时间取自您的操作系统指定的日期,并由Date对象使用。返回值以毫秒为单位。

这似乎是一种奇怪而武断的表示日期的方式,但是它使日期数学化(比较、加法和减法等)。)相当容易。

语法

exampleDate.getTime();

例子

var dateObject = new Date();           // Create new date object with today's date
alert(dateObject.getTime());           // Alerts the milliseconds since January 1, 1970

getTimezoneOffset()

Date.getTimezoneOffset()方法 用于返回 GMT 与客户端计算机上的时间之差对应的分钟数,这是您的操作系统指定的时间,由Date对象使用。

语法

exampleDate.getTimezoneOffset();

例子

var dateObject = new Date();           // Create new date object with today's date
alert(dateObject.getTimezoneOffset()); // Alerts the timezone offset

getutcdate()

Date.getUTCDate()方法 用于返回操作系统指定的一个月中的某一天,用 UTC 时间或不含本地时差的时间表示。返回值是 1 到 31 之间的值。UTC 代表协调世界时,与格林威治标准时间(GMT)相同。

语法

exampleDate.getUTCDate();

例子

var dateObject = new Date();           // Create new date object with today's date
alert(dateObject.getUTCDate());        // alerts the day of the month

getUTCDay()

Date.getUTCDay()方法 用于返回一周中的某一天(0 到 6,表示一周中的七天),由操作系统指定,用 UTC 时间或不含本地时差的时间表示。返回值是 0 到 6 之间的值。UTC 代表协调世界时,与格林威治标准时间(GMT)相同。

语法

exampleDate.getUTCDay();

例子

var dateObject = new Date();            // Create new date object with today's date
alert(dateObject.getUTCDay());          // Alerts the day of the week

getUTCFullYear()

Date.getUTCFullYear()方法 用于返回操作系统指定的当前年份的四位数表示,用 UTC 时间或不含本地时差的时间表示。UTC 代表协调世界时,与格林威治标准时间(GMT)相同。

语法

exampleDate.getUTCFullYear();

例子

var dateObject = new Date();            // Create new date object with today's date
alert(dateObject.getUTCFullYear());     // Alerts the current year

getUTCHours()

Date.getUTCHours()方法用于返回一天中的小时,用 UTC 时间或不带本地时差的时间表示。返回的值是从 00 到 23 的值,使用 24 小时制。UTC 代表协调世界时,与格林威治标准时间(GMT)相同。

语法

exampleDate.getUTCHours();

例子

var dateObject = new Date();            // Create new date object with today's date
alert(dateObject.getUTCHours());        // Alerts the current hour of the day

getUTCMilliseconds()

Date.getUTCMilliseconds()方法用于返回当前日期的毫秒部分,由 0 到 999(包括 0 和 999)之间的整数表示,由您的操作系统指定,以 UTC 时间或不含本地偏移量的时间表示。UTC 代表协调世界时,与格林威治标准时间(GMT)相同。

语法

exampleDate.getUTCMilliseconds();

例子

var dateObject = new Date();            // Create new date object with today's date
alert(dateObject.getUTCMilliseconds()); // Alerts the milliseconds of dateObject

getUTCMinutes()

Date.getUTCMinutes()方法用于返回过去一小时的分钟数,由您的操作系统指定,以 UTC 时间或不含本地时差的时间表示。返回值是 0 到 59 之间的值。UTC 代表协调世界时,与格林威治标准时间(GMT)相同。

语法

exampleDate.getUTCMinutes();

例子

var dateObject = new Date();       // Create new date object with today's date
alert(dateObject.getUTCMinutes()); // Alerts the UTC minutes of the hour.

getUTCMonth()

Date.getUTCMonth()方法用于返回操作系统指定的一年中的月份,用 UTC 时间或不含本地时差的时间表示。返回值是一个从 0 到 11 的值。UTC 代表协调世界时,与格林威治标准时间(GMT)相同。

语法

exampleDate.getUTCMonth();

例子

var dateObject = new Date();       // Create new date object with today's date
alert(dateObject.getUTCMonth());   // Alerts the month.

getUTCSeconds()

Date.getUTCSeconds()方法用于返回过去最后一分钟的秒数,该时间取自操作系统指定的时间,以 UTC 时间或不含本地偏移量的时间表示。返回值是 0 到 59 之间的值。UTC 代表世界时间坐标,与格林威治标准时间(GMT)相同。

语法

exampleDate.getUTCSeconds();

例子

var dateObject = new Date();       // Create new date object with today's date
alert(dateObject.getUTCSeconds()); // Alerts the seconds past the minute

解析( )

Date.parse()方法用于返回从给定日期date(见语法)到当前日期的时间,以毫秒为单位。当前日期取自客户端操作系统。指定的完整日期必须采用以下格式:

DayOfWeek, dayOfMonth MM YYYY HH:MM:SS TimeZoneOffset

以上是toGMTString()方法遵循的格式。

语法

exampleDate.parse(date);

例子

alert("The number of milliseconds from January 1st, 1970 to Sun, Jan 3 1999 10:15:30 is : " + Date.parse('Sun, Jan 3 1999 10:15:30') ); // Will alert "The number of milliseconds from January 1st, 1970 to Sun, Jan 3 1999 10:15:30 is : 915387330000"

setDate( )

Date.setDate()方法用于设置Date对象的日期属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。

day参数应该在 1 到 31 之间(包括 1 和 31)。如果提供 0,则该月中的某一天属性将设置为上个月的最后一个小时。如果提供-1,则该月中的某一天属性将设置为上个月最后一个小时之前的一个小时。如果一个月有 30 天,提供 32 天会将该月的日期属性设置为下个月的第二天。如果一个月有 31 天,那么提供 32 天会将该月的日期属性设置为下个月的第一天。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setDate(day);

例子

var dateObject = new Date(); // Create new date object with today's date
dateObject.setDate(3);       // Changes the day from whatever it currently is to 3 (Wednesday)
alert(dateObject.getDate()); // Alerts 3

setFullYear()

Date.setFullYear()方法用于设置Date对象的 year 属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。

此外,通过提供可选的monthday参数,该方法可用于设置Date对象的月和日属性。

year参数应该是用零填充的四位数年份。允许负值。

month参数应该是 0 到 11 之间的整数。如果提供-1,month 属性将设置为上一年的最后一个月。如果提供 12,则 month 属性将设置为下一年的第一个月,如果提供 13,则 month 属性将设置为下一年的第二个月。

day参数应该是 1 到 31 之间的整数。如果您提供 0,日期将设置为上个月的最后一天。如果提供-1,日期将设置为上个月最后一天的前一天。如果一个月有 31 天,则提供 32 天将得到下个月的第一天,如果一个月有 30 天,则提供 32 天将得到下个月的第二天。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setFullYear(year, month, day);

例子

var dateObject = new Date();     // Create new date object with today's date
dateObject.setFullYear(1999);    // Set year to 1999
alert(dateObject.getFullYear()); // Alerts 1999.

setHours()

Date.setHours()方法用于设置Date对象的时间属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。

此外,通过提供可选的minutessecondsmilliseconds参数,该方法可用于设置Date对象的分、秒和毫秒属性。

hours参数应该是 0 到 23 之间的整数。如果提供 1,hours 属性将设置为前一天的最后一个小时,如果提供 24,hours 属性将设置为第二天的第一个小时。

minutes参数应该是 0 到 59 之间的整数。如果提供 1,分钟属性将设置为前一小时的最后一分钟,如果提供 60,分钟属性将设置为下一小时的第一分钟。

seconds参数应该是 0 到 59 之间的整数。如果提供 1,seconds 属性将设置为上一分钟的最后一秒,如果提供 60,seconds 属性将设置为下一分钟的第一秒。

milliseconds参数应该是 0 到 999 之间的整数。如果提供 1,毫秒属性将设置为前一秒的最后一毫秒。如果提供 1000,毫秒属性将设置为下一秒的第一毫秒。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setHours(hour, minutes, seconds, milliseconds);

例子

var dateObject = new Date();     // Create new date object with today's date
dateObject.setHours(11);         // Set the hour to 11am
alert(dateObject.getHours());    // Alerts 11.

setMilliseconds()

Date.setMilliseconds()方法用于设置Date对象的毫秒属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。

milliseconds参数应该是 0 到 999 之间的整数。如果提供 1,毫秒属性将设置为前一秒的最后一毫秒。如果提供 1000,毫秒属性将设置为下一秒的第一毫秒。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setMilliseconds(milliseconds);

例子

var dateObject = new Date();         // Create new date object with today's date
dateObject.setMilliseconds(300);     // Sets the milliseconds value to 300
alert(dateObject.getMilliseconds()); // Alerts 300

setMinutes()

Date.setMinutes()方法用于设置Date对象的分钟属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。您还可以通过向该方法提供可选的secondsmilliseconds参数来设置Date对象的秒和毫秒。

minutes参数应该是 0 到 59 之间的整数。如果提供 1,分钟属性将设置为前一小时的最后一分钟,如果提供 60,分钟属性将设置为下一小时的第一分钟。

seconds参数应该是 0 到 59 之间的整数。如果提供 1,seconds 属性将设置为上一分钟的最后一秒,如果提供 60,seconds 属性将设置为下一分钟的第一秒。

milliseconds参数应该是 0 到 999 之间的整数。如果提供 1,毫秒属性将设置为前一秒的最后一毫秒。如果提供 1000,毫秒属性将设置为下一秒的第一毫秒。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setMinutes(minutes, seconds, milliseconds);

例子

var dateObject = new Date();         // Create new date object with today's date
dateObject.setMinutes(40);           // Sets the minutes value to 40
alert(dateObject.getMinutes());      // Alerts 40

集月()

Date.setMonth()方法用于设置Date对象的月份属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。此外,您可以通过提供可选的day参数来设置Date对象的 day 属性。

month参数应该是 0 到 11 之间的整数。如果提供-1,month 属性将设置为上一年的最后一个月。如果提供 12,则 month 属性将设置为下一年的第一个月,如果提供 13,则 month 属性将设置为下一年的第二个月。

day参数应该是 1 到 31 之间的整数。如果您提供 0,日期将设置为上个月的最后一天。如果提供-1,日期将设置为上个月最后一天的前一天。如果一个月有 31 天,提供 32 天将得到下个月的第一天,如果一个月有 32 天,提供 32 天将得到下个月的第二天。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setMonth(month, day);

例子

var dateObject = new Date();    // Create new date object with today's date
dateObject.setMonth(4);         // Sets the month value to 4
alert(dateObject.getMonth());   // Alerts 4

setSeconds()

Date.setSeconds()方法用于设置Date对象的秒属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。此外,通过提供一个可选的milliseconds参数,这个方法可以用来设置Date对象的毫秒属性。

seconds参数应该是 0 到 59 之间的整数。如果提供 1,seconds 属性将设置为上一分钟的最后一秒,如果提供 60,seconds 属性将设置为下一分钟的第一秒。

milliseconds参数应该是 0 到 999 之间的整数。如果提供 1,毫秒属性将设置为前一秒的最后一毫秒。如果提供 1000,毫秒属性将设置为下一秒的第一毫秒。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setSeconds(seconds, milliseconds);

例子

var dateObject = new Date();    // Create new date object with today's date
dateObject.setSeconds(11);      // Set seconds to 11
alert(dateObject.getSeconds()); // Alerts 11.

第七个( )

Date.setTime()方法用于设置自 1970 年 1 月 1 日午夜以来的毫秒数。这种方法不会查询系统时钟的日期和时间。

milliseconds参数应该是一个正整数或负整数。然后,Date对象将根据参数计算日、月、年、小时、分钟、秒和毫秒。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setTime(milliseconds);

例子

var dateObject = new Date();        // Create new date object with today's date
dateObject.setTime(-8348438943984); // Subtract 83,848,438,943,984 milliseconds from midnight January 1 1970
alert(dateObject.toDateString());   // Alerts "Sat Jun 13 1705"

setUTCDate()

Date.setUTCDate()方法用于根据通用时间设置Date对象的日期属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。

UTC 代表世界协调时间,由世界时间标准设定。它与格林威治标准时间(GMT)相同。

day参数应该在 1 到 31 之间。如果提供 0,则该月中的某一天属性将设置为上个月的最后一个小时。如果提供-1,则该月中的某一天属性将设置为上个月最后一小时的前一天。如果一个月有 30 天,提供 32 天会将该月的日期属性设置为下个月的第二天。如果一个月有 31 天,则提供 32 天会将该月的日期属性设置为下个月的第一天。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setUTCDate(day);

例子

var dateObject = new Date();        // Create new date object with today's date
dateObject.setUTCDate(3);           // Changes the day from whatever it currently is to 3
alert(dateObject.getUTCDate());     // Alerts 3

setUTCFullYear()

Date.setUTCFullYear()方法用于根据世界时设置Date对象的年份属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。

UTC 代表世界协调时间,由世界时间标准设定。它与格林威治标准时间(GMT)相同。

此外,通过提供可选的monthday参数,该方法可用于设置Date对象的月和日属性。

year参数应该是用零填充的四位数年份。允许负值。

month参数应该是 0 到 11 之间的整数。如果提供-1,month 属性将设置为上一年的最后一个月。如果提供 12,则 month 属性将设置为下一年的第一个月,如果提供 13,则 month 属性将设置为下一年的第二个月。

day参数应该是 1 到 31 之间的整数。如果您提供 0,日期将设置为上个月的最后一天。如果提供-1,日期将设置为上个月最后一天的前一天。如果一个月有 31 天,则提供 32 天将得到下个月的第一天,如果一个月有 30 天,则提供 32 天将得到下个月的第二天。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setUTCFullYear(year, month, day);

例子

var dateObject = new Date();        // Create new date object with today's date
dateObject.setUTCFullYear(1999);    // Set year to 1999
alert(dateObject.getUTCFullYear()); // Alerts 1999.

setUTCHours()

Date.setUTCHours()方法用于根据通用时间设置Date对象的时间属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。

UTC 代表世界协调时间,由世界时间标准设定。它与格林威治标准时间(GMT)相同。

此外,通过提供可选的minutessecondsmilliseconds参数,该方法可用于设置Date对象的分、秒和毫秒属性。

hours参数应该是 0 到 23 之间的整数。如果提供 1,hours 属性将设置为前一天的最后一个小时,如果提供 24,hours 属性将设置为第二天的第一个小时。

minutes参数应该是 0 到 59 之间的整数。如果提供 1,分钟属性将设置为前一小时的最后一分钟,如果提供 60,分钟属性将设置为下一小时的第一分钟。

seconds参数应该是 0 到 59 之间的整数。如果提供 1,seconds 属性将设置为上一分钟的最后一秒,如果提供 60,seconds 属性将设置为下一分钟的第一秒。

milliseconds参数应该是 0 到 999 之间的整数。如果提供 1,毫秒属性将设置为前一秒的最后一毫秒。如果提供 1000,毫秒属性将设置为下一秒的第一毫秒。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setUTCHours(hour, miutes, seconds, milliseconds);

例子

var dateObject = new Date();            // Create new date object with today's date
dateObject.setUTCHours(11);             // Set the hour to 11am
alert(dateObject.getUTCHours());        // Alerts 11.

setUTCMilliseconds()

Date.setUTCMilliseconds()方法用于根据通用时间设置Date对象的毫秒属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。

UTC 代表世界协调时间,由世界时间标准设定。它与格林威治标准时间(GMT)相同。

milliseconds参数应该是 0 到 999 之间的整数。如果提供 1,毫秒属性将设置为前一秒的最后一毫秒。如果提供 1000,毫秒属性将设置为下一秒的第一毫秒。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setUTCMilliseconds(milliseconds);

例子

var dateObject = new Date();            // Create new date object with today's date
dateObject.setUTCMilliseconds(300);     // Sets the milliseconds value to 300
alert(dateObject.getUTCMilliseconds()); // Alerts 300

setUTCMinutes()

Date.setUTCMinutes()方法用于根据通用时间设置Date对象的分钟属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。您还可以通过向该方法提供可选的secondsmilliseconds参数来设置Date对象的秒和毫秒。

UTC 代表世界协调时间,由世界时间标准设定。它与格林威治标准时间(GMT)相同。

minutes参数应该是 0 到 59 之间的整数。如果提供 1,分钟属性将设置为前一小时的最后一分钟,如果提供 60,分钟属性将设置为下一小时的第一分钟。

seconds参数应该是 0 到 59 之间的整数。如果提供 1,seconds 属性将设置为上一分钟的最后一秒,如果提供 60,seconds 属性将设置为下一分钟的第一秒。

milliseconds参数应该是 0 到 999 之间的整数。如果提供 1,毫秒属性将设置为前一秒的最后一毫秒。如果提供 1000,毫秒属性将设置为下一秒的第一毫秒。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setUTCMinutes(minutes, seconds, milliseconds);

例子

var dateObject = new Date();       // Create new date object with today's date
dateObject.setUTCMinutes(40);      // Sets the minutes value to 40
alert(dateObject.getUTCMinutes()); // Alerts 40

setUTCMonth()

Date.setUTCMonth()方法用于根据世界时设置Date对象的月份属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。此外,您可以通过提供可选的day参数来设置Date对象的 day 属性。

UTC 代表世界协调时间,由世界时间标准设定。它与格林威治标准时间(GMT)相同。

month参数应该是 0 到 11 之间的整数。如果提供-1,month 属性将设置为上一年的最后一个月。如果提供 12,则 month 属性将设置为下一年的第一个月,如果提供 13,则 month 属性将设置为下一年的第二个月。

day参数应该是 1 到 31 之间的整数。如果您提供 0,日期将设置为上个月的最后一天。如果提供-1,日期将设置为上个月最后一天的前一天。如果一个月有 31 天,提供 32 天将得到下个月的第一天,如果一个月有 32 天,提供 32 天将得到下个月的第二天。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setUTCMonth(month, day);

例子

var dateObject = new Date();       // Create new date object with today's date
dateObject.setUTCMonth(4);         // Sets the month value to 4
alert(dateObject.getUTCMonth());   // Alerts 4

setUTCSeconds()

Date.setUTCSeconds()方法用于根据通用时间设置Date对象的秒属性。Date对象将使用这个值进行操作。这种方法不会查询系统时钟的日期和时间。此外,通过提供一个可选的milliseconds参数,这个方法可以用来设置Date对象的毫秒属性。

UTC 代表世界协调时间,由世界时间标准设定。它与格林威治标准时间(GMT)相同。

seconds参数应该是 0 到 59 之间的整数。如果提供 1,seconds 属性将设置为上一分钟的最后一秒,如果提供 60,seconds 属性将设置为下一分钟的第一秒。

milliseconds参数应该是 0 到 999 之间的整数。如果提供 1,毫秒属性将设置为前一秒的最后一毫秒。如果提供 1000,毫秒属性将设置为下一秒的第一毫秒。

该方法返回一个整数,表示从Date对象的新值到 1970 年 1 月 1 日午夜之间的毫秒数。

语法

exampleDate.setUTCSeconds(seconds, milliseconds);

例子

var dateObject = new Date();       // Create new date object with today's date
dateObject.setUTCSeconds(11);      // Set seconds to 11
alert(dateObject.getUTCSeconds()); // Alerts 11.

toDateString()

Date.toDateString()方法返回表示Date对象的日期(不是时间)的格式化字符串。格式是三个字母的日缩写后跟 MMM DD YYYY 例如,“2013 年 1 月 26 日,星期日”。

语法

exampleDate.toDateString();

例子

var dateObject = new Date();       // Create new date object with today's date
alert(dateObject.toDateString());  // Alerts the current date.

toisostring()

Date.toISOString()方法返回一个表示Date对象值的字符串,其格式符合 ISO-8601:YYYY-MM-DDTHH:MM:ss . sssz

语法

exampleDate.toISOString();

例子

var dateObject = new Date();       // Create new date object with today's date
alert(dateObject.toISOString());   // Alerts today's date in ISO-8601 format.

toJSON()

方法将返回一个表示 JSON 格式的日期的字符串。JSON 格式与 ISO 格式相同,所以这个方法与调用Date.toISOString()相同。

语法

exampleDate.toJSON();

例子

var dateObject = new Date(); // Create new date object with today's date
alert(dateObject.toJSON());  // Alerts today's date in ISO-8601 format.

toLocaleDateString()

Date.toLocaleDateString()方法用于返回标准格式的日期,该日期取自操作系统指定的日期,由Date对象使用。该值根据主机浏览器、主机操作系统以及可能的用户设置所指定的区域设置约定进行格式化。因此,该方法在不同的浏览器和不同的操作系统中会有不同的表现

语法

exampleDate.toLocaleDateString();

例子

var dateObject = new Date(); // Create new date object with today's date
alert(dateObject.toLocaleDateString()); // Alerts today's date in a formatted string, e.g. "Thursday, September 13, 2012"

托 LocaleTimeString()

Date.toLocaleTimeString()方法用于以标准格式返回Date对象的时间部分,其信息取自操作系统指定的日期,并由Date对象使用。使用由主机浏览器、主机操作系统以及可能的用户设置指定的区域设置约定返回该值。因此,该方法在不同的浏览器和不同的操作系统中会有不同的表现。

语法

exampleDate.toLocaleTimeString();

例子

var dateObject = new Date();            // Create new date object with today's date
alert(dateObject.toLocaleTimeString()); // Alerts the current time, e.g. "09:17:42"

托洛卡斯汀( )

Date.toLocaleString()方法用于以标准格式返回Date对象的值,其信息取自操作系统指定的日期,并由Date对象使用。使用由主机浏览器、主机操作系统以及可能的用户设置指定的区域设置约定返回该值。因此,该方法在不同的浏览器和不同的操作系统中会有不同的表现。

语法

exampleDate.toLocaleString();

例子

var dateObject = new Date(); // Create new date object with today's date
alert(dateObject.toLocaleString()); // Alerts the current time, e.g. "Thu Sep 13 2012 09:17:42 GMT-0700 (Pacific Daylight Time)"

toString()

Date.toString()方法用于将Date对象转换成字符串。

语法

exampleDate.toString();

例子

var dateObject = new Date(); // Create new date object with today's date
alert(dateObject.toString()); // Alerts the current time, e.g. " Thu Sep 13 2012 09:17:42 GMT-0700 (Pacific Daylight Time)"

toTimeString()

Date.toTimeString()方法用于将Date对象的时间部分转换成字符串。

语法

exampleDate.toTimeString();

例子

var dateObject = new Date();      // Create new date object with today's date
alert(dateObject.toTimeString()); // Alerts the current time, e.g. "09:17:42 GMT-0700 (Pacific Daylight Time)"

toUTCString()

Date.toUTCString()方法用于根据通用协调时间将Date对象转换为字符串。

语法

exampleDate.toUTCString();

例子

var dateObject = new Date();      // Create new date object with today's date
alert(dateObject.toUTCString());  // Alerts the current time, e.g. " Thu Sep 13 2012 16:17:42 GMT"

世界协调时( )

Date对象上的其他方法不同,Date.UTC()方法是为了方便而公开的静态方法。因此,您不必为了访问它而创建一个新的Date对象——事实上,您不能从一个Date对象访问它。相反,你可以直接从全局Date对象中访问它。

Date.UTC()方法采用如下一组参数:

  • Year:四位数年份;1900 年以后的任何一年都有效。必选。
  • Month:0 到 11 之间的整数,表示所需月份。必选。
  • Date:1 到 31 之间的一个整数,表示一个月中的某一天。
  • Hours:0 到 23 之间的整数,表示所需的小时。
  • Min:0 到 59 之间的整数,表示所需的分钟数。
  • Sec:从 0 到 59 的整数,表示所需的秒。
  • MS:0 到 999 之间的整数,表示所需的毫秒数。

该方法将返回一个数字,该数字表示指定日期与 1970 年 1 月 1 日午夜之间的毫秒数。

语法

Date.UTC(Year, Month, Date, Hours, Min, Sec, MS);

例子

var intMilliseconds = Date.UTC(2013,01,26); // intMilliseconds is now 1361836800000
var myNewDate = new Date(intMilliseconds);  // Create a new date object
alert(myNewDate.toUTCString()):             // Alerts "Tue, 26 Feb 2013 00:00:00 GMT".

valueOf()

Date.valueOf()方法返回一个Date对象的原始值,即从 UTC 时间 1970 年 1 月 1 日午夜开始的秒数。

语法

exampleDate.valueOf();

例子

var dateObject = new Date(); // Create new date object with today's date
alert(dateObject.valueOf()); // Alerts the milliseconds from midnight January 1 1970, e.g."1347553875570"

数学

Math对象提供了对各种数学属性和函数的访问。与本章介绍的其他对象不同,Math对象不是一个构造函数。你不需要创建一个新的Math对象来使用它;您可以直接使用它的属性和方法。

数学属性

Math 对象有几个对应于常见数学常数的属性:

  • E:欧拉数
  • LN2:2 的自然对数
  • LN10:10 的自然对数
  • LOG2E:E 的以 2 为底的对数
  • LOG10E:E 的以 10 为底的对数
  • PI:圆周率(π)的值
  • SQRT1_2:1/2 的平方根
  • SQRT2:2 的平方根

语法

Math.E;
Math.LN2;
Math.PI;

例题

alert(Math.E);
alert(Math.LN2);
alert(Math.PI);

数学方法

Math对象有几种方法,包括三角函数、舍入和随机化。

abs()

Math.abs()方法用于计算所提供参数的绝对值。出于计算目的,非数字参数被转换为数字。

语法

Math.abs(number);

例题

alert(Math.abs(-1));             // will alert "1"
alert(Math.abs(0.1));            // will alert "0.1"
alert(Math.abs("Math is fun!")); // will alert "NaN"
alert(Math.abs(null));           // will alert "0"

acos()

Math.acos()方法返回所提供参数的反余弦(以弧度为单位)。出于计算目的,非数字参数被转换为数字。

语法

Math.acos(number);

例题

alert(Math.acos(1));              // will alert 0
alert(Math.acos(-1));             // will alert 3.141592653589793
alert(Math.acos(0.1));            // will alert 1.4706289056333368
alert(Math.acos("Math is fun!")); // will alert NaN
alert(Math.acos(null));           // will alert 1.5707963267948966

阿辛( )

Math.asin()方法返回所提供参数的反正弦(以弧度为单位)。出于计算目的,非数字参数被转换为数字。

语法

Math.asin(number);

例题

alert(Math.asin(1));              // will alert 1\. 5707963267948966
alert(Math.asin(-1));             // will alert -1\. 5707963267948966
alert(Math.asin(0.1));            // will alert 0.1001674211615598
alert(Math.asin("Math is fun!")); // will alert NaN
alert(Math.asin(null));           // will alert 0

atan()

Math.atan()方法返回所提供参数的反正切值(以弧度为单位)。出于计算目的,非数字参数被转换为数字。

语法

Math.atan(number);

例题

alert(Math.atan(1));              // will alert 0.7853981633974483
alert(Math.atan(-1));             // will alert 0.7853981633974483
alert(Math.atan(0.1));            // will alert 0.09966865249116204
alert(Math.atan("Math is fun!")); // will alert NaN
alert(Math.atan(null));           // will alert 0

atan2()

Math.atan2()方法返回其两个参数的商的反正切值。该值以介于-PI/2 和 PI/2 之间的弧度返回。出于计算目的,非数字参数被转换为数字。

语法

Math.atan2(num1, num2);

例题

alert(Math.atan2(1, 7));              // will alert 0.1418970546416394
alert(Math.atan2("Math is fun!", 8)); // will alert NaN
alert(Math.atan2(null, null));        // will alert 0

细胞( )

Math.ceil()方法返回上舍入到下一个最接近整数的参数。出于计算目的,非数字参数被转换为数字。对比Math.floor()

语法

Math.ceil(number);

例题

alert(Math.ceil(0.4));                // will alert 1
alert(Math.ceil(-7.9));               // will alert -7
alert(Math.ceil("Math is fun"));      // will alert NaN
alert(Math.ceil("0.1"));              // will alert 1

cos()

Math.cos()方法返回所提供参数的余弦值(以弧度为单位)。出于计算目的,非数字参数被转换为数字。

语法

Math.cos(number);

例题

alert(Math.cos(1));                   // will alert 0.540323058681398
alert(Math.cos(0.1));                 // will alert 0.9950041652780258
alert(Math.cos("Math is fun!"));      // will alert NaN
alert(Math.cos(null));                // will alert 1

exp()

Math.exp()方法接受一个数字参数并返回 e^number 作为结果。

语法

Math.exp(number);

例子

alert(Math.exp(-1));              // will alert 0.36787944117144233

地板( )

Math.floor()方法返回下一个最接近的整数的参数。出于计算目的,非数字参数被转换为数字。对比Math.ceil()

语法

Math.floor(number);

例题

alert(Math.floor(0.4));           // will alert 0
alert(Math.floor(-7.9));          // will alert -8
alert(Math.floor("Math is fun")); // will alert NaN
alert(Math.floor("0.1"));         // will alert 0

日志( )

Math.log()方法将返回参数的自然对数。非数字参数是为了计算而强制转换的。

语法

Math.log(number);

例题

alert(Math.log(2));               // will alert 0.691347185599453
alert(Math.log("0));              // will alert -Infinity
alert(Math.log(-34));             // Will alert NaN

最大( )

Math.max()方法接受一组数字参数,并将返回其中最大的一个。传递一个非数字参数将导致该方法返回NaN。如果没有给定参数,该方法将返回-Infinity。对比Math.min()

语法

Math.max(num1, num2, ..., numN);

例子

alert(Math.max(5, 0, 2, 100, 4, 68490, 4, -1, 234)); // will alert 68490

最小值( )

方法接受一组数字参数,并将返回其中最小的一个。传递一个非数字参数将导致该方法返回NaN。如果没有给定参数,该方法将返回Infinity。对比Math.max()

语法

Math.min(num1, num2, ..., numN);

例子

alert(Math.min(5, 0, 2, 100, 4, 68490, 4, -1, 234)); // will alert -1

功率( )

Math.pow()方法带两个参数numberBasenumberExponent,返回numberBase^numberExponent。非数字参数是为了计算而强制转换的。

语法

Math.pow(numberBase, numberExponent);

例题

alert(Math.pow(10, 10));                 // will alert 10000000000
alert(Math.pow(100, 0.5));               // will alert 10
alert((Math.pow(2, 0.5) == Math.SQRT2)); // will alert true

随机( )

Math.random()方法将返回一个大于或等于 0 且小于 1 的 16 位小数的浮点数。

语法

Math.random();

例题

alert(Math.random());                    // will alert a random number, such as 0.40920510
// Function to generate a random integer between 0 and intMax
function generateRandomInt(intMax) {
    return Math.floor((Math.random() * intMax) + 1);
}
alert(generateRandomInt(100));           // will alert a random integer between 0 and 100.

圆形( )

Math.round()方法接受一个数字参数,并将该数字四舍五入为最接近的整数。出于计算目的,非数字参数将被强制转换。

语法

Math.round(number);

例题

alert(Math.round(1.49));         // will alert 1
alert(Math.round(1.5));          // will alert 2

罪恶( )

Math.sin()方法返回所提供参数的正弦值(以弧度为单位)。出于计算目的,非数字参数被转换为数字。

语法

Math.sin(number);

例题

alert(Math.sin(1));              // will alert 0.8414709848078965
alert(Math.cos(0.1));            // will alert 0.09983341664682815
alert(Math.cos("Math is fun!")); // will alert NaN
alert(Math.cos(null));           // will alert 1

sqrt()

Math.sqrt()方法接受一个数字参数并返回该数字的平方根。负数将返回NaN,非数字参数将被强制转换用于计算。

语法

Math.sqrt(number);

例子

alert(Math.sqrt(100));           // will alert 10

谭( )

Math.tan()方法返回所提供参数的正切值(以弧度为单位)。出于计算目的,非数字参数被转换为数字。

语法

Math.tan(number);

例题

alert(Math.tan(1));               // will alert 1.5574077246549023
alert(Math.tan(0.1));             // will alert 0.10033467208545055
alert(Math.tan("Math is fun!"));  // will alert NaN
alert(Math.tan(null));            // will alert 0

数字

Number对象是数值的包装类。在 JavaScript 中,所有数值都是 64 位浮点数。

数字属性

JavaScript Number对象有几个在数字比较中有用的属性,特别是因为 JavaScript 是弱类型的:

  • MAX_VALUE:JavaScript 中可能的最大数值
  • MIN_VALUE:JavaScript 中可能的最小数值
  • NEGATIVE_INFINITY:负无穷大
  • NaN:特殊“非数字”值(详见第二章)
  • POSITIVE_INFINITY:正无穷大

语法

Number.MAX_VALUE;
Number.MIN_VALUE;

例子

alert(Number.MAX_VALUE);          // will alert 1.7976931348623157e+308

数字方法

toexponentail_)

Number.toExponential()方法返回一个以指数记数法表示数字的字符串。该方法采用一个介于 0 和 20 之间的可选整数参数,该参数表示小数点后的位数;如果省略,该方法将根据需要使用尽可能多的数字来完全表示该数字。

语法

exampleNumber.toExponential(digits);

例题

var myNumber = 4309;
alert(myNumber.toExponential());  // will alert 4.309e+3
alert(myNumber.toExponential(2)); // will alert 4.31e+3

toFixed( )

Number.toFixed()方法返回一个表示十进制数的字符串,并且在可选整数参数指定的小数点后有精确的位数。如果未指定该参数,则该参数被视为 0,并且该数字将被四舍五入为最接近的整数。

语法

exampleNumber.toFixed(places);

例题

var myNumber = 40.29;
alert(myNumber.toFixed());       // will alert 40
alert(myNumber.toFixed(1));      // will alert 40.3
alert(myNumber.toFixed(4));      // will alert 40.2900

最高精度( )

Number.toPrecision()方法返回一个字符串,表示四舍五入到参数指定的有效位数的指定数字。根据需要,结果可以是定点或指数表示法。通常,该参数应该是 1 到 21 之间的一个整数值,尽管这可以根据实现而变化。超出允许范围的参数将引发范围错误。如果没有指定数字参数,该方法只返回数字的字符串表示,相当于调用toString()方法。

语法

exampleNumber.toPrecision(precision);

例题

var myNumber = 40.29;
alert(myNumber.toPrecision());   // alerts 40.29
alert(myNumber.toPrecision(1));  // alerts 4e+1 (which is 40 in exponential notation)
alert(myNumber.toPrecision(10)); // alerts 40.2900000000

toString()

Number.toString()方法返回一个表示数字值的字符串。该方法采用一个可选参数,该参数是一个介于 2 和 26 之间的整数,表示字符串的基数。如果您提供的参数超出了可接受的范围,将会引发异常。如果您根本没有提供基数,则默认值为 10。

语法

exampleNumber.toString(radix);

例题

var myNumber = 17;
alert(myNumber.toString());      // alerts 17
alert(myNumber.toString(2));     // alerts 10001

valueOf()

Number.valueOf()方法将数字的原始值作为数字数据类型返回。这种方法不常用,因为直接赋值是首选。

语法

exampleNumber.valueOf();

例题

var myNumber = 17;
var myOtherNumber = myNumber.valueOf();
var myBetterWay = myNumber;                       // direct assignment
alert(myOtherNumber === myBetterWay);             // Will alert true, because the two are the same

正则表达式

JavaScript RegExp 对象提供了 JavaScript 中正则表达式的实现。正则表达式是搜索和操作文本字符串的强大工具,有自己的语言和方法。本参考资料仅涵盖正则表达式的 JavaScript 实现,并不深入研究如何实际构建它们。

在 JavaScript 中,正则表达式以两种方式之一创建:通过RegExp对象构造函数,或者通过文字。使用构造函数时,表达式中出现的任何字符串都必须进行转义。

语法

var exampleRegExp = new RegExp(regularExpression);
var exampleRegExp = regularExpression;

例题

var myConstructedRegExp = new RegExp("the", "g"); // same as myLiteralRegExp
var myLiteralRegExp = /the/g;                     // search an entire string for "the"

正则表达式属性

正则表达式有几个属性(全局、不区分大小写等。)并且RegExp对象对它们都有匹配的属性。

全球的

RegExp.global属性是一个布尔值,如果已经为正则表达式设置了global标志,则该布尔值被设置为真。

语法

exampleRegExp.global;

例子

var myRegExp = /the/g;
alert(myRegExp.global);     // will alert true

ignoreCase

RegExp.ignoreCase属性是一个布尔值,指示是否为正则表达式设置了忽略大小写标志。

语法

exampleRegExp.ignoreCase;

例子

var myRegExp = /the/i;
alert(myRegExp.ignoreCase); // will alert true

loadIndex

RegExp.lastIndex属性将包含一个整数,表示由RegExp.match()方法或RegExp.test()方法找到的最后一个匹配后的字符位置。仅当全局属性设置为 true 时,才会设置此属性。

语法

exampleRegExp.lastIndex;

例子

var slogan = "Never give up, never surrender!";
var regExp = /never/gi;
while (regExp.test(slogan) === true) {
    alert("Found 'never'; index is now " + regExp.lastIndex);
}

多线

RegExp.multiline属性是一个布尔值,指示是否在正则表达式上设置了多行标志。

语法

exampleRegExp.multiline;

例子

var myRegExp = /never/m;
alert(myRegExp.multiline);  // will alert true

来源

RegExp.source属性包含一个表示正则表达式本身的字符串。

语法

exampleRegExp.source;

例子

var myRegExp = /never/g;
alert(myRegExp.source); // will alert "/never/g" (some browsers will just alert "never" which is equivalent)

正则表达式方法

RegExp对象方法用于在目标上运行正则表达式。

执行( )

RegExp.exec()方法将目标字符串作为参数,并对其运行正则表达式匹配。如果没有找到匹配,该方法返回 null。如果找到匹配项,该方法将停止并返回具有以下属性的数组:

  • 数组中的第一个元素是匹配的文本。
  • 数组中的后续元素是正则表达式中任何匹配括号的内容。
  • 该数组将有一个包含目标字符串的input属性。
  • 该数组将有一个index属性,该属性将包含一个表示匹配子字符串索引的整数。

此外,该方法将更新RegExp对象的lastIndex

如果正则表达式设置了global标志,那么对match()方法的后续调用将继续对字符串进行扫描,返回找到的任何匹配,直到没有匹配为止。

由于迭代的性质,RegExp.exec()通常在循环中使用。

语法

exampleRegExp.exec(target)

例题

var regExp = /never/gi;     // global case-insensitive search for "never"
var slogan = "Never give up, never surrender!";
alert(regExp.exec(slogan)); // will alert "Never"
alert(regExp.exec(slogan)); // will alert "never"
alert(regExp.exec(slogan)); // will alert null

// Demonstrate using a loop: this will alert "Never" and then "never".
var regExp = /never/gi,
      slogan = "Never give up, never surrender!",
      result;
while(result = regExp.exec(slogan)) {
    alert(result);
}

测试( )

RegExp.test()方法将目标字符串作为参数,并在目标字符串上运行正则表达式匹配。如果匹配,则该方法返回 true 否则返回 false。

语法

exampleRegExp.test(target);

例题

var regExp = /never/g;
var slogan = "Never give up, never surrender!";
var otherSlogan = "May the force be with you!";
alert(regExp.test(slogan));      // will alert true
alert(regExp.test(otherSlogan)); // will alert false

线

对象是所有字符串的包装类。每当您访问字符串文字上的String的属性或方法之一时,JavaScript 将在幕后用一个String对象包装该文字,为您提供您所请求的功能。您的文字字符串将保持不变,但看起来拥有一个String对象的所有属性和方法。

很少直接使用String对象;你几乎不会调用使用String作为构造函数来创建一个String对象。创建一个String对象的唯一好处是,因为结果是一个对象,你可以给它其他的属性或方法。

如果需要使用String构造函数创建一个String对象,语法很简单,结果是一个包含所有字符串属性和方法的对象。要访问用于构造对象的实际字符串,请使用valueOf()方法。

语法

var exampleString = new String("desired string");

例题

var myConstructedString = new String("Hi");
var myLiteralString = "Hi";
alert(myConstructedString === myLiteralString);           // will alert false, because literals and objects are different types.
alert(myConstructedString == myLiteralString);            // will alert true, because JavaScript uses the valueOf method in cast comparisons.
alert(myConstructedString.valueOf() === myLiteralString); // will alert true.

弹簧属性

JavaScript 全局String 对象有一些属性,其中大部分是从Object继承的。它为自己定义的唯一属性是length

长度

String.length属性包含一个表示字符串中字符总数的整数。

语法

exampleString.length;

例子

var myString = "Hi";
alert(myString.length);        // will alert 2

字符串方法

JavaScript 提供了几种非常有用的操作字符串的方法,包括转换成数组和正则表达式扫描。

夏拉特( )

String.charAt()方法期望一个整数作为参数,表示字符串中的一个索引,并将返回出现在该索引处的字符。字符串中的字符从左到右从零开始索引。如果指定的索引超出了字符串的长度,该方法将返回一个空字符串。

语法

exampleString.charAt(index);

例子

var myString = "Hello World";
alert(myString.charAt(6));     // will alert "W"--spaces are characters too!

charCodeAt()

String.charCodeAt()方法期望一个整数作为参数,表示字符串中的一个索引,并将返回该索引处字符的 Unicode 数值。字符串中的字符从左到右从零开始索引。如果指定的索引超出了字符串的长度,该方法将返回一个空字符串。

语法

exampleString.charCodeAt(index);

例子

var myString = "Hello World";
alert(myString.charCodeAt(6)); // will alert "87"

concat()

String.concat()方法组合一个或多个字符串(作为参数提供)并返回结果。原始字符串不会改变。

语法

exampleString.concat(string1, string2, ..., stringN);

例子

var myString = "Hello";
var myOtherString = "World";
var mySpace = " ";
var myFullMessage = myString.concat(mySpace, myOtherString, "!");
alert(myFullMessage)               // will alert Hello World!

fromCharCode()

String.fromCharCode()方法将任意数量的 Unicode 字符代码作为参数。它将代码转换成相关的字符,并返回结果字符串。

注意这是String对象的静态方法,所以可以直接调用;您不需要首先创建一个String对象。

语法

exampleString.fromCharCode(charCode1, charCode2, ..., charCodeN);

例子

alert(String.fromCharCode(87));    // will alert W

索引()

String.indexOf()方法 将一个子串作为参数,从该子串的开头开始搜索。如果找到,该方法返回第一个匹配项的第一个字符的索引;否则,它返回 1。

该方法还可以采用一个可选的整数参数来指定搜索的起始索引。如果未指定,默认值为 0。如果指定的索引超出了字符串的界限,该方法将返回 1。

语法

exampleString.indexOf(substring, startIndex);

例题

var myString = "Never give up, never surrender!";
alert(myString.indexOf("up"));     // will alert 11
alert(myString.indexOf("up", 12)); // will alert -1

lastIndexOf()

String.lastIndexOf()方法将一个子串作为参数,并从末尾开始搜索该子串。如果找到子串,该方法返回索引;否则,它返回 1。

该方法还可以采用一个可选的整数参数来指定起始索引。如果未指定索引,则该方法默认为字符串的长度。

语法

exampleString.lastIndexOf(substring, startIndex);

例子

var myString = "Never give up, never surrender!";
alert(myString.lastIndexOf("er"); // will alert 28.

匹配( )

String.match()方法将正则表达式对象作为参数,然后在字符串上运行该正则表达式。该方法返回一个匹配数组;如果没有设置全局标志,那么只返回第一个匹配。如果不匹配,该方法返回 null。

语法

exampleString.match(regexp);

例题

var regExp = /never/gi,
      slogan = "Never give up, never surrender!",
      otherSlogan = "May the force be with you!";
alert(slogan.match(regExp));      // will alert "Never, never"
alert(otherSlogan.match(regExp)); // will alert "null"

替换( )

JavaScript String.replace()方法提供了一种方法来搜索给定模式的字符串,并用给定的子字符串替换它。搜索参数可以是正则表达式或字符串,替换可以是字符串或函数。方法返回修改后的字符串,原始字符串和参数字符串不变。

如果您提供一个函数作为替换参数,该函数将在每次匹配时执行。匹配的子字符串将被函数的输出替换。该功能将按顺序提供以下参数:

  • match:匹配的子串
  • paren1, paren2, ..., parenN:正则表达式中任何匹配括号匹配的子字符串(如果有)
  • offset:匹配的子串在字符串中的索引
  • string:被搜索的字符串

函数可以作为命名函数或内联函数提供。

语法

exampleString.replace(searchParam, replaceParam);

例题

// Simple find and replace
var slogan = "May the force be with you!";
var newSlogan = slogan.replace("force", "Force"); // newSlogan is now "May the Force be with you!"

// Using a regular expression to search and a function to replace
var slogan = "Never give up, never surrender!",
      regExp = /never/gi,
      newSlogan;
function sarrisify(matchedString) {
    if (matchedString === "Never") {
        return "Always";
    }
    if (matchedString === "never") {
        return "always";
    }
}
newSlogan = slogan.replace(regExp, sarrisify);    // newSlogan is now "Always give up, always surrender!"

搜索( )

String.search()方法将子字符串搜索的正则表达式作为参数,并在字符串上执行搜索。如果找到子字符串,该方法将返回一个整数,该整数表示子字符串第一次出现的起始位置的索引。如果找不到子字符串,该方法返回 1。如果您提供了一个不是正则表达式的参数,JavaScript 将尝试对它进行强制转换,就像您使用RegExp构造函数创建它一样。(有关创建正则表达式的解释,请参见“RegExp”一节。)

语法

exampleString.search(regexp);

例子

var slogan = "Never give up, never surrender!",
      regexp = /up/i;
alert(slogan.search(regexp));                     // will alert 11

切片( )

String.slice()方法提供了一种基于字符索引从更大的字符串中提取子字符串的方法。该方法采用两个参数:

  • startIndex:表示子串起始索引的整数。允许负整数,表示从字符串末尾开始计数的索引。
  • endIndex:表示子串结束索引的整数。该参数是可选的;如果省略,该方法将提取到目标字符串的末尾。允许负整数,表示从字符串末尾开始的索引。请注意,该索引处的字符是返回的切片中包含的而不是

方法返回指定的子字符串。目标字符串不变。对比String.substr()

语法

exampleString.slice(startIndex, endIndex);

例子

var slogan = "Never give up, never surrender!";
var mySlice = slogan.slice(1, 5); // mySlice is now "ever"

拆分( )

String.split()方法提供了一种将字符串转换成数组的方法。该方法将表示分隔符的字符串作为参数。该方法搜索整个目标字符串,并沿着分隔符(不包括在新子字符串中)的每个出现处对其进行拆分。然后将子字符串按顺序放入一个数组中,这就是该方法返回的内容。如果没有提供分隔符,则整个字符串作为数组中的第一个元素返回。

该方法还带有一个可选的limit参数,它是一个整数,表示返回数组中元素的最大数量。如果省略参数,则返回所有元素。

目标字符串不会改变。

语法

exampleString.split(delimiter, limit);

例子

var slogan = "Never give up, never surrender!";
var arrWords = slogan.split(" "); // split the slogan along the spaces, resulting in the array ["Never", "give", "up,", "never", "surrender!"]

substr()

String.substr()方法提供了一种基于起始索引和长度从更大的字符串中提取子字符串的方法。该方法有两个参数:startIndex,它是一个表示子串开始的整数,和length,它是一个表示新的子串的期望长度的整数。

startIndex参数可以是正整数或负整数,也可以是 0。如果是正整数,表示从字符串开始的索引;如果是负整数,则表示从字符串末尾开始的索引。

如果省略了length参数,该方法将返回从指定的起始索引开始的子字符串,一直到目标字符串的末尾。如果length参数指定的子字符串比目标字符串中可用的子字符串长,该方法将返回到目标字符串末尾的子字符串。

语法

exampleString.substr(startIndex, length);

例题

var slogan = "Never give up, never surrender!";
var subString = slogan.substr(15, 5);     // subString is now "never"
var newSlogan = slogan.substr(15);        // newSlogan is now "never surrender!"

子字符串( )

String.substring()方法提供了一种基于字符索引从更大的字符串中提取子字符串的方法。该方法采用两个参数:

  • startIndex:表示子串起始索引的整数。不允许使用负整数,这将导致该方法返回空字符串。
  • endIndex:表示子串结束索引的整数。该参数是可选的;如果省略,该方法将提取到目标字符串的末尾。允许负整数,表示从字符串末尾开始的索引。请注意,此索引处的字符不包括在返回的子字符串中。

方法返回指定的子字符串。目标字符串不变。对比String.slice()

语法

exampleString.substring(startIndex, endIndex);

例子

var slogan = "Never give up, never surrender!";
var mySubstring = slogan.substring(1, 5); // mySubstring is now "ever"

toLowerCase()

String.toLowerCase()方法返回所有字符都转换成小写的目标字符串。目标字符串本身不受影响。

语法

exampleString.toLowerCase();

例子

var slogan = "Never give up, never surrender!";
alert(slogan.toLowerCase());              // will alert "never give up, never surrender!"

toUpperCase()

String.toUpperCase()方法返回所有字符都转换成大写的目标字符串。目标字符串本身不受影响。

语法

exampleString.toUpperCase();

例子

var slogan = "Never give up, never surrender!";
alert(slogan.toUpperCase()); // will alert "NEVER GIVE UP, NEVER SURRENDER!"

修剪( )

String.trim()方法返回删除了所有前导和尾随空格的目标字符串。原始目标字符串不变。

语法

exampleString.trim();

例子

var myString = "   hello world   ";
alert(myString.trim());      // will alert "hello world"

trimLeft()

String.trimLeft()方法返回删除了所有前导空格的目标字符串。原始目标字符串不变。

语法

exampleString.trimLeft ();

例子

var myString = "   hello world   ";
alert(myString.trimLeft());  // will alert "hello world   "

trimRight()

String.trimRight()方法返回删除了所有尾随空格的目标字符串。原始目标字符串不变。

语法

exampleString.trimRight();

例子

var myString = "   hello world   ";
alert(myString.trimRight()); // will alert "   hello world"

杂项全局变量和函数

本节涵盖了 JavaScript 全局范围内存在的各种变量和函数。其中许多并不知名,但可能非常有用。

变量

JavaScript 在全局范围内提供了一些重要的变量。

无穷大

Infinity是代表无穷大的数字。这个和Number.POSITIVE_INFINITY没什么区别。

语法

Infinity

例子

alert(Infinity == Number.POSITIVE_INFINITY); // will alert true

数据

JSON 是一个全局对象,它收集了创建和读取 JSON 格式数据的相关方法。这里就不详细介绍 JSON 了;有关 JSON 的更多信息,请参见www.json.org

JSON.parse()

JSON.parse()方法解析一个 JSON 字符串,然后重新创建并返回它所代表的对象。该方法接受一个 JSON 字符串参数。如果字符串没有解析为有效的 JSON,该方法将抛出语法错误异常。

该方法还采用一个可选的转换函数。transformation 函数提供了一种检查 JSON 键/值对的方法,并在需要时提供不同的值。该函数有两个参数,keyvalue,并且只返回一个值,该值将被用作JSON对象中的值。如果函数返回undefined或者什么都不返回,那么这个键就会从 JSON 对象中删除。

请注意,将按顺序对对象中的每个键/值对调用转换函数。然后,一旦处理完所有的键/值对,就再次调用翻译函数,并给出空字符串作为键和重构的对象本身。此时,您可以进一步修改对象,或者按原样返回(如果您没有正确处理最后一步,翻译将会失败)。

翻译函数有时用于将值从字符串转换为对象——例如,如果一个值是一个格式类似于日期的字符串,翻译器实际上可以基于该字符串创建一个Date对象并返回它。当以这种方式使用时,该函数有时被称为 reviver

语法

JSON.parse(jsonString, translator);

例题

var myJsonString = '{"one" : 1, "two" : 2, "three" : 3}';
myObject = JSON.parse(myJsonString);
alert(myObject.one);                  // will alert 1

function myNumeralTranslator(key, value) {
    if (key === "one") {
        return "I";
    } else if (key === "two") {
        return "II";
    } else if (key === "three") {
        return "III"
    } else {
        return value;
    }
}
myNumeralObject = JSON.parse(myJsonString, myNumeralTranslator);
alert(myNumeralObject.two);           // Will alert "II"

在这个例子中,我们创建了一个简单的 JSON 格式的字符串,包含三个键/值对。首先,我们将它还原为一个对象,然后我们创建一个转换函数,将整数转换为罗马数字,然后我们使用它将相同的字符串还原为不同的对象。

var purchaseJsonString = '{"type" : "gift", "method" : "cash", "date" : "2013-01-28T05:08:11.873Z"}';
function revivePurchase(key, value) {
    if (key === "date") {
        return new Date(value);
    } else {
        return value;
    }
}
var myPurchase = JSON.parse(purchaseJsonString, revivePurchase);
alert(myPurchase.method);             // will alert "cash";
alert(myPurchase.date.toUTCString()); // will alert "Mon, 28 Jan 2013 :05:08:11 GMT"

在本例中,我们创建了一个 JSON 格式的字符串,其中包含一些购买信息,包括一个包含日期格式字符串的值。然后,我们构建一个简单的 reviver 函数来查找日期,并用该值创建一个新的Date对象。

JSON.stringify()

JSON.stringify()方法将一个对象作为参数,并返回相应的 JSON 字符串。

该方法还可以接受一个可选的filter参数,该参数可以用来过滤 JSON 字符串中包含的键/值对。参数可以是数组或函数。

如果筛选器参数是一个数组,那么成员应该表示将包含在字符串中的键。不在过滤器数组中的键不会包含在 JSON 字符串中。

如果filter是一个函数,它将被作为参数传递键和值,并且应该返回该键的期望值。如果返回值是undefined或者什么都没有,那么这个键就不包含在 JSON 字符串中。

语法

JSON.stringify(object, filter);

例题

var myObject  = {"a" : 1, "b" : 2, "foo" : "bar"},
     arrFilter = ["a", "foo"];
function myFilter(key, value) {
    if (key === "a") {
        return undefined;
    } else {
        return value;
    }
}

var firstString = JSON.stringify(myObject ); // stringifies entire object
var secondString = JSON.stringify(myObject, arrFilter);         // Leaves out "b", only stringifies "a" and "foo"
var thirdString = JSON.stringify(myObject, myFilter);           // leaves out "a", only stringifies "b" and "foo"
alert(firstString + "\n" + secondString + "\n" + thirdString);  // Compare all three results.

NaN属性是 JavaScript 中特殊的“非数字”属性。这个属性只在 JavaScript 内部使用,不应该在比较中使用。要确定某个东西是否是NaN,使用在接下来的“函数”一节中讨论的isNaN()方法。

未定义的

undefined属性是 JavaScript 中 undefined 的原始值。以下事情被认为是undefined:

  • 任何已定义但未赋值的变量
  • 试图计算这种未定义变量的语句
  • 没有显式返回值的函数(通过不使用return关键字或通过逻辑)

参见第二章对undefined的深入讨论,以及如何确定事物在 JavaScript 中是否未定义。

请注意,在 JavaScript 的早期实现中,该属性是可写的,允许脚本覆盖该值。在 ECMAScript 5 标准(对应于 JavaScript 1.8.5)中,该属性是只读的。覆盖如此重要的值被认为是不好的做法。

功能

JavaScript 还在全局范围内提供了几个方便的函数。

decorator()、encore()、decorator component()、encore component()

这些方法为编码和解码整个 URIs 及其单个组件(例如,查询字符串)提供了便利的例程。

编码方法不编码字母数字字符或字符- _。!“* ”(和)。另外,encodeURI()方法不会对保留字符进行编码;, / ?😡 & = + $和#。所有其他字符将被替换为一个、两个、三个或四个转义序列,表示该字符的 UTF-8 编码。

语法

decodeURI(encodedURI);
encodeURI(unencodedURI);
decodeURIComponent(encodedURI);
encodeURIComponent(unencodedURI);

例子

var myURI = ' http://www.apress.com/?foo=bar&a="something new"';
alert(encodeURI(myURI)); // will alert http://www.apress.com?foo=bar&a=%22something%20new%22

eval()

eval()方法接受一个字符串参数,并将其解析为 JavaScript。该字符串可以包含对存在于调用eval()的作用域中的对象的引用。

有关 eval 及其注意事项和替代方案的深入讨论,请参见第二章。

语法

eval(target);

例子

var myString = "10 + 2";
var myResult = eval(myString);
alert(myResult);         // will alert 12

isFinite()

isFinite()方法检查参数,如果它是一个有限的数,则返回 true,否则返回 false。这是一个方便的例行检查有限的数字,而不是使用等式。

注意:如果参数是NaN,该方法也会返回 false。

语法

isFinite(target);

例题

alert(isFinite(243988));                   // will alert true
alert(isFinite(Number.NEGATIVE_INFINITY)); // will alert false

isNaN( )

isNaN()方法检查参数,如果是NaN则返回 true,否则返回 false。

实际上,这个方法首先将参数强制为一个数值,如果它还不是一个数值的话。然后它检查结果数值是否等于NaN。这种行为偶尔会让 JavaScript 新手出错,他们认为该方法可以用来确定某个东西是否是数值。

语法

isNaN(target);

例题

alert(isNaN(10));   // will alert false
alert(isNaN("10")); // will alert false even though strings are not numbers; "10" coerces to 10 which is not equal to NaN
alert(isNaN(""));   // will alert false
alert(isNaN(NaN));  // will alert true

解析浮点( )

parseFloat()方法解析提供的参数并试图返回包含在字符串开头的十进制数值。基本上,该方法从字符串的开头开始并构建数字。如果遇到不是数字、指数、小数点或符号(+或-)的字符,该方法将停止并返回它已经创建的任何数字。如果字符串的第一个字符不能转换成数字(空格除外),该方法返回NaN

语法

parseFloat(target);

例题

var myString = "This will return NaN",
    mySecondString = "10.27 this will return 10.27",
    myThirdString = "Even though this has 10.27 in it, it will return NaN",
    myFourthString = "10.27 50.20 this will return only the first number, 10.27";
alert(parseFloat(myString) + "\n" + parseFloat(mySecondString) + "\n" + parseFloat(myThirdString) + "\n" + parseFloat(myFourthString));

parseInt()

parseInt()方法解析参数并返回包含在字符串开头的数字整数值。方法从字符串的开头开始,并生成数字。如果遇到不是数字、指数、小数点或符号(+或–)的字符,该方法将停止并返回已创建的数字。如果字符串的第一个字符不能转换成数字(空格除外),这个方法将返回NaN

语法

parseInt(target);

例题

var myString = "This will return NaN",
    mySecondString = "10.97 this will return 10",
    myThirdString = "Even though this has 10.27 in it, it will return NaN",
    myFourthString = "10.97 50.20 this will return only the first number, 10";
alert(parseInt(myString) + "\n" + parseInt(mySecondString) + "\n" + parseInt(myThirdString) + "\n" + parseInt(myFourthString));

摘要

在本章中,我们已经介绍了几个全局对象的属性和方法以及它们的使用方法:

  • 您可以使用文字符号或使用Array对象作为构造函数来创建数组。
  • JavaScript 会在需要时将字符串和布尔文字与其关联的对象进行静默包装。
  • 您可以使用相关的全局对象作为构造函数来创建字符串和布尔值,但几乎没有必要这样做。
  • Date对象提供了大量用于操作日期的属性和方法。
  • JavaScript 通过它的RegExp全局对象拥有正则表达式的全功能实现。
  • Math对象提供了许多与数学相关的静态属性和方法。

此外,我们还介绍了其他几个全局函数和变量,包括:JSON 对象,它提供了创建和操作 JSON 序列化的功能。

在下一章中,我们将为 JavaScript 中所有可用的控制语句提供类似的参考,并提供它们的用法示例。

六、JavaScript 控制语句参考

介绍

正如我们在第二章中讨论的,JavaScript 拥有你所期望的类似 C 语言的常见控制语句:

  • do循环
  • forfor/in循环
  • while 循环往复
  • if-else条件句
  • switch条件句

此外,JavaScript 提供了管理循环迭代的方法,完全打破循环,甚至限制循环的范围。

在这一章中,我们将按照字母顺序提供所有 JavaScript 控制语句的可靠参考。有关这些声明的详细讨论,请参见第二章。

打破

break语句终止当前循环或当前labelswitch语句。该语句带一个可选的label,它对应于要中断的循环的label

语法

break label;

例子

// Set up a loop that would ordinarily alert 0 through 10, but instead breaks at 2.
var i = 0;
while (i <= 10) {
    alert(i);
    if (i === 2) {
        break;
    } else {
        i++
    }
}

继续

continue语句停止循环的当前迭代,并继续下一次迭代。该语句使用一个可选的label来中断循环的label

continue语句只能在for循环、for/in循环、while、和do/while循环中使用。如果在一个for循环中使用,循环将跳回它的增量表达式,执行它,然后检查是否应该继续。

如果在for/in循环中使用,循环将进行到下一场,并从那里继续循环。

如果在whiledo/while循环中使用,循环将立即重新测试条件,并根据结果决定是否继续。

image 明智地使用continue。很容易编写使用难以阅读和维护的continue的代码。事实上,看到风格指南明确指出应该完全避免continue并不罕见。

语法

continue label

例子

var i;
for (i = 0; i < 11; i++) {
    if (i < 4) continue;
    alert(i);
    if (i < 7) continue;
    alert(10 * i);
}

在这个例子中,我们创建了一个从 0 到 10 迭代变量i的循环(关于for循环的细节稍后提供)。如果i < 4,我们什么都不做。一旦i为 5 或更高,我们就开始提醒它的值。而一旦i > 6,我们就开始戒备10 * i。因此,该脚本将依次发出警报,4、5、6、7、70、8、80、9、90、10 和 100。

这是一个有点做作的例子,但是它确实说明了continue的一个常用模式:分层测试。在这种情况下,我们对循环迭代器的值进行分层简单检查,但是分层测试可以是对任何会随着特定循环的进展而改变的东西进行的任何测试。

当然,这个例子可以重写,这样它就根本不用continue了:

var i;
for (i = 0; i < 11; i++) {
    if (i > 3) {
        alert(i);
    }
    if (i > 6) {
        alert(10 * i);
    }
}

任何一种方式都是有效的。

do/while

do/while循环创建一个循环,并在结束时进行条件测试。因为条件是在每个循环结束时计算的,所以循环至少会执行一次。

语法

do {
    // things
} while (conditional);

例子

// A way to alert only odd numbers
var i = 1;
do {
    alerti);
    i = i + 2;
} while (i <= 10);

赞成和赞成/反对

在 JavaScript 中,for循环可以设置为forfor/in循环。

在一个基本的for循环中,for语句有三个参数:

  • 一个initializer,在循环第一次开始时运行一次。
  • 一个conditional,每次循环执行时都会对其进行测试。如果评估为true,则执行循环;如果是false,循环终止,控制转到for循环后的下一条语句。
  • 一个expression,在每个循环结束时执行。

初始化器中声明的任何变量都与for语句具有相同的作用域。

语法

for (initializer, conditional, expression) {
    // do things
}

例子

// Another way to alert odd numbers
for (var i = 1; i <= 10; i = i +2) {
    alert(i);
}

赞成/反对

一个for/in循环遍历一个对象,提供对每个属性的访问。for语句有两个参数,propertyobject。这提供了一种方便的方法来枚举对象,正如在第二章中所讨论的。

语法

for (property in object) {
    // Do things
}

例子

// All-purpose enumeration loop
var testObject = {
    property1 : "this is a test object.",
    property2 : 1,
    arrIntegers : [1, 2, 3, 4],
    boolIsTrue : true
}

var strAlert = "";
for (var thing in testObject) {
    strAlert += thing + ": " + testObject[thing] + "\n";
}
alert(strAlert);

此示例将在一个警报中枚举我们的testObject的所有属性:

9781430246299_Fig06-01.jpg

如果

if语句为 JavaScript 提供了标准的逻辑流控制。一个if语句评估它的参数,如果该参数为真,该语句将执行条件代码。

一个if语句后面可以跟一个else语句,如果参数的值为false,这个语句就会执行。一个else语句可以类似地跟随着一个if语句,允许条件链。

语法

if (condition) {
    // conditional code, executed if condition is true
}

if (condition) {
    // conditional code to be executed if condition is true
} else {
    // conditional code to be executed if condition is false
}

if (condition1) {
    // conditional code to be executed if condition1 is true
} else if (condition2) {
    // conditional code to be executed if condition2 is true
} else {
    // conditional code to be executed if both condition1 and condition2 are false.
}

例子

// Play with random numbers
var myNumber = Math.floor((Math.random() * 100) + 1); // Generate a random number from 1 to 100
if (myNumber <= 10) {
    alert("number is less than 10");
} else if (myNumber <=50) {
    alert("number is less than 50");
} else {
    alert("Number is frighteningly large.");
}

标签

label语句将一个标识符与一个特定的语句相关联,该语句可以使用breakcontinue来引用。

标签在 JavaScript 中被认为是不好的做法,因为它们使代码难以阅读。通常,您可以使用命名函数来代替标签。

语法

label:
    statement

例子

outerloop:
for (var i =0; i < 5; i++) {
    innerloop:
    for (var j = 0; j < 5; j++) {
        if ((i == 2) && (j == 2)) {
            continue outerloop; // skip when both indices are 2
        } else {
            alert(i  + ", " + j);
        }
    }
}

返回

return 语句用于指定函数的返回值。该值可以是任何有效的 JavaScript 数据类型:字符串、布尔值、数组、函数、日期、正则表达式等。

语法

return value;

例子

// Trivial function to test whether a value is less than ten or simply too large to understand.
function testValue(intValue) {
    if (intValue < = 10) {
        return "value is less than ten";
    } else {
        return "value is terrifyingly large.";
    }
}
var niceNumber = 5,
    scaryNumber = 20909239;

alert(testValue(niceNumber));  // will alert "value is less than ten"
alert(testValue(scaryNumber)); // will alert "value is terrifyingly large"

开关/外壳

基于给定表达式的多个值,语句提供了一种提供不同条件的便捷方式。

一个switch语句计算一个表达式,然后搜索与结果相关联的case。如果找到一个case,则执行相关的条件代码。

此外,每个条件代码块可以在末尾包含一个可选的break语句。如果存在,switch语句将立即结束;如果没有,switch语句将继续搜索匹配的case语句。

语法

switch (expression) {
    case value1:
        conditional1
        break;
    case value2:
        conditional2
        break;
    case value3:
        conditional3
        break;
    default
        default conditional
}

例子

switch(booze) {
    case "tequila":
        alert("Margarita time!");
        break;
    case "vodka":
        alert("Mr. Bond, is that you?");
        break;
    case "scotch":
        alert("Aye.");
        break;
    default:l
        alert("But why is the rum gone?");
}

在这个例子中,我们打开了一直流行的booze变量;根据它的值,我们将得到一个适当的警报。

这是一个开启文学子类别的全功能示例:

var leftBehind = "religious";
var dogstar = "apocalyptic";
var kinglear = "shakespeare";

function defineSubGenre(strType) {
    var strResult = "";
    switch (strType) {
        case "religious":
            strResult += "religious ";
        case "apolcalyptic":
            strResult += "apocalyptic ";
        case "science fiction":
            strResult += "science ";
        default:
            strResult += "fiction";
    }
    return strResult;
}

alert(defineSubGenre(leftBehind)); // will alert "religious apocalyptic science fiction"
alert(defineSubGenre(dogstar));    // will alert "apocalyptic science fiction"
alert(defineSubGenre(kinglear));   // will alert "fiction"

在这个例子中,我们演示了没有break语句的switch语句的使用。这适用于越来越普遍的包含彼此的情况:小说包含科幻小说的子类;科幻小说包含启示录小说这一子类;启示录科幻小说包含宗教启示录科幻小说这一子类。

switch语句构建一个return字符串,函数返回该字符串。我们尝试了三本不同的书:一本是“宗教”(提醒“宗教启示录科幻小说”),一本是“启示录”(提醒“启示录科幻小说”),还有一本是“莎士比亚”(只提醒“小说”)。

正在…

while语句创建一个循环,每当指定的表达式计算结果为true时,该循环将继续执行。表达式在循环的每次迭代之前进行计算。(与do. . .while循环相比,条件测试在最后。)

语法

while (expression) {
    // code to execute each time
}

例子

// alert the integers 1 through 10
var i = 0;
while (i < 11) {
    alert(i);
    i++;
}

with 语句修改给定代码块的作用域链。回想一下,如果您访问一个变量,JavaScript 会检查它是否是在直接作用域内定义的。如果找不到变量,JavaScript 会检查包含变量的范围,依此类推,直到全局范围。with语句将指定的对象添加到作用域链的头部,确保在给定代码块中发生的所有作用域查找中都能搜索到该对象。

尽管最初是为了方便起见才包含在语言中的,但with语句被认为是不好的做法,因为它会使代码难以阅读和维护,事实上在 ECMAScript 5 严格模式中是被禁止的。如果您打算使用with语句,请记住在指定代码块中发生的每个范围查找都将首先检查指定的对象,因此您应该尝试限制以下内容:

  • 将托管代码块中的查找范围扩大到指定的对象
  • 对象的复杂性

限制这两者将有助于提高代码的效率。

语法

with (object) {
    // statements with scope limited to object
}

例子

alert(Math.PI); // will alert the value of PI.
// Limit the scope to just Math
with(Math) {
    alert(PI);  // will alert the value of PI.
    alert(cos(PI));
}

摘要

在这一章中,我们已经讨论了 JavaScript 的控制语句,它用于指导程序的逻辑流程:

  • JavaScript 的控制语句类似于其他类 C 语言中的控制语句。
  • JavaScript 有两个条件流控制语句:if语句和switch语句。
  • JavaScript 有四种不同的循环方法:do. . .whileforfor-inwhile
  • 您可以用breakcontinue语句修改循环执行。
  • with语句修改了作用域链,但被认为是不好的做法。
  • JavaScript 有label语句的概念,但是它们也被认为是不好的实践。

在下一章,我们将提供 JavaScript 操作符的参考,包括赋值操作符、算术操作符和比较操作符。

七、JavaScript 运算符参考

在 JavaScript 中,运算符对表达式执行运算。运算符所运算的表达式被称为操作数。JavaScript 支持一元运算符(处理一个表达式的运算符,如 increment 运算符)、二元运算符(需要两个表达式的运算符,如大多数数学运算符)和一个三元运算符(需要三个表达式)。

JavaScript 中的运算符可以分为七大类:

  • 赋值:给变量赋值
  • 比较:比较数值
  • 算术:执行基本算术——加、减、取模等。
  • 按位:根据二进制表示修改操作数
  • 逻辑:逻辑结构,如 AND 和 OR
  • String :修改字符串(此类别只包含一个运算符)
  • 杂项:不属于其他类别的剩余运营商的总括组

在本参考资料中,我们将依次介绍这些类别。

赋值运算符

JavaScript 赋值运算符用于根据右操作数的值给左操作数赋值,最简单的例子是基本赋值运算符=:

x = 1;
strLocation = "California";
boolSuccess = false;

JavaScript 还有其他几个赋值操作符,它们是其他操作的简写。这些简写操作符,在表 7-1 中列出,提供了一种编写更简洁代码的方法。

表 7-1。速记数学运算符

速记操作员 等效表达式
operand1 += operand2 operand1 = operand1 + operand2
operand1 -= operand2 operand1 = operand1 - operand2
operand1 *= operand2 operand1 = operand1 * operand2
operand1 /= operand2 operand1 = operand1 / operand2

例题

var num1 = 5,
num2 = 9;
num1 += num2; // num1 is now 13
num1 -= num2; // num1 (which was 13) is now 5 again
num1 *= num2; // num1 is now 45
num1 /= num2; // num1 is now 5 again

注意,如果两个操作数中有一个是字符串,那么速记操作符+=将执行字符串连接,而不是算术加法。如果另一个操作数不是字符串,它将首先被转换为字符串。有关详细信息,请参阅本章后面的“字符串运算符”。

JavaScript 还支持几种简写的二元运算符,在表 7-2 中列出。二元运算符通过修改组成它们的位来对它们的操作数进行运算。有关二元运算符的完整讨论,请参阅本章后面的“按位运算符”一节。

表 7-2。速记按位运算符

速记操作员 等效表达式
operand1 %= operand2 operand1 = operand1 % operand2
operand1 ^= operand2 operand1 = operand1 ^ operand2
operand1 <<= operand2 operand1 = operand1 << operand2
operand1 >>= operand2 operand1 = operand1 >> operand2
operand1 >>> operand2 operand1 = operand1 >>> operand2
operand1 &#124;= operand2 operand1 = operand1 &#124; operand2

例子

var num1 = 100,
    num2 = 50;
num1 %= num2; // num1 is now 0

比较运算符

比较运算符用于比较两个操作数。因为 JavaScript 是弱类型的,所以它有两种不同的比较:严格的和强制的。

严格比较中,操作符比较操作数的值和它们的类型。如果两者都不匹配,比较返回false。例如,严格比较true === "true"的结果是false,因为true是一个布尔值,它与"true"是不同的类型,后者是一个字符串。

强制比较中,操作符在比较它们的值之前将操作数转换为相同的类型。这种转换过程,被称为铸造强制,遵循 ECMA-262 标准中定义的非常具体的算法,所以结果是完全可以预测的——只要你熟悉这些算法。如果你不是,结果可能是违反直觉的。例如:

if ("true") {
    alert("true" == true);  // will alert false
    alert("true" == false); // will alert false
}

这个例子将触发两个警报,它们将是false,即使它们周围的if语句必须计算为true,警报才会发生。

为了避免混淆,在编写 JavaScript 时尽可能使用严格的比较被认为是一种好的做法,当没有强制转换发生时,或者当强制转换发生但将产生已知的理想结果时,使用强制比较。例如,根据定义,对象上的toString()方法只返回一个字符串,因此,如果您正在与一个字符串文字进行比较,则没有必要进行严格的比较,因为转换永远不会发生:

// toString only ever returns a string, so no need for strict comparison with a string literal
if (myObject.toString() == "string literal") {
    // (do stuff)
}
// The variable testVar might change types, so a strict comparison is a good idea
if (myObject.toString() === testVar) {
    // (do stuff)
}

关于 JavaScript 使用的强制算法的细节,参见第一章。

严格比较

使用严格比较运算符时,JavaScript 会比较操作数的值和类型。两个严格比较运算符是严格相等运算符(===),如果两个操作数的值和类型相等,则返回true,以及严格不等式运算符(!==),如果两个操作数的类型或值不同,则返回true

语法

operand1 === operand2
operand1 !== operand2

例题

var boolOperand1 = true,
    boolOperand2 = false,
    intOperand1 = 1,
    intOperand2 = 2,
    strOperand1 = "1",
    strOperand2 = "2";

alert(boolOperand1 === boolOperand2); // will alert false
alert(boolOperand1 !== boolOperand2); // will alert true
alert(intOperand1 !== intOperand2);   // will alert true
alert(intOperand1 === strOperand1);   // will alert false
alert(strOperand1 === strOperand2);   // will alert false

强制比较

当进行强制比较 s 时,JavaScript 首先将操作数转换为相同的数据类型,然后再比较它们的值(更多信息请参见表 7-3 )。关于 JavaScript 如何执行类型强制的细节,参见第一章。

表 7-3。强制比较运算符

操作 句法 返回
Equality operand1 == operand2 true如果操作数的值相同。如果两个操作数都是对象,并且这两个对象引用内存中的同一个对象,那么这个操作符返回true
不平等 operand1 != operand2 true如果操作数的值不相同。如果两个操作数都是对象,那么如果它们引用内存中不同的对象,这个操作符将返回true
大于 operand1 > operand2 true如果operand1大于operand2
不到 operand1 < operand2 true如果operand1小于operand2
大于或等于 operand1 >= operand2 true如果operand1大于等于operand2
小于或等于 operand1 <= operand2 true如果operand1小于或等于operand2

例题

var strOperand1 = "1",
    intOperand1 = 1,
    boolOperand1 = true;
alert(strOperand1 == intOperand1);  // will alert true
alert(intOperand1 == boolOperand1); // will alert true
alert(strOperand1 == boolOperand1); // will alert true

算术运算符

JavaScript 有一组基本的算术运算符,在表 7-4 中列出,它们对操作数执行指定的算术运算。

表 7-4。算术运算符

操作 句法 返回
添加 operand1 + operand2 operand1operand2之和。
减法 operand1 - operand2 operand1operand2的区别。
增加 operand1 * operand2 运算 1 和运算 2 的乘积。
分开 operand1 / operand2 操作数 1 除以操作数 2 的商。
系数 operand1 % operand2 ;返回operand1operand2
Increment by 1 operand1++(或++operand) operand1++返回递增前operand1的值,而++operand1返回递增后operand1的值。
Decrement by 1 operand1--(或--operand) operand1--返回operand1递减前的值,--operand1返回operand1递减后的值。
Negative value - operand1 operand1的负值。operand1保持不变。
Positive value + operand1 operand1的正值。operand1保持不变。

请注意,JavaScript 中的所有算术运算符都将尝试对非数字操作数执行类型强制,这可能会产生非直观的结果。参见第一章了解更多关于 JavaScript 类型强制规则的信息。

例题

var intOperand1 = 1,
    intOperand2 = 2,
    boolOperand1 = true;
alert(intOperand1 + intOperand2);  // will alert 3
alert(intOperand1 + boolOperand1); // will alert 2
var testResult = intOperand1++;
alert(testResult);                 // will alert 1
alert(intOperand1);                // will alert 2
testResult = ++intOperand2;
alert(testResult);                 // will alert 3
alert(intOperand2);                // will alert 3

按位运算符

JavaScript 的按位操作符接受整数操作数,并基于它们的 32 位表示对它们执行操作。非整数操作数首先被强制(见第一章)。尽管按位运算符是在一个位级别上执行的,但它们返回的是整数。

关于二进制数的一点知识

JavaScript 的按位运算符,见表 7-5 ,都将其操作数转换为有符号的 32 位整数。一般来说,32 位整数的最高有效位可以在左边,从左到右递减;也可以在右边,从右到左递减。前者被称为大端记数法,后者被称为小端记数法。这些术语的起源是乔纳森·斯威夫特的小说格列佛游记,讲述了小人国(其居民在小端敲碎半熟鸡蛋)和布莱费斯克(其居民在大端敲碎半熟鸡蛋)这两个敌对王国之间持续紧张的故事。

表 7-5。按位运算符

操作 句法 细节
按位 AND operand1 & operand2 比较两个操作数中的每个位位置,并返回通过在两个操作数都为 1 的每个位位置放置 1 而形成的新数字。
按位或 operand1 &#124; operand2 比较两个操作数中的每个位位置,并返回一个新数,该新数是通过在任一操作数为 1 的每个位位置放置 1 而形成的。
按位异或 operand1 ^ operand2 比较两个操作数中的每个位位置,并返回一个新数字,该数字是通过在每个操作数(但不是两个操作数)都为 1 的位置放置 1 而形成的。
按位非 ∼ operand1 返回通过反转operand1的位形成的新数字。
按位左移 operand1 << operand2 返回一个新数字,它是通过将operand1’s位向左移动operand2指定的位数而形成的,在右边补零。
按位符号传播右移 operand1 >> operand2 返回通过将operand1’的位向右移动operand2指定的位数而形成的新数字。移出的位会被丢弃而不是换行(从而保留符号位)。
按位零填充右移位 operand1 >>> operand2 返回通过将operand1的位向右移动operand2指定的位数而形成的新数字。被移出的位被丢弃。零从左边开始填充。

JavaScript 的 32 位整数是大端的,所以最大的位总是在左边,而二进制补码,意味着负数是它们的正值的逐位反转,加 1。

作为一个实际的例子,让我们将数字 5 转换成一个 JavaScript 二进制数。在二进制中,5 表示为 101。在大端格式中,最高有效位在左边。101 只有 3 位;要使它成为 32 位数字,我们必须用零填充它:

00000000000000000000000000000101

这是数字 5 的 32 位大端表示法。JavaScript 先将操作数转换成这种格式,然后再对它们执行任何位运算。

逐位运算在 JavaScript 中相当少见。一些有用的例子,见第四章。

逻辑运算符

JavaScript 有一组逻辑运算符,列在表 7-6 中,用于实现布尔逻辑。通常,这些语句与流控制语句结合使用。

表 7-6。逻辑运算符

操作 句法 返回
Logical AND operand1 && operand2 true如果两个操作数都为真。
Logical OR operand1 &#124;&#124; operand2 true如果任一操作数为真。
Logical NOT !operand1 如果操作数为真,则返回true

例题

if (expression1 && expression2) {
    // Do something if both expression1 and expression2 are true
}
var boolFalse = !true;
alert(boolFalse); // will alert false
if (expression1 && !expression2) {
    // Do something if expression1 is true and expression2 is false
}

字符串运算符

JavaScript 有一个字符串操作符:连接操作符+。该运算符返回连接在一起的两个操作数。如果其中一个操作数不是字符串,它将被该运算符转换为字符串。

例子

var strString1 = "Hello",
    strString2 = "World";
alert(strString1 + " " + strString2); // will alert Hello World

杂项运算符

既然我们已经介绍了操作符的主要类别,我们还剩下一些不太合适但仍然很重要的操作符。这些操作符包括 JavaScript 中唯一的三元操作符——条件操作符,以及一些用于检查数据类型和操作对象及其属性的有用操作符。

条件运算符

条件运算符是 JavaScript 唯一的三元运算符。它提供了if / then / else语句的简写。

一些 JavaScript 风格指南建议避免条件操作符,支持显式的if / then / else语句,以使代码更具可读性。

语法

conditional ? trueOperand : falseOperand // If conditional is true, evaluate trueOperand, otherwise evaluate falseOperand

例子

(3 > 4) ? alert("Three is greater than four") : alert("Three is not greater than four"); // will alert Three is not greater than four.

逗号运算符

JavaScript 逗号运算符 ( ,)接受两个操作数。它计算两个操作数,并返回第二个操作数的值。逗号操作符最常见的两种用法是在一个var语句中定义多个变量,以及在for循环中提供多个参数。

语法

operand1, operand2

例题

// Initialize an array of integers
var myArray = [];
for (var i = 0, j = 100; i <= 100; i++, j--) {
    myArray[i] = j;
}

// Multiple variable declarations with one var statement.
var myVar = "one",
    numericVar = 1,
    booleanVar = true;

删除操作员

delete 运算符将对象属性作为操作数,并将其从父对象中删除。如果无法删除该属性,则返回false,否则返回true

语法

delete myObject.myProperty;
delete myObject[myProperty];
delete myArray[index];

例题

var myObject = {
    "prop1" : 1,
    "prop2" : "two",
    "prop3" : true
};
alert(delete myObject.prop1);    // will alert true
alert(myObject.prop1);           // will alert undefined
alert(delete myObject["prop2"]); // will alert true
alert(myObject.prop2);           // will alert undefined

请注意,您不能删除像Math这样的预定义对象的属性(因此delete Math.PI将返回false,而不会删除属性)。

如果删除对象上被覆盖的属性,将恢复对象原型的原始属性。此外,虽然可以直接从原型中删除属性,但是不能从对象中删除从对象原型继承的属性。(关于对象及其原型的详细讨论,请参见第一章。)

如果按索引从数组中删除元素,数组的长度不会受到影响。被删除的属性将只是未定义:

var myArray = [0, 1, 2];
alert(delete myArray[1]); // will alert true
alert(myArray[1]);        // will alert undefined
alert(myArray.length);    // will alert 3

函数运算符

JavaScript function 运算符用于声明一个新的函数表达式。(关于函数、函数表达式和函数语句的深入讨论,参见第一章。)

语法

function identifier(param1, param2, ..., paramN) {
    // body
}

例子

var newFunctionExpression = function() {
    alert('This is my new function expression.');
}
newFunctionExpression(); // Will alert "This is my new function expression."

获取运算符

JavaScript get操作符 提供了访问对象内数据的接口。您可以使用它在对象上定义一个 getter 方法,该方法可以返回值或执行另一个方法。

语法

// Object literal notation
var myObject = {
    prop1: value,
    get prop1: function() {
        return this.prop1;
    }
}
// object notation
function myObject() {
    // Constructor.
}
myObject.prototype = {
    prop1: value,
    get prop1 : function() {
        return this.prop1;
    }
}

例子

var myObject = {
    "privateValue" : 10,
    "units" : "degrees",
    get angle () {
        return this.privateValue + " " + this.units;
    }
}
alert(myObject.angle); // will alert "10 degrees"

in 运算符

in操作符 接受两个操作数:一个目标对象和一个目标属性。如果目标属性在目标对象中,它返回true,否则返回false

语法

targetProperty in targetObject;

例题

var myObject = {
    "prop1" :1,
    "prop2" : "two"
}
alert("prop1" in myObject); // will alert true
alert("two" in myObject);   // will alert false; there is no property named "two"

运算符的实例

instanceof操作符接受两个操作数:一个对象和一个构造函数。如果对象的原型链中有构造函数,则返回true,如果没有,则返回false

语法

targetObject instanceof targetConstructor;

例子

// Everything in JavaScript is an Object--or maybe not.
var myArray = new Array();
var myBool = true;
alert(myArray instanceof Object);  // will alert true
alert(myBool instanceof Object);   // will alert false--primitives are not objects.
myBool = new Boolean(true);
alert(myBool instanceof Object);   // will alert true, because we constructed a new boolean object, not just a primitive.

在这个例子中,我们演示了原始值不是 JavaScript 中的对象,而数组和布尔对象是。

新操作员

new操作符将一个对象构造函数作为操作数。然后它创建一个新的空对象,其原型继承自操作数,将构造函数的上下文设置为空对象(因此在函数中关键字this将引用空对象),然后调用函数。如果构造函数没有显式返回结果对象,那么new关键字会为你做,允许你根据需要创建和分配新的对象。这个语法类似于其他语言中实例化类的语法,但是不要忘记:JavaScript 没有类,而是有一个原型继承模型。关于 JavaScript 继承模型的细节,请参见第一章。

语法

var myNewObject = new objectConstructor;

例子

var myNewArray = new Array(); // Creates a new array object.
function myConstructor() {
    this.message = "hello world"
}
var myObject = new myConstructor();
alert(myObject.message);      // will alert "hello world"

集合运算符

set操作符 提供了一个改变对象中值的接口。将它与本章前面描述的get操作符进行比较。

语法

// Object literal notation
var myObject = {
    prop1: value,
    get prop1: function() {
        return this.prop1;
    },
    set prop1: function(newVal) {
        this.prop1 = newVal;
    }
}
// object notation
function myObject() {
    // Constructor.
}
myObject.prototype = {
    prop1: value,
    get prop1 : function() {
        return this.prop1;
    },
    set prop1 : function(newVal) {
        this.prop1 = newVal;
    }
}

例子

var myAngle = {
    "privateValue" : 10,
    "privateUnits" : "degrees",
    get angle() {
        return this.privateValue + " " + this.units;
    },
    set units(newVal) {
         // Allow the user to set the units to either degrees or radians
        if ((newVal !== "degrees") && (newVal !== "radians")) {
            alert("Allowed units are degrees and radians.");
        }
        if (newVal !== this.privateUnits) {
            this.privateUnits = newVal;
            if (newVal === "radians") {
                // Need to convert our value from degrees to radians
                this.privateValue = (this.privateValue * 0.01745);
            } else {
                // need to convert our value from radians to degrees
                this.privateValue = (this.privateValue * 57.3);
            }
        }
    }
}
alert(myAngle.angle); // will alert "10 degrees"
myAngle.units = "radians";
alert(myAngle.angle); // will alert "0.1745 radians"

在这个例子中,我们正在构建一个对象,当角度的单位改变时,它会自动转换角度的单位。这是一个很好的例子,说明在对象中使用 setter 不仅仅是设置内部值。您可以在这里做任何事情,包括触发自定义事件,甚至修改其他对象的属性。关于使用 getters 和 setters 进行高级操作的一个很好的例子,参见第四章。

运算符的类型

typeof 运算符返回操作数的数据类型。一个常见的误解是typeof操作符必须作为函数使用,并且它的操作数必须放在括号内。没这个必要。typeof运算符返回其操作数的数据类型。JavaScript 开发新手通常会将typeof误认为是一个函数,并将操作数用括号括起来。然而,括号只是用来将表达式组合成一条语句,因此对于单个操作数来说是不必要的。

语法

typeof operand;

例题

var myObject = new Object();
var myBoolean = true;
alert(typeof myObject);  // will alert Object
alert(typeof myBoolean); // will alert Boolean

无效运算符

void 运算符计算其操作数,然后返回undefined

语法

void operand;

例子

alert(void 0);            // will alert "undefined"
alert(void(alert("hi"))); // Will alert "hi" first, then will alert "undefined"

摘要

在本章中,我们已经详细介绍了 JavaScript 的各种操作符:

  • JavaScript 的大部分操作符都是一元或者二元的;唯一的例外是比较运算符,它是一个三元运算符。
  • JavaScript 运算符包括按位运算符、赋值运算符、数学运算符、字符串运算符、比较运算符和逻辑运算符。
  • 您可以使用getset操作符来修改对象的属性。

在下一章中,我们将提供 DOM 的参考,包括windowdocument对象。

八、DOM 参考

如第三章所述,DOM 不是 JavaScript,也不是 ECMA-262 标准的一部分。相反,DOM 是由 W3C 跨多个规范指定的。然而,您将使用 JavaScript 做的许多事情都将涉及 DOM,因此了解它是很重要的。

我们已经在第三章中介绍了 DOM 的重要方面,包括:

  • DOM 的历史和组成它的不同规范
  • 访问 DOM 中的元素
  • 创建、删除和修改 DOM 中的元素
  • DOM 事件:处理程序、自定义事件等。

这一章提供了第三章第一节所涉及的主题的参考,以及我们在第三章第三节没有涉及到的 DOM 的其他一些共同特性。因为 DOM 规范相当大,所以本章不会详尽无遗。相反,我们将关注最常用的特性。我们还将讨论那些提供非常有用的功能但可能不常用的特性,或者是因为它们是新的,或者是因为它们不经常在参考资料中涉及。

浏览器支持

正如在第三章中提到的,DOM 在不同的浏览器和不同版本的浏览器之间有不同的支持。本参考假定了所谓的“现代”浏览器:Internet Explorer 9 和更高版本,以及最新版本的自动更新浏览器,如 Safari、Firefox 和 Chrome。如果某个特性在这些目标浏览器中有支持问题,我们会提到它。如果您的项目需要面向较旧的浏览器,您应该确保您想要使用的功能在您的目标浏览器中受支持。确保这一点的一个很好的参考是 QuirksMode.org 网站上的兼容性表:DOM 特性包含在www.quirksmode.org/dom/w3c_core.html中,DOM 事件包含在www.quirksmode.org/dom/events中。

DOM 对象

使用 DOM 最常见的工作是访问和操作文档及其元素。在本参考中,我们将重点关注与这些任务最相关的 DOM 对象:

  • window:window对象模拟浏览器窗口本身,在那里加载文档。它包括属性和方法来处理滚动窗口、定位浏览器等。
  • document:对象document对文档进行建模。它具有访问和修改文档内容的属性和方法。
  • element:element对象是一个抽象对象(意味着它不是你可以直接访问的东西,像windowdocument,而是作为一个模板,其他对象从它那里继承而来),它定义了文档中包含的元素所公开的属性和方法。当您使用 DOM 元素时,element的所有属性和方法都将在它们上面可用。

我们将按照前面的顺序(而不是按照字母顺序,就像我们在《??》第五章的参考资料中对主要 JavaScript 对象所做的那样)来介绍这些对象,因为它代表了容器的进展:window对象包含document对象,document对象包含element对象。

窗口对象引用

window对象是 DOM 树的顶端,代表加载到浏览器中的文档。通常情况下,浏览器中一次只能加载一个文档,但是通过使用 iframes 可以加载多个文档。由于每个文档都需要自己的window对象,默认情况下window对象是一个类似数组的对象:主对象代表主文档,索引条目代表 iframes 中加载的子文档,而length属性代表子文档的数量。因此,如果只有主文档而没有子文档,window.length属性将为 0。每个 iframe 都有自己的window对象,如果一个给定的 iframe 中有子文档,那么它也会有索引元素,子文档的数量也在它的length属性中表示。

子文档可以通过它们的索引来访问;它们的顺序与在文档中出现的顺序相同。通过window.parent属性,子文档中的脚本可以访问其父文档。因此,加载到浏览器中的文档中的任何脚本都可以访问加载到浏览器中的任何其他文档。出于安全原因,这种访问受到单一来源策略的限制。

image 注意单一来源策略是浏览器中的一项安全功能,旨在防止恶意脚本访问它们不应该访问的内容。该策略基本上是说,从特定站点提供的脚本只能访问从同一站点提供的文档。更具体地说,这两个文档必须使用相同的协议(HTTP 或 HTTPS)和端口(如果指定了的话)从相同的主机提供。如果其中任何一个不同,则不允许文档之间的访问。

window对象也作为 JavaScript 的全局上下文。因此,每个文档都有自己的全局上下文。因为window对象是全局上下文,所以不需要在它的任何属性或方法前面加上window。标识符。例如,要访问location属性,您可以简单地使用location而不是window.location。然而,有些属性和方法是通过window访问的。为了清楚起见的标识符。(有关在全局范围内创建和管理自己的属性和方法的详细信息,请参见第二章。)在本节中,我们将明确使用window。参考文献。

性能

除了作为 JavaScript 的全局上下文之外,window对象还有自己的属性,可以通过脚本访问。这些属性代表了浏览器窗口和其中加载的文档的各个方面:文档的 URL、窗口的几何形状等。

窗口.文档

window.document属性是对已经载入浏览器窗口的 HTML 文档的引用。(请参阅本章后面的“文档对象引用”一节。)对象document是传统上不使用window引用的属性之一。标识符。

句法

document.propertyName;
document.method();

窗口.框架

window.frames属性只是对window对象本身的引用,并提供了一种显式访问加载到主文档中的不同子文档(如果有的话)的方法。该属性是 HTML 早期版本的延续,早期版本支持使用框架集在单个窗口中加载多个文档,现在不再支持该功能。注意,因为window.frames只是引用windowwindow === window.frameswindow[3] === window.frames[3],如果子文档存在的话。

句法

window.frames[intIndex];

窗口.历史

window.history属性是对History对象的引用,该对象由浏览器公开以提供对会话历史的访问。它基本上是一个已经访问过的页面的模型,以及一些操作它们的有用方法。

性能

History对象有一个属性length

长度

length属性指的是历史的长度——已经载入窗口的页数。加载了单个文档的新窗口(或标签)的window.history.length为 1。

句法

var myLength = window.history.length;

方法

History对象有三种方法来浏览浏览器历史。

后退( )

back()方法在浏览器历史中向后移动一个条目。如果您已经在历史记录的开头,调用此方法没有任何作用。调用此方法相当于单击浏览器的后退按钮。

句法

window.history.back();

向前( )

forward()方法在浏览器历史中向前移动一个条目。如果你已经在浏览器历史的末尾,这个方法什么也不做。调用此方法相当于单击浏览器的前进按钮。

句法

window.history.forward();

去( )

go()方法按照指定的条目数遍历浏览器历史。正数在历史中向前移动(相当于单击浏览器的前进按钮),负数向后移动(相当于单击浏览器的后退按钮)。

句法

window.history.go(intDelta);

例子

var myHist = window.history; // Get a reference to the history object--saves a bit of typing.
myHist.back();    // goes back 1 entry in the history.
myHist.go(-3);    // goes back 3 more entries in history.
myHist.forward(); // goes forward 1 entry in history.
myHist.go(3);     // returns to the most recent page.

窗口.innerHeight

window.innerHeight属性包含浏览器的实际渲染视口的高度,以像素为单位。该值包括水平滚动条(如果有)。(对比window.outerHeightwindow.innerWidth。)此属性是只读的;如果你想改变窗口的高度,使用window.resizeBy()window.resizeTo()方法。

句法

var currentHeight = window.innerHeight;

例子

<!DOCTYPE html>
<html>
        <head>
        <title>JavaScript Programmer's Reference</title>
        <style>
#centerme {
    width: 100px;
    height: 100px;
    position: absolute;
    top: 0px;
    left: 0px;
    background-color: #ccc;
}
        </style>
    </head>
    <body>
        <div id="centerme"></div>
        <script>
var centerMe = document.getElementById("centerme");
// Center vertically
var newPos = (window.innerHeight - 100) / 2;
centerMe.style.top = newPos + "px";
        </script>
    </body>
</html>

本示例创建一个宽 100 像素、高 100 像素的灰色框,并将其垂直居中显示在屏幕上。

窗口.内宽

window.innerWidth属性包含浏览器的实际渲染视口的宽度,以像素为单位。该值将包括垂直滚动条(如果存在)。(对比window.outerWidthwindow.innerHeight。)此属性是只读的;如果你想改变窗口的宽度,使用window.resizeBy()window.resizeTo()方法。

句法

var currentWidth = window.innerWidth;

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
        <style>
#centerme {
    width: 100px;
    height: 100px;
    position: absolute;
    top: 0px;
    left: 0px;
    background-color: #ccc;
}
        </style>
    </head>
    <body>
        <div id="centerme"></div>
        <script>
var centerMe = document.getElementById("centerme");
// Center horizontally
var newPos = (window.innerWidth - 100) / 2;
centerMe.style.left = newPos + "px";
        </script>
    </body>
</html>

本示例创建一个宽 100 像素、高 100 像素的灰色框,并将其水平居中显示在屏幕上。

窗口.长度

属性返回通过 iframes 加载的子文档的数量。如果没有子文档,该属性将为 0。

句法

var numberOfSubdocuments = window.length;

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <iframe name="frame1"></iframe>
        <iframe name="frame2"></iframe>
        <iframe name="frame3"></iframe>
        <script>
alert(window.length); // will alert 3
        </script>
    </body>
</html>

在这个例子中,我们创建了三个 iframes,将window.length设置为 3。

窗口.位置

属性提供了一个代表被加载文档的 URL 的对象。一个Location对象具有以下属性::

  • hash:URL 中跟在#后面的部分,如果有的话。包括了#。例如,对于网址http://www.example.com:8080/subdirectory/index.html?prop=value#anchor,其hash"#anchor"
  • host:URL 的主机部分,包括端口号(如果指定)。例如,对于网址http://www.example.com:8080/subdirectory/index.html?prop=value#anchor,其host为“www.example.com:8080”。
  • hostname:不含端口号的 URL 的主机部分。例如,对于网址http://www.example.com:8080/subdirectory/index.html?prop=value#anchor,其hostname为“www.example.com”。
  • href:完整的网址。例如,对于网址http://www.example.com:8080/subdirectory/index.html?prop=value#anchor,其href为“http://www.example.com:8080/subdirectory/index.html?prop=value#anchor”。
  • origin:协议,主机,端口。例如,对于网址http://www.example.com:8080/subdirectory/index.html?prop=value#anchor,其origin为“http://www.example.com:8080”。
  • pathname:相对于主机的路径。例如,对于网址http://www.example.com:8080/subdirectory/index.html?prop=value#anchor,其pathname就是"/subdirectory/index.html"
  • port:URL 的端口,如果指定的话。如果没有指定端口,这个属性就是""。例如,对于网址http://www.example.com:8080/subdirectory/index.html?prop=value#anchor,这个port就是"8080"
  • protocol:使用的传输协议。例如,对于网址http://www.example.com:8080/subdirectory/index.html?prop=value#anchor,其protocol就是"http:"
  • search:URL 中第一个?后面的部分,如果有的话。包括问号。例如,对于网址http://www.example.com:8080/subdirectory/index.html?prop=value#anchor,这个search就是"?prop=value"

一个Location对象有以下方法:

  • assign(targetURL):将targetURL加载到浏览器中。
  • reload(boolIgnoreCache):重新加载当前网址。如果boolIgnoreCache为真,浏览器从服务器重新加载文档;否则,如果合适,它可以从其缓存中重新加载文档。
  • replace(targetURL):从浏览器历史中删除当前文档的条目,并替换为targetURL。也将targetURL载入浏览器。有关处理浏览器历史的更多信息,请参见上文的window.history
  • toString():以简单字符串的形式返回完整的 URL。

句法

var currentLocation = window.location;

个别属性:

var currHash = currentLocation.hash;
var currHost = currentLocation.host;
var currHostname = currentLocation.hostname;
etc.

个别方法:

currentLocation.reload(true);                    // Reloads the current document, bypassing the cache.
currentLocation.assign("http://www.google.com"); // Loads the Google front page.
etc.

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <script>
var currentLocation = window.location;
alert(currentLocation.toString()); // Will alert the URL of this page.
        </script>
    </body>
</html>

本示例在文档加载后立即提示文档的 URL。

窗口.本地存储

属性为现代浏览器中的本地存储特性(也称为“DOM 存储”)提供了一个接口。作为 HTML5 的一部分,本地存储特性提供了一种替代 cookies 的方法,以键/值对的形式在浏览器中存储任意数据。(关于 cookies 的详细信息,请参见document.cookie。)本地存储跨浏览器会话持续存在,这意味着用户可以关闭他们的 web 浏览器,甚至重启他们的计算机,并且数据将持续存在。访问受到同源策略的限制,就像 cookies 一样;来自一个来源的脚本将不能访问由来自另一个来源的脚本存储的数据。

localStorage接口提供了三种访问本地存储器的方法:

  • localStorage.getItem(key):返回之前用key存储的值。如果没有存储这样的值,这个方法返回null
  • localStorage.removeItem(key):从本地存储中删除key指定的键/值对。
  • localStorage.setItem(key, value):将数据值保存在key下,以备日后检索。
  • localStorage.clear():清除本地存储中的所有键/值对。

注意本地存储只能存储字符串;它不能存储数组或对象之类的东西。但是,您可以首先使用JSON.stringify()将这些项转换成 JSON 字符串,然后将结果字符串存储在本地存储中。当您稍后检索该字符串时,您可以使用JSON.parse()来重组该项。关于JSON.stringify()JSON.parse()的详细信息,请参见第五章。

还要注意,即使您在应用中使用本地存储,也不能保证您的数据以后会在那里。大多数现代浏览器实现某种形式的“私人浏览”,其中每个会话开始时没有数据,在会话结束时,包括本地存储在内的所有数据都被擦除。用户也可以手动清除他们的本地存储。因此,如果您计划在应用中使用本地存储,那么您应该在编码时考虑到这一点。

句法

var storedValue = localStorage.getItem(key);
localStorage.removeItem(key);
localStorage.setItem(key, valueToStore);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World!</h1>
        <script>
// Check to see if we've visited this page before.
var myValue = localStorage.getItem("test");
if (myValue == null) {
    alert('This is your first time here!');
    localStorage.setItem("test", "true");
} else {
    alert('You have been here before!');
}
        </script>
    </body>
</html>

在这个例子中,我们检查一个特定的键是否被用来在本地存储中存储一个值。如果没有值,我们假设用户还没有访问过网站,并使用那个键存储一个值。如果存储了值,我们假设用户已经访问了该站点。按如下方式测试此示例:

  1. 正常加载。它会提醒“这是你第一次来这里!”
  2. 单击重新加载按钮。它会提醒“你以前来过这里!”
  3. 如果您的浏览器支持选项卡式浏览,请打开一个新的选项卡并再次加载该示例。它会提醒“你以前来过这里!”
  4. 关闭并重启浏览器。重新加载这个例子,它会提示“你以前来过这里!”

如果您的浏览器支持,请在隐私浏览模式下再次执行这些测试。每次关闭浏览器并重新打开时,您应该会看到该值已被删除。

有关使用本地存储的示例,请参见第四章。

开窗器

如果这个窗口是由使用window.open()方法的脚本打开的,window.opener属性将包含一个对包含该脚本的窗口的引用。如果这个窗口是由用户手动打开的(例如,通过启动浏览器,或者打开一个新的标签),那么这个属性将是null

句法

var myOpener = window.opener;

窗口.外部高度

window.outerHeight属性包含浏览器的总高度,包括所有 chrome、工具栏等。,以像素为单位。(对比window.outerWidthwindow.innerHeight。)该值是只读的;如果你想改变浏览器的尺寸,使用window.resizeBy()window.resizeTo()方法。

句法

var totalHeight = window.outerHeight;

窗口.外宽度

window.outerWidth属性包含浏览器的总宽度,包括所有 chrome、工具栏等。,以像素为单位。(对比window.outerHeightwindow.innerWidth。)该值是只读的;如果你想改变浏览器的尺寸,使用window.resizeBy()window.resizeTo()方法。

句法

var totalWidth = window.outerWidth;

window.pageXOffset

window.pageXOffset属性包含文档水平滚动的像素数的值。(对比window.pageYOffset。)该值是只读的;如果你想滚动文档,使用window.scroll()window.scrollBy()window.scrollByLines()window.scrollByPages()window.scrollTo()方法。

image 注意window.pageXOffsetwindow.scrollX属性引用同一个值。pageXOffset属性早于scrollX,但是大多数浏览器都实现了这两个属性……除了 Internet Explorer。在版本 9 之前,Internet Explorer 不提供任何属性,而是提供了document.body.scrollLeft属性。

句法

var horizScroll = window.pageXOffset;

window.pageYOffset

window.pageYOffset属性包含文档垂直滚动的像素数的值。(对比window.pageXOffset。)该值是只读的;如果你想滚动文档,使用window.scroll()window.scrollBy()window.scrollByLines()window.scrollByPages()window.scrollTo()方法。

image 注意window.pageYOffsetwindow.scrollY属性引用同一个值。pageYOffset属性早于scrollY,但是大多数浏览器都实现了这两个属性……除了 Internet Explorer。在版本 9 之前,Internet Explorer 不提供任何属性,而是提供了document.body.scrollTop属性。

句法

var vertScroll = window.pageYOffset;

windows . parent

如果这个窗口是一个 iframe,window.parent属性将包含一个对包含它的窗口的引用。否则就是null

句法

var myParent = window.parent;

window.scrollX

window.scrollX属性包含文档水平滚动的像素数的值。(对比window.scrollYwindow.pageXOffset。)该值是只读的;如果你想滚动文档,使用window.scroll()window.scrollBy()window.scrollByLines()window.scrollByPages()window.scrollTo()方法。

image 注意window.pageXOffsetwindow.scrollX属性引用同一个值。pageXOffset属性早于scrollX,但是大多数浏览器都实现了这两个属性……除了 Internet Explorer。在版本 9 之前,Internet Explorer 不提供任何属性,而是提供了document.body.scrollLeft属性。

句法

var horizScroll = window.scrollX;

window.scrollY

window.scrollY属性包含文档垂直滚动的像素数的值。(对比window.scrollX。)该值是只读的;如果你想滚动文档,使用window.scroll()window.scrollBy()window.scrollByLines()window.scrollByPages()window.scrollTo()方法。

image 注意window.pageYOffsetwindow.scrollY属性引用同一个值。pageYOffset属性早于scrollY,但是大多数浏览器都实现了这两个属性……除了 Internet Explorer。在版本 9 之前,Internet Explorer 不提供任何属性,而是提供了document.body.scrollTop属性。

句法

var vertScroll  = window.scrollY;

window.sessionStorage 存储

会话存储功能类似于本地存储功能,只是使用会话存储存储的数据会在会话结束时丢失。像本地存储一样,对会话存储数据的访问受单一来源策略的限制,并且像本地存储一样,会话存储只能存储字符串。参见本章前面的window.localStorage,了解更多细节和示例。

句法

var storedValue = sessionStorage.getItem(key);
sessionStorage.removeItem(key);
sessionStorage.setItem(key, valueToStore);
sessionStorage.clear();

window.top

在嵌套 iframe 的情况下,window.top属性提供了对最顶层窗口的引用,该窗口是所有 iframe 的父窗口。在最上面的窗口中,或者在没有子文档的窗口中,这将是对window对象本身的引用。

句法

var myTopWindow = window.top;

方法

window对象有几个重要的方法,可以用来设置定时器,与用户通信,滚动文档,甚至移动浏览器窗口。

window.addEventListener()

window.addEventListener()方法允许您向window对象本身添加事件监听器。否则,该方法的行为与element.addEventListener()相同。有关详细信息和示例,请参见该条目。

句法

window.addEventListener(strEventType, eventHandler, boolCapture);

window.alert()

方法打开一个包含指定文本的警告弹出对话框。注意,这是一种经常在没有window.标识符的情况下被访问的方法。

句法

alert(strMessage);

例子

alert("We've been using alerts throughout the book.");

window.clearTimeout()

window.clearTimeout()方法将一个定时器 ID 作为参数,并清除该 ID 指定的超时:也就是说,它在不执行其功能的情况下将其移除。ID 是用window.setTimeout()方法或window.setInterval()方法创建定时器时返回的值。(详见window.setTimeout()window.setInterval()方法条目。)

句法

window.clearTimeout(timeoutID);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <script>
// Set a timeout that will result in an alert after a 5 second delay
var myID = setTimeout(function() {
    alert('Five seconds has passed!');
}, 5000);

// Clear the timeout so it will never execute.
clearTimeout(myID);
        </script>
    </body>
</html>

本示例设置了一个计时器,该计时器会在 5 秒钟的延迟后发出警报,然后清除计时器,使警报永远不会发生。为了验证,注释掉clearTimeout()调用并重新运行脚本。延迟之后,警报就会发生。

window.close()

方法关闭窗口。只能关闭用window.open()方法打开的窗口。

句法

var windowRef = window.open(strURL);
windowRef.close();

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <p id="opener">Click here to open a search window.</p>
        <p id="closer">Click here to close the search window.</p>
        <script>
var opener = document.getElementById("opener"),
    closer = document.getElementById("closer"),
    windowRef = false;

opener.addEventListener("click", function() {
    // If the search window isn't open, we should open it.
    // If the search window is open, we should let the user know.
    if (windowRef === false) {
        windowRef = window.open("http://www.google.com");
    } else {
        alert("The search window is already open.");
    }
});

closer.addEventListener("click", function() {
    // If the search window is open, we should close it.
    // If the search window isn't open, we should let the user know.
    if (windowRef !== false) {
        windowRef.close();
        windowRef = false;
    } else {
        alert("The search window isn't open.")
    }
})
        </script>
    </body>
</html>

在这个例子中,我们首先获得对这两段的引用。对于 opener 引用,我们添加了一个 click 事件处理程序,如果一个搜索窗口尚未打开,它将打开该窗口,并将对该窗口的引用存储在一个变量中。如果已经打开了一个,我们会提醒用户。对于更接近的引用,我们添加了一个 click 事件处理程序,如果搜索窗口打开,它将关闭搜索窗口,并将引用变量设置回false。如果搜索窗口没有打开,我们会提醒用户。

window.confirm()

window.confirm()方法打开一个确认弹出对话框,其中包含作为参数指定的文本。确认弹出对话框有一个“确定”按钮和一个“取消”按钮;当用户点击确定时,该方法返回true,当用户点击取消时,该方法返回false

句法

var returnVal = confirm(message);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <p id="opener">Click here to open a search window.</p>
        <p id="closer">Click here to close the search window.</p>
        <script>
var opener = document.getElementById("opener"),
    closer = document.getElementById("closer"),
    windowRef = false;

opener.addEventListener("click", function() {
    // If the search window isn't open, we should open it.
    // If the search window is open, we should let the user know.
    if (windowRef === false) {
        windowRef = window.open("http://www.google.com");
    } else {
        var returnVal = confirm("The search window is already open. Would you like to close it?");
        if (returnVal === true) {
            windowRef.close();
            windowRef = false;
        }
    }
}, false);

closer.addEventListener("click", function() {
    // If the search window is open, we should close it.
    // If the search window isn't open, we should let the user know.
    if (windowRef !== false) {
        windowRef.close();
        windowRef = false;
    } else {
        var returnVal = confirm("The search window isn't open. Would you like to open it?");
        if (returnVal === true) {
            windowRef = window.open("http://www.google.com");
        }
    }
}, false);
        </script>
    </body>
</html>

这个例子扩展了我们之前的例子:在错误条件下,我们不使用警告来与用户交流,而是使用一个confirm对话框来询问用户他们想做什么。

window . get computer style()

当提供了元素引用(必需的)和可选的伪元素时,window.getComputedStyle()方法返回实际用于显示元素的样式。返回值是一个只读的Style对象,其格式与元素的style属性相同:元素上设置的每个 CSS 属性在对象中都有一个对应的属性。

句法

var appliedStyles = window.getComputedStyle(targetElement, pseudo);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
        <style>
.styledElement {
    background-color: #ccccff;
    border: 1px solid #000000;
    color: #0000FF;
}
#testElement {
    border-width: 50px;
    color: #00FF00;
}
p#testElement {
    border-width: 5px;
}
        </style>
    </head>
    <body>
        <p id="testElement" class="styledElement" style="border=color: #FF0000;">This is a test paragraph.</p>
        <script>
var myTarget = document.getElementById("testElement"),
    appliedStyles = window.getComputedStyle(myTarget);

alert(appliedStyles.backgroundColor); // Will alert something like "rgb(204, 204, 255)"
alert(appliedStyles.borderWidth);     // Will alert 5
alert(appliedStyles.color);           // Will alert something like "rgb(0, 255, 0)"
alert(appliedStyles.margin);          // Will alert something like "16px 0px"
alert(appliedStyles.padding);         // Will alert something like "0px"
        </script>
    </body>
</html>

在这个例子中,我们使用样式表和内联样式中的规则来应用一些样式。注意,我们可以检查我们在元素上设置的样式和默认样式:我们没有在元素上设置边距或填充,但是它们存在于appliedStyles对象中。(具体应用什么样的边距和填充因浏览器而异,因为每个浏览器都有自己的默认样式表;在 Chrome 中,边距为 16px 0px,填充为 0px。)

请注意,不同的浏览器实际警告的内容可能有所不同。例如,在一些浏览器中,颜色被警告为 RGB 值;在其他情况下,它们以十六进制值发出警报。值是正确的,只是格式不同。此外,一些浏览器试图尽可能提供简写值。当尝试比较样式值时,这可能会有问题—如果您期望颜色的 RGB 值字符串,但却获得了十六进制字符串,那么这可能会导致比较失败。

window.open()

window.open()方法打开一个带有指定参数的窗口。在实现选项卡式浏览的浏览器中,没有设置功能的新窗口将作为新选项卡打开。设置功能字符串通常会强制浏览器将窗口作为独立窗口打开,而不是作为新选项卡打开,尽管用户也可以覆盖这种行为,并指定所有新窗口都作为选项卡打开。

window.open()方法的有效参数如下:

  • url:要在新窗口中显示的所需文档的 URL。
  • strName:窗口的名称(可选)。这并不指定窗口的标题——它是由加载到新窗口中的文档的<title>标签指定的。这个名字可以作为使用target属性的链接和表单的目标。
  • strFeatures:以feature=value对的形式的所需窗口特性的逗号分隔列表。可用功能及其实现因浏览器而异,但最常见的如下:
  • left:新窗口在用户工作区的左侧位置,相对于显示器的左边缘。有效值是整数;许多浏览器只允许正整数。
  • top:新窗口在用户工作区的顶部位置,相对于监视器的上边缘。有效值是整数;许多浏览器只允许正整数。
  • height:内容窗口的期望高度,以像素为单位;相当于window.innerHeight。有效值是整数;最小值是 100。
  • width:内容窗口的期望宽度,以像素为单位;相当于window.innerWidth。有效值是整数;最小值是 100。
  • menubar:当设置为yes时,使新窗口呈现其菜单栏(菜单栏包含文件、编辑、查看等)。浏览器的菜单)。如果您使用一个特性参数并且没有设置该属性,它将被设置为no,并且菜单栏将不显示。有效值为yesno
  • toolbar:当设置为yes时,使新窗口呈现其工具栏(工具栏包含后退、前进、重新加载、停止等)。按钮)。如果您使用一个特征参数并且没有设置该属性,它将被设置为no,并且工具栏将不显示。有效值为yesno
  • location:当设置为yes时,使新窗口呈现其地址栏(地址栏包含 URL 输入字段)。如果您使用一个特性参数并且没有设置该属性,它将被设置为no,并且地址栏不会显示。有效值为yesno
  • status:当设置为yes时,使新窗口呈现其状态栏(浏览器窗口底部的栏)。如果您使用一个特征参数并且没有设置该属性,它将被设置为no,并且状态栏将不会显示。有效值为yesno
  • resizable:如果设置为yes,将允许用户调整新窗口的大小。如果您使用 features 参数并且没有指定 resizable 属性,它将被设置为no,并且窗口将不可调整大小。为了良好的可用性,您应该始终将该值指定为yes。有效值为yesno
  • scrollbars:如果设置为yes,当内容对于指定区域来说太大时,允许新窗口显示水平和垂直滚动条。如果你使用一个特性参数并且没有指定scrollbars属性,它将被设置为no,滚动条将不会被添加到窗口中,用户无法滚动内容。为了良好的可用性,您应该始终将该值指定为yes。有效值为yesno

方法返回一个被打开的窗口的引用。

在 DOM 中的所有方法中,window.open()可能是被恶意的垃圾邮件制造者和粗心的程序员滥用最多的方法之一。因此,许多浏览器现在允许用户覆盖指定的设置,甚至通过使用弹出窗口阻止参数和插件来完全禁止其使用。如果浏览器的内部阻塞参数阻止了弹出窗口,那么该方法将返回null而不是窗口引用。如果弹出窗口被插件阻止,通常你将无法辨别(这就是为什么许多依赖弹出窗口的应用建议用户禁用弹出窗口阻止程序)。

由于这些限制,如果您想在应用中使用弹出窗口,您应该仔细测试,以确保使用弹出窗口拦截器的用户不会被拒绝访问应用的重要区域。

句法

var strFeatures = "left=0,right=0,scrollbars=true,resizable=true";
var windowRef = window.open(URL, strName, strFeatures);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <p id="opener">Click here to open a search window.</p>
        <p id="closer">Click here to close the search window.</p>
        <script>
var opener = document.getElementById("opener"),
    closer = document.getElementById("closer"),
    windowRef = false;

opener.addEventListener("click", function() {
    // If the search window isn't open, we should open it.
    // If the search window is open, we should let the user know.
    if (windowRef === false) {
        windowRef = window.open("http://www.google.com", "searchwindow", "left=0,top=0,resizable=true,scrollbars=true");
    } else {
        var returnVal = confirm("The search window is already open. Would you like to close it?");
        if (returnVal === true) {
            windowRef.close();
            windowRef = false;
        }
    }
}, false);

closer.addEventListener("click", function() {
    // If the search window is open, we should close it.
    // If the search window isn't open, we should let the user know.
    if (windowRef !== false) {
        windowRef.close();
        windowRef = false;
    } else {
        var returnVal = confirm("The search window isn't open. Would you like to open it?");
        if (returnVal === true) {
            windowRef = window.open("http://www.google.com");
        }
    }
}, false);
        </script>
    </body>
</html>

这个例子通过为新窗口指定一个窗口名和一些特性来扩展我们前面的例子。如果您的浏览器实现了选项卡式浏览,那么上一版本的示例可能会以选项卡的形式打开新窗口。在此版本的示例中,设置这些功能应该会导致您的浏览器将搜索窗口作为独立窗口打开。我们指定的特性应该使窗口在屏幕的左上角打开,并且可以调整大小和有滚动条。由于我们没有指定menubartoolbarlocationstatus特性,这些特性应该默认为no而不显示,但浏览器是否会接受这一点取决于您使用的浏览器以及该浏览器的设置。(在我们的 Chrome 版本中,新窗口作为独立窗口打开,但指定的位置不被认可,而是显示地址栏;在我们的 Firefox 版本中,窗口在期望的位置打开,地址栏和状态栏都显示出来。)尝试在不同的浏览器中用不同的设置打开这个例子,看看它的行为是如何变化的。

window.postMessage()

Post Message 特性是 HTML5 的新增特性,它提供了一种安全的方式将字符串从一个帧发送到另一个帧,即使单个源策略禁止在相关帧之间直接访问。当您调用postMessage()方法时,它将在目标窗口中调度一个message事件,将您指定的字符串作为结果Event对象的data属性。接收 iframe 需要加载一个文档来实现message事件的事件监听器,然后它可以接收Event对象并读取消息。

当您调用postMessage()方法时,您不仅要指定想要发送的字符串,还要指定作为 URL 加载到 iframe 中的文档的来源。如果加载到 iframe 中的文档来自不同的来源,则不会调度该事件。这种安全特性使您能够确保只有来自您想要的源的文档才能接收您的消息。您可以选择将消息发送到所有域(就像我们在下一个例子中使用星号"*"所做的那样),但是这样做会在您的应用中留下一个安全漏洞,恶意站点可以通过侦听message事件来利用这个漏洞。

在接收窗口中,事件处理程序接收一个Event对象,该对象具有包含消息的data属性。它还有一个source属性,包含发送消息的文档的来源。您应该经常检查source以确保您收到的消息来自预期的来源。如果来源不匹配,您可以丢弃该消息。这样可以防止恶意站点将潜在的有害数据注入您的站点。

根据 DOM 标准,消息可以是任何内容,一些浏览器的最新版本将允许发送对象。大多数浏览器只支持发送字符串。即便如此,您可以首先用JSON.stringify()方法序列化对象和数组以及其他东西,然后在接收端用JSON.parse()重新组合它们。

Post Message 特性是 HTML5 的新特性,但在现代浏览器中享有广泛的支持。然而,在 Internet Explorer 中,Post Message 只能在 iframes 之间工作,而不能在用window.open()方法打开的窗口中工作。

句法

在发送窗口中:

windowRef.postMessage(strMessage, targetOrigin);

在接收窗口中:

function handleMessage(event) {
    // Check event.source to make sure it comes from the desired origin
    // The message is in event.data
}
window.addEventListener("message", handleMessage, false);

为了创建一个例子,我们需要两个页面,我们称之为主页面和目标页面。主页将包含一个加载目标页面的 iframe。

主页

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <iframe src="target-page.html" id="targetFrame"></iframe>
        <p id="clickme">Click to send a message to the iframe.</p>
        <script>
var strMessage = "Hello, main window here, are you receiving?",
    clickme = document.getElementById("clickme"),
    targetFrame = document.getElementById("targetFrame");

clickme.addEventListener("click", function() {
    targetFrame.contentWindow.postMessage(strMessage, "*");
})

function handleMessage(event) {
    var strAlert = "Main Window:\n";
    strAlert += event.data;
    alert(strAlert);
}
window.addEventListener("message", handleMessage, false);
        </script>
    </body>
</html>

目标页面

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Target iframe</h1>
        <script>
function handleMessage(event) {
    var strAlert = "Target iframe:\n";
    strAlert += event.data;
    alert(strAlert);
    window.top.postMessage("Hello, target iframe here, I received your message.", "*");
}
window.addEventListener("message", handleMessage, false);
        </script>
    </body>
</html>

要运行这个示例,用任何名称保存主页面,然后用名称target-page.html将目标页面保存在同一个目录中。当您将第一页加载到浏览器中时,它将加载 iframe 中的第二页。

两个文档都将message事件处理程序绑定到它们的window对象,并且都通过警告消息来处理消息。

当你点击文本向 iframe 发送消息时,脚本将使用postMessage()发送消息“你好,这里是主窗口,你收到了吗?”iframe 将接收消息,并使用它的handleMessage()事件处理程序处理它,这将提醒我们刚刚发送的消息。然后,目标页面将通过window.top引用向主页面发回一条消息(详见上文window.top)。主页面将处理产生的message事件并给出警告消息。

注意,在这个例子中,我们没有在对postMessage()的调用中指定目标源,也没有在事件处理程序中检查源源。我们这样做是因为这是一个测试案例,我们不知道您将如何运行这些示例。您应该始终指定目标来源,并检查将发布到野外的任何脚本中的来源来源—这对于安全性非常重要。事实上,我们鼓励您修改这些脚本,以便它们指定目标源,并根据您的特定环境检查源源。此外,尝试将它们设置为不同的值,亲自验证这些示例的行为是否符合预期。

window.print()

window.print()方法打开浏览器的打印对话框,就像用户从菜单中选择了文件images打印一样。出于安全原因,您不能从 JavaScript 访问打印对话框的任何功能,包括关闭打开的打印对话框。

句法

window.print();

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
            <script>
window.print();
            </script>
    </body>
</html>

当您加载这个示例时,它将打开浏览器的打印对话框,让您有机会打印页面。

window.prompt()

window.prompt()方法打开一个提示对话框,它将显示一个指定的字符串,并为用户提供一个文本输入字段。提示对话框有两个按钮,确定和取消。单击 OK 将关闭对话框,并使该方法返回用户在文本输入字段中输入的任何内容。单击 Cancel 将关闭对话框,并使该方法返回null

句法

var strReturnValue = prompt(strMessage);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1></h1>
        <script>
var header = document.querySelector("h1"),
    userName = prompt("Hey there, what's your name?");

if ((userName !== null) && (userName !== "")) {
    header.innerText = "Pleased to meet you, " + userName + "!";
} else {
    header.innerText = "I wish I knew your name. :(";
}
        </script>
    </body>
</html>

在这个例子中,我们提示用户输入他们的名字。如果他们点击“取消”,或者他们什么也没输入,然后点击“确定”,我们会告诉他们这让我们多么难过。否则,我们告诉他们我们很高兴见到他们。

window.removeEventListener()

方法删除了一个之前在窗口中注册的事件监听器。否则它的行为与element.removeEventListener()完全一样。详情和例子见element.removeEventListener()

句法

window.removeEventListener(strEventType, eventHandler, boolCapture);

window.resizeBy( )

方法通过指定的像素数来改变窗口的宽度和高度。正数表示尺寸增大,负数表示尺寸减小。(对比window.resizeTo()。)

大多数浏览器只允许你调整使用window.open()方法打开的独立窗口的大小(不是标签,如果浏览器实现了标签浏览)。一些浏览器还为用户提供了明确禁用该功能的选项。

句法

window.resizeBy(changeInWidth, changeInHeight);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World!</h1>
        <script>
var myWindow = window.open("http://www.google.com", "searchWindow", "top=10,left=10,width=500,height=500");
myWindow.resizeBy(-100, -100); // Shrinks the window's width and height by 100 px each.
        </script>
    </body>
</html>

在这个例子中,我们打开一个 500 像素宽和 500 像素高的搜索窗口。然后,我们通过将宽度和高度都减少 100 像素来调整它的大小。当浏览器首先以 500x500 大小打开,然后缩小到 400x400 大小时,您可能会看到短暂的闪烁。参见window.open()了解该方法的详细信息,包括浏览器的依赖性和局限性。

window.resizeTo( )

方法将窗口调整到指定的尺寸(以像素为单位)。(对比window.resizeBy()。)大多数浏览器只允许你调整使用window.open()方法打开的独立窗口的大小(不是标签,如果浏览器实现了标签浏览)。一些浏览器还为用户提供了明确禁用该功能的选项。

句法

window.resizeTo(intWidth, intHeight);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World!</h1>
        <script>
var myWindow = window.open("http://www.google.com", "searchWindow", "top=10,left=10,width=500,height=500");
myWindow.resizeTo(200, 200); // Resize the window to 200 pixels by 200 pixels.
        </script>
    </body>
</html>

在这个例子中,我们打开一个 500 像素宽和 500 像素高的搜索窗口。然后我们把它的大小调整为 200 乘 200 像素。当浏览器首先以 500x500 大小打开,然后缩小到 200x200 大小时,您可能会看到短暂的闪烁。参见window.open()了解该方法的详细信息,包括浏览器的依赖性和局限性。

window.scroll()

window.scroll()方法将窗口中的文档滚动到指定的 x 和 y 坐标,以像素为单位,原点(0,0)位于文档的左上角。这两个参数都是必需的。

句法

windowRef.scroll(intX, intY);

window.scrollBy()

方法将窗口滚动指定的偏移量,以像素为单位。正数向下(或向右)滚动窗口,负数向上(或向左)滚动窗口。

句法

windowRef.scrollBy(intX, intY);

window.scrollByLines()

方法将窗口垂直滚动指定的文本行数。正数向下滚动,负数向上滚动。

句法

windowRef.scrollByLines(intLines);

window . scrollbypages()

方法将窗口垂直滚动指定的文本页数。正数向下滚动,负数向上滚动。

句法

windowRef.scrollByPages(intPages);

window.scrollTo()

window.scrollTo()方法的功能与window.scroll()相同。

句法

windowRef.scrollTo(intX, intY);

window.setInterval()

window.setInterval()方法允许您设置一个计时器,该计时器将每隔指定的毫秒数调用一次指定的函数。该方法返回计时器的 ID,当作为参数提供给window.clearTimeout()方法时,将取消计时器。如果没有取消,计时器将继续运行,每隔一段时间调用指定的函数,直到窗口关闭(通过关闭选项卡或浏览器本身)或新文档加载到窗口中。

请注意,计时器将每隔指定的毫秒数运行其函数,而不管函数执行需要多长时间。如果函数的执行时间超过了间隔时间,您可能会以函数同时执行而告终。例如,如果函数运行需要 700 毫秒,而您计划它每 500 毫秒运行一次,那么您将会同时执行函数。

句法

var timerID  = setInterval(functionToExecute, intMilliseconds);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>JavaScript Clock</h1>
        <h2>hh : mm : ss</h2>
        <p><button>Start</button></p>
        <script>
var clockID = null,
    buttonRef = document.querySelector("button");

// Create a function that can be called every second to update the clock.
function updateClock() {
    var ptrClock = document.querySelector("h2"),
        myTime = new Date(),
        strTime;

    // Build a string with the current time
    strTime = myTime.getHours() + " : ";
    strTime += myTime.getMinutes() + " : ";
    strTime += myTime.getSeconds();

    // Update the clock
    ptrClock.innerText = strTime;
}

// Handle clicks on the button to either start or stop the clock.
function handleButtonClick() {
    // If the clockID is null, then we need to start the clock.
    // Otherwise, we need to stop the clock.
    if (clockID == null) {
        updateClock();                            // Set the clock to the correct time
        clockID = setInterval(updateClock, 1000); // start the timer
    } else {
        clearTimeout(clockID);                    // Stop the timer
        // Clear the ID so that the next time we click on the
        // button we'll know there is no timer running
        clockID = null;
    }
}

// Bind the event handler to the button.
buttonRef.addEventListener("click", handleButtonClick, false);
        </script>
    </body>
</html>

在本例中,我们创建了一个简单的时钟,您可以通过单击按钮来启动和停止它。首先,我们创建一个可以被计时器每秒调用的函数,名为updateClock()。该函数使用Date对象获取当前时间,并使用Date方法构建一个表示时间的字符串(关于Date对象及其方法的详细信息,参见第五章)。然后,它更新时钟的文本。

当用户点击按钮时,我们希望在时钟停止的情况下启动时钟,或者在时钟启动的情况下停止时钟,因此我们创建一个事件处理程序来检查是否设置了clockID。如果时钟没有停止,事件处理程序会更新时钟,然后启动计时器。如果时钟停止,事件处理器将停止计时器并清除clockID

window.setTimeout( )

window.setTimeout()方法的工作方式类似于setInterval()方法,因为它允许您指定一个函数在指定的毫秒数后运行。然而,与setInterval()不同的是,setTimeout()只执行一次该功能。像setInterval()一样,setTimeout()返回一个代表新计时器的 ID,可以使用clearInterval()方法将其清零。

句法

var timerID  = setTimeout(functionRef, intMilliseconds);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
                <script>
// This will alert "Hello World" after 5 seconds.
var timerID  = setTimeout(function() {
    alert('Hello World!');
}, 5000);
        </script>
    </body>
</html>

在这个例子中,我们创建了一个简单的超时,在页面加载五秒钟后提醒“Hello World”。

window . sizetocontent()

方法根据窗口的内容调整窗口的大小。这对于将弹出窗口调整到适合其内容所需的大小非常有用。请注意,在调用该方法之前,必须加载并准备好 DOM。如果在 DOM 加载之前调用它,它可能会将窗口调整到不正确的大小。

句法

windowRef.sizeToContent();

document对象引用

document对象代表已经加载到窗口中的文档。document对象是window对象的一个属性,所以你可以用window.document来访问它。由于window对象也作为 JavaScript 的全局上下文,您可以省略指定window.标识符,而直接使用document(这是一个非常常见的约定)。

性能

对象document上的属性提供了关于document本身的信息,比如 URL 和 cookies。

document.activeElement

document.activeElement属性提供了对文档中当前具有键盘焦点的元素的只读引用。如果没有焦点,该属性返回对body元素的引用。

句法

var myElementReference = document.activeElement;

文档.正文

document.body属性提供了对文档的body元素的引用。

句法

var bodyRef = document.body;

例子

var myDiv = document.createElement("div"); // Creates a new element.
document.body.appendChild(myDiv); //Appends the new DIV to the document at the end.

document.compatMode

属性返回用于呈现文档的兼容模式。浏览器可以根据不同的兼容性级别来呈现文档,通过document.compatMode属性,您可以判断哪个是用来呈现当前文档的。这些值是:

  • CSS1Compat:浏览器以严格模式呈现文档,这意味着它根据相关标准呈现文档的标记,以产生可预测的结果。
  • BackCompat:该文档是使用 quirks 模式呈现的。Quirks 模式是一种呈现模式,旨在保持与旧的非标准标记的向后兼容性。因为怪癖模式偏离了标准,所以结果可能是不可预测的。

请注意,所有现代浏览器(甚至大多数旧浏览器)都以严格模式自动呈现 HTML5 文档。

句法

var strCompat = document.compatMode;

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <script>
var strCompat = document.compatMode;
alert(strCompat); // will alert "CSS1Compat" because this is an HTML 5 document.
        </script>
    </body>
</html>

本示例提示文档的呈现模式。因为这是一个 HTML5 文档(由第一行的doctype标记指定),所以浏览器以符合标准的模式呈现。

document.cookie

document.cookie属性提供对与该文档相关联的 cookies 的访问。Cookies 提供了一种在用户系统上存储少量信息的方式,以便以后检索。这些信息可以在浏览器会话中持续存在,使您能够在用户关闭浏览器或重新启动计算机后检索存储的数据。对 cookie 的访问受到单一来源策略的限制,因此来自一个来源的脚本不能访问由来自另一个来源的脚本设置的 cookie。但是,来自一个来源的脚本可能会设置一个不同来源的 cookie,从而产生一个不同来源的 cookie(原始脚本无法读取,但来自指定来源的脚本可以访问)。此类 cookie 被称为第三方 cookie。第三方 cookie 通常用于跨网站跟踪用户:每个域使用一个脚本为第三个域编写一个 cookie,并为该域加载一个可以读取该 cookie 的脚本。这样,当用户从一个域移动到另一个域时,第三个域可以监视用户及其活动。第三方 cookies 会带来一些严重的隐私问题,并可能被恶意脚本用作安全漏洞。

存储的 cookie 是一个由单个键/值对组成的字符串,后跟以下可选属性:

  • domain:可以读取结果 cookie 的文档的域。您可以指定一个确切的域(如"www.apress.com",这将允许仅从www.apress.com域提供的脚本中读取 cookie)或子域(如.apress.com,这将允许从apress.com : www.apress.commy.apress.comexamples.my.apress.com等的任何子域中读取 cookie。).
  • expires:GMT 格式的日期,指定数据过期的时间。如果您使用Date对象管理应用中的日期,那么Date.toUTCString方法将产生一个正确格式的字符串。如果既没有设置expires值也没有设置max-age值,cookie 将在会话结束时过期。
  • max-age:cookie 的最长期限,以秒为单位。如果既没有设置expires值也没有设置max-age值,cookie 将在会话结束时过期。
  • path:指定可以读取生成的 cookie 的文档的路径。如果未设置,则默认为当前文档的路径。
  • secure:如果包含了这个密钥(它不需要值,只需要包含在字符串中),那么只能通过安全(HTTPS)连接读取 cookie。

Cookies 只能包含文本,不能包含对象,并且不能包含分号、逗号或空格。您可以为给定的域设置多个 cookie。

句法

var myCookie = document.cookie; // reads the cookie string that has been set
document.cookie = myCookie;     // sets the cookie string for the document

例如,考虑 cookies 的一个常见用例:存储用户偏好。在假设的站点上,用户可以通过指定主题和语言来定制他们的用户界面。您可以将这些信息存储为 cookies,这样当用户返回您的站点时,他们的设置将会保留。您还希望指定 cookies 应该持续 5 天(或 432000 秒),并且可以在任何子域上读取。

例子

// Set the cookies
document.cookie = "username=uberuser;max-age=432000;domain=.yourdomain.com"; // Sets a cookie for username.
document.cookie = "theme=greenapple;max-age=432000;domain=.yourdomain.com"; // Sets a cookie for theme.

读取 cookie 稍微复杂一点,因为所有 cookie 都以分号分隔的字符串形式一次发送回来。所以对于前面的例子,访问document.cookie将返回字符串username=uberuser; theme=greenapple。要处理多个 cookies,您需要一种方法来搜索给定键/值对的字符串。解决方案是字符串的格式遵循一个非常特定的模式:给定一个特定的键,您需要搜索与之相关联的值,这意味着您需要查找第一次出现的字符串"key="和下一个分号之间的文本。有许多方法可以做到这一点,但最简洁的方法是使用正则表达式。正则表达式可以搜索由开始和结束分隔符分隔的子字符串:(?:^|;)\s?key=(。*?)(?:;|$)这个正则表达式指定从字符串的开头开始,我们将搜索子串"key=",一旦找到,就返回该子串和下一个分号之间的子串。下面是一个使用该正则表达式的函数:

// Returns the value associated with strKey within the document's cookie, or null if not found.
function readCookie(strKey) {
    var myCookie = document.cookie,
        cookieReg = new RegExp('(?:^|;)\\s?' + strKey + '=(.*?)(?:;|$)'),
        myVal = myCookie.match(cookieReg);
    if(myVal == null) {
        return myVal;
    } else {
        return myVal[1];
    }
}

这里有一个例子,把所有的东西放在一起:

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
        <script>
// Returns the value associated with strKey within the document's cookie, or null if not found.
function readCookie(strKey) {
    var myCookie = document.cookie,
        cookieReg = new RegExp('(?:^|;)\\s?' + strKey + '=(.*?)(?:;|$)'),
        myVal = myCookie.match(cookieReg);
    if(myVal == null) {
        return myVal;
    } else {
        return myVal[1];
    }
}

var myCookie = document.cookie;
if (readCookie("username") != null) {
    // We have been here before!  Use the readCookie function to get our preferences
    var myUsername = readCookie("username"),
        myTheme = readCookie("theme");
    alert("Hello " + myUsername + ", your theme is " + myTheme);
} else {
    // We have not been here before.  Set new cookies.
    document.cookie = "username=uberuser"; // Sets a cookie for username.
    document.cookie = "theme=greenapple";  // Sets a cookie for theme.
    alert('Cookie set.  Reload the browser to see the results.')
}
        </script>
    </head>
    <body>
        <h1>Testing Cookies</h1>
    </body>
</html>

在这个例子中,我们为usernametheme设置了 cookies。不过,我们没有在它们上面设置max-ageexpires值,所以它们是会话 cookies,一旦你关闭浏览器就会被删除。尝试在一个几分钟的 cookie 上设置一个max-age,以验证该 cookie 是否如预期的那样持续存在,然后在过期后不可用。(大多数浏览器不会访问从文件系统加载的页面的 cookies,所以如果您希望运行这个示例,您可能需要使用个人 web 服务器来提供服务。)

Cookie 字符串的总大小限制为 4kb,因此您可以存储的数据量也是有限的。此外,许多浏览器为用户提供了对 cookies 的精细控制,包括覆盖它们的过期或内容,甚至完全禁止它们。

关于 cookies 的现代替代方案,请参见本章前面对window.sessionStoragewindow.localStorage的讨论。

文档.标题

属性返回一个对文档的元素的引用。该属性可用作访问 head 中元素的快捷方式,如脚本、样式表等。

句法

var myHead = document.head;

例子

var docTitle = document.head.title;

文档.位置

document.location属性的行为与window.location相同,返回一个Location对象。详见window.location

文档.推荐人

在用户通过点击另一个页面上的链接来访问当前页面的情况下,document.referrer属性返回引用页面的 URL。否则,它返回一个空字符串("")。

句法

var myReferrer = document.referrer;

文档.标题

document.title 属性返回一个包含文档的title标签内容的字符串。

句法

var myTitle = document.title;

文件。统一资源定位器

属性返回一个包含文档 URL 的字符串。

句法

var myUrl = document.URL;

方法

document对象有几个方法可用于操作文档内容、访问元素和管理事件。

document.addEventListener()

document.addEventListener()方法向文档添加一个事件监听器。否则它的行为和element.addEventListener()完全一样;有关详细信息和示例,请参见该条目。

句法

document.addEventListener(strEventType, eventHandler, boolCapture);

document.createComment

方法用指定的文本创建一个新的注释元素。然后可以使用任何 DOM 操作方法将这个元素插入到 DOM 中。

句法

var myCommentEl = document.createComment("This is my comment.");

例子

var myCommentEl = document.createComment("END OF DOCUMENT");
document.body.appendChild(myCommentEl); // appends the new comment to the end of the document.

document . create document fragment()

document.createDocumentFragment()方法创建一个新的文档片段。文档片段是通用的容器,可以作为您正在创建的新元素的临时区域。根据您的喜好配置文档片段后,您可以将它附加到主文档的所需位置。

句法

var myFrag = document.createDocumentFragment();

例子

var myFrag = document.createDocumentFragment(),
    myPar = document.createElement("p"),
    myText = document.createTextNode("Hello world!");
myPar.appendChild(myText);         // append the text to the paragraph.
myFrag.appendChild(myPar);         // Append the paragraph to the document fragment.
document.body.appendChild(myFrag); // Append the total fragment to the end of the document body.

document.createElement()

方法创建一个指定类型的新 DOM 元素。类型是用引号括起来的字符串,如果您提供了无效的类型,该方法将引发错误。

句法

var myEl = document.createElement(tagName);

document.createEvent()

document.createEvent()方法创建指定类型的 DOM Event对象,然后可以使用target.dispatchEvent()在目标上配置和调度该对象。详情和示例见第三章。

句法

var myEvent = document.createEvent(eventType);

document.createTextNode()

方法用想要的文本创建一个文本节点。然后,可以将该节点附加到另一个节点。

句法

var myTextNode = document.createTextNode(strText);

例子

var myParagraph = document.createElement("p"),
    myTextNode = document.createTextNode("This is a dynamically added paragraph.");

myParagraph.appendChild(myTextNode);
document.body.appendChild(myParagraph); // Appends the paragraph to the very end of the document.

document.getElementById()

document.getElementById()方法返回对具有指定 ID 的元素的引用。id 被认为在文档中是唯一的;如果文档中有重复的 ID,该方法返回对它遇到的具有指定 ID 的第一个元素的引用。

句法

var targetEl = document.getElementById(strID);

document . getelementsbyclassname()文件

document.getElementsByClassName()方法在文档中搜索具有指定类的所有元素,并返回一个类似数组的对象,包含它们的引用。每个匹配都将作为对象中的索引元素出现,length属性将表示元素的总数。如果没有匹配的元素,这个方法返回一个类似数组的对象,没有成员,并且length属性设置为 0。

句法

var myTags = document.getElementsByClassName(strClass);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <ul>
            <li>This should stay visible.</li>
            <li class="hideme">This should be hidden.</li>
            <li>This should stay visible.</li>
            <li>This should stay visible.</li>
            <li class="hideme">This should be hidden.</li>
            <li>This should stay visible.</li>
            <li class="hideme">This should be hidden.</li>
            <li class="hideme">This should be hidden.</li>
            <li>This should stay visible.</li>
        </ul>
        <p>This should stay visible.</p>
        <p class="hideme">This should be hidden.</p>
        <div class="hideme">
            <p>This div and everything within it should be hidden.</p>
        </div>
        <div>
            <p>This div and everything within it should be visible.</p>
        </div>
        <script>
var myEls = document.getElementsByClassName("hideme"),
    i;
for (i = 0; i < myEls.length; i++) {
    myEls[i].style.display = "none";
}
        </script>
    </body>
</html>

在这个例子中,我们获取了一个对所有具有类"hideme"的条目的引用,然后我们遍历集合,并将每个条目的display属性设置为隐藏。

document . getelementsbytagname()

document.getElementsByTagName()方法返回一个类似数组的对象,该对象包含对指定标记名的所有元素的引用。每个匹配都将作为对象中的索引元素出现,length属性将表示元素的总数。如果没有匹配的元素,这个方法返回一个类似数组的对象,没有成员,并且length属性设置为 0。

句法

var myEls = document.getElementsByTagName(strTagName);

document.querySelector()

方法返回第一个匹配指定 CSS 选择器的元素的引用。如果不匹配,该方法返回null。(对比document.querySelectorAll()。)有关querySelector()的详细信息,请参见第三章的“访问 DOM 中的元素”一节

句法

var myEl = document.querySelector(strSelector);

document.querySelectorAll()

方法返回一个类似数组的对象,其成员是匹配指定 CSS 选择器的元素。每个匹配都将作为对象中的索引元素出现,length属性将表示元素的总数。如果没有匹配的元素,这个方法返回一个类似数组的对象,没有成员,并且length属性设置为 0。(对比document.querySelector()。)有关querySelectorAll()的详细信息,请参见第三章部分“访问 DOM 中的元素”

句法

var myEls = document.querySelectorAll(strSelector);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <table>
            <tr>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
            </tr>
            <tr>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
            </tr>
            <tr>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
            </tr>
            <tr>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
            </tr>
            <tr>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
            </tr>
            <tr>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
            </tr>
        </table>
        <script>
var myEls = document.querySelectorAll("tr:nth-child(odd)"),
    i;
for (i = 0; i < myEls.length; i++) {
    myEls[i].style.backgroundColor = "#ccc";
}
        </script>
    </body>
</html>

在这个例子中,我们使用querySelectorAll()来访问表中所有奇数行,然后给它们不同的背景颜色。

element对象引用

windowdocument对象不同,element对象是一个抽象对象:你不能直接访问它。相反,它充当其他对象可以继承的模板对象。DOM 中的任何 HTML 元素都继承自element对象,因此 DOM 中的任何元素都将拥有element的所有属性和方法。

性能

HTML 元素的许多属性(classIDhreftarget等)。)被公开为与其相关联的 DOM element对象的属性。此外,DOM element对象具有提供对元素的子元素、兄弟元素和父元素以及元素在页面中的位置的访问的属性。

element.childNodes

属性提供了一个类似数组的对象,其成员是元素的直接子节点。这包括子元素以及其他子元素,比如注释、cdata 节,甚至表示 HTML 标记中空白的文本节点。每个节点将作为对象中的一个索引元素出现,length属性将表示子节点的总数。如果元素没有子节点,该属性返回一个类似数组的对象,没有成员,并且length属性设置为 0。(对比element.children。)

句法

var targetElement = document.getElementById("myId");
var myChildNodes = targetElement.childNodes;

元素.子元素

属性提供了一个类似数组的对象,其成员是目标元素的直接子元素。每个元素都将作为索引元素出现在对象中,length属性将表示子元素的总数。如果元素没有子节点,该属性返回一个类似数组的对象,没有成员,并且length属性设置为 0。(对比element.childNodes。)

句法

var myChildren = targetElement.children;

element.classList

属性为应用于对象的 CSS 类提供了一个接口。当单独访问时,此属性提供一个类似数组的集合,其成员是元素上的各个类。每个类都将作为一个索引元素出现在对象中,length属性将表示类的总数。如果元素没有类,这个属性提供一个类似数组的对象,没有成员,并且length属性设置为 0。

此外,classList接口有几个有用的方法:

  • add(className):将指定的类添加到元素中。
  • remove(className):从元素中删除指定的类。
  • toggle(className):如果存在,从元素中删除类;如果不存在,则添加该类。
  • contains(className):如果指定的类在元素上,则返回true;如果没有返回false

element.classList功能相对较新。Internet Explorer 9 及更低版本不支持,Safari 5.0 及更低版本也不支持。在旧的浏览器中,可以通过className属性访问元素的类。

句法

var myClasses = targetElement.classList;
targetElement.classList.add(strClass);
targetElement.classList.remove(strClass);
targetElement.classList.toggle(strClass);
var boolHasClass = targetElement.classList.contains(strClass);

element.className

element.className属性提供了对已经应用于元素的类的访问。访问时,它提供一个字符串,该字符串是所有类的空格分隔列表,如果没有,则为空字符串。当在赋值中使用时,它将元素上的类更改为以空格分隔的类的指定列表。

句法

var myClasses = targetElement.className;
targetElement.className = myClasses;

例子

var targetElement = document.getElementById("myId");
targetElement.className = "class1 class2"); // Add 2 classes to the element

element.contentEditable

属性设置元素的属性。将该值设置为true启用编辑,而将其设置为false禁用编辑。将值设置为inherit会导致元素继承其父元素的contentEditable值。要确定元素是否可编辑,请使用element.isContentEditable属性。

根据 HTML5 规范,几乎任何元素都是可编辑的。(实际上,这是一个老特性,但在 HTML5 之前它不是标准的一部分。)你只需要在元素上设置contentEditable属性,用户就可以修改它包含的文本内容了。

句法

targetElement.contentEditable = true;
targetElement.contentEditable = false;
targetElement.contentEditable = "inherit";

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <script>
var headline = document.querySelector("h1");
headline.contentEditable = true;
        </script>
    </body>
</html>

在本例中,标题是可编辑的。用户可以单击它,然后更改其内容。

element.id

element.id属性提供了对应用于元素的 ID 的访问。更改此属性会更改元素的 ID。

句法

var elID = targetElement.id;
targetElement.id = differentID;

element.innerHTML

属性提供了对包含在目标元素中的 HTML 的访问。当用作访问器时,它返回一个字符串,该字符串包含元素中的序列化 HTML。在赋值中使用时,它接受提供的 HTML 字符串,删除元素的后代,反序列化提供的 HTML,并将结果元素作为目标元素的后代插入 DOM。

将该属性设置为null或空字符串将删除目标元素的所有子元素。在较旧的浏览器中,这可能导致内存泄漏;详见第三章的章节“删除元素”。

注意,任何有效的 HTML 都可以通过这种方式插入到元素中,包括脚本。当处理用户提供的内容或您不确定是否安全的内容时,您应该小心地在插入 HTML 之前对其进行净化,以避免危及应用的安全性。

句法

var strHtml = targetElement.innerHTML;
targetElement.innerHTML = strDifferentHtml;

例子

document.body.innerHTML = ""; // completely erase a document
var strHtml = '<h1>Hello World</h1><p>This is dynamically created.</p>';
document.body.innerHTML = strHtml; // Add our content.

element.isContentEditable

element.isContentEditable属性是一个只读属性,如果元素的contentEditable属性被设置为true,或者如果它被设置为inherit,并且元素的父元素的contentEditable被设置为true,则该属性被设置为true

句法

var boolEditable = targetElement.isContentEditable;

element.lastChild

属性提供了一个只读指针,指向目标元素的最后一个子节点。如果元素没有子节点,则设置为null。节点包括元素、标签、cdata 节,甚至是表示标记中空白的文本节点。(比较element.lastElementChild。)

句法

var childNode = targetElement.lastChild;

元素。lastelementchild

属性提供了一个只读指针,指向目标元素的最后一个 HTML 元素子元素。如果元素没有子元素,则设置为null。(比较element.lastChild。)

句法

var childNode = targetElement.lastElementChild;

element.name

element.name属性提供了对元素的name属性的访问。

句法

var myName = targetElement.name;
targetElement.name = myName;

element.nextSibling

属性提供了一个只读指针,指向目标元素的下一个同级节点。如果元素没有兄弟元素,则设置为null。节点包括元素、标签、cdata 节,甚至是表示标记中空白的文本节点。(比较element.nextElementSibling。)

句法

var mySeebl = targetElement.nextSibling;

element.nextElementSibling

属性提供了一个指向 HTML 元素的只读指针,该 HTML 元素是目标元素的下一个兄弟元素。如果该元素没有兄弟元素,则设置为null。(比较element.nextSibling。)

句法

var mySeebl = targetElement.nextElementSibling;

元素偏移高度

element.offsetHeight属性提供对目标元素高度的只读访问,该高度是通过将内容的高度加上顶部和底部填充加上顶部和底部边框宽度计算出来的。它不包括上边距或下边距。

句法

var elHeight = targetElement.offsetHeight;

element.offsetLeft

element.offsetLeft属性提供对目标元素的左上边框从其偏移父元素的左上边框偏移的像素数的只读访问。元素的 offset parent 是第一个 CSS position 属性设置为relativeabsolute的父元素。

句法

var elLeft = targetElement.offsetLeft;

element.offsetParent

element.offsetParent属性提供了一个指向目标元素的偏移父元素的只读指针,该父元素是第一个将其 CSS 位置属性设置为relativeabsolute的父元素。

句法

var ptrOffsetParent = targetElement.offsetParent;

element.offsetTop

element.offsetTop属性提供对目标元素的左上边框从其偏移父元素的左上边框偏移的像素数的只读访问。元素的 offset parent 是第一个 CSS position 属性设置为relativeabsolute的父元素。

句法

var elTop = targetElementoffsetTop;

element.offsetWidth

element.offsetWidth属性提供对目标元素的偏移宽度的只读访问,该宽度通过将元素的内容宽度加上左右填充加上左右边框宽度来计算。它不包括左边距或右边距。

句法

var elWidth = targetElement.offsetWidth;

element.outerHTML

属性提供了对目标元素及其所有派生元素的 HTML 的访问。当用作访问器时,它返回包含元素及其后代的序列化 HTML 的字符串。当在赋值中使用时,它接受提供的 HTML 字符串,删除元素及其后代,反序列化提供的 HTML,并将结果元素插入 DOM。

将该属性设置为null或空字符串将删除目标元素及其所有后代。在较旧的浏览器中,这可能导致内存泄漏;详见第三章的章节“删除元素”。

注意,任何有效的 HTML 都可以通过这种方式插入到元素中,包括脚本。当处理用户提供的内容或您不确定是否安全的内容时,您应该小心地在插入 HTML 之前对其进行净化,以避免危及应用的安全性。

句法

var strHtml = targetElement.outerHTML;
targetElement.outerHTML = strHtml;

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <ul>
            <li>Apples</li>
            <li>Oranges</li>
            <li>Bananas</li>
        </ul>
        <script>
var myList = document.querySelector("ul"),
    strToDo = '<ol><li>Laundry</li><li>Grocery Store</li><li>Dry cleaning</li></ol>';

myList.outerHTML = strToDo;  // Replace the list and all of its children with our to-do list.
        </script>
    </body>
</html>

在这个例子中,我们想用待办事项列表替换水果列表。我们首先获取一个对列表的引用,然后创建一个包含要插入到文档中的 HTML 的字符串。然后我们使用outerHTML用新的标记替换目标元素。

element.parentNode

属性提供了一个指向元素父节点的只读指针。如果元素在 DOM 片段中或者没有被添加到 DOM 中,它将被设置为null

句法

var myParent = targetElement.parentNode;

元素. previousSibling

属性提供了一个只读指针,指向目标元素的上一个兄弟节点。如果没有前一个兄弟姐妹,这将是null。节点可以是元素,也可以是 cdata 节、注释,甚至是表示标记中空白的文本节点。(比较element.previousElementSibling。)

句法

var mySeebl = targetElement.previousSibling;

element.previousElementSibling

属性提供了一个只读指针,指向目标元素的上一个兄弟元素。如果没有前一个兄弟姐妹,这将是null。(比较element.previousSibling。)

句法

var mySeebl = targetNode.previousElementSibling;

项.滚动高

element.scrollHeight属性提供对元素高度和上下边距的只读访问。这相当于包含元素在不滚动的情况下显示所有元素内容的最小高度。

句法

var myHeight = targetElement.scrollHeight;

element.scrollLeft

属性提供了对目标元素的左滚动偏移量的访问。当用作访问器时,它返回目标元素的内容水平滚动的像素数。当赋值时,它会导致目标元素的内容滚动到指定位置。

句法

var scrollPos = targetElement.scrollLeft;
targetElement.scrollLeft = scrollPos;

元素.滚动顶部

属性提供了对目标元素的垂直滚动偏移量的访问。当用作访问器时,它返回目标元素的内容垂直滚动的像素数。当赋值时,它会导致目标元素的内容滚动到指定位置。

句法

var scrollPos = targetElement.scrollTop;
targetElement.scrollTop = scrollPos;

元素. scrollWidth

element.scrollWidth属性提供对元素内容宽度加上左右填充的只读访问。它不包括左边距或右边距。这相当于包含元素在没有水平滚动的情况下显示所有元素内容的最小宽度。

句法

var myWidth = targetElement.scrollWidth;

元素.样式

element.style属性通过一个Style对象提供对元素的style属性的访问。Style对象具有代表元素的style属性中设置的所有 CSS 属性的属性。可以根据需要添加新的。这些属性可用于读取当前值或将它们设置为新值。

句法

var myValue = targetElement.style.desiredCssAttribute;
targetElement.style.desiredCssAttribute = myValue;

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1 style="border: 1px solid red;color: #00F;background-color: #ccc">Hello World</h1>
        <script>
var headline = document.querySelector("h1");

alert(headline.style.backgroundColor); // will alert something like "rgb(204, 204, 204)"
headline.style.fontStyle = "italic";   // will make the text within the headline italic.
        </script>
    </body>
</html>

在这个例子中,我们在 headline 元素上设置了一些内联样式,然后使用元素的style属性来检查背景颜色和斜体文本。

element.tabIndex

element.tabIndex属性提供对元素的tabindex属性的访问。浏览器维护文档中表单元素的默认跳转顺序—默认情况下,这是它们在 HTML 中的标记顺序。您可以使用这个属性来覆写这个顺序。此外,通过设置此属性,可以强制浏览器在跳转顺序中包含非窗体元素。该值是一个从 0 开始的整数。将该值设置为 1 意味着不能使用 Tab 键访问该元素。

句法

var myTabIndex = targetElement.tabIndex;
targetElement.tabIndex = newTabIndex;

element.tagName

element.tagName属性是一个只读属性,它返回一个字符串,该字符串是元素的标签。

句法

var strTag = targetElement.tagName;

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <script>
var headline = document.querySelector("h1");

alert(headline.tagName); // will alert "H1"
        </script>
    </body>
</html>

元素.标题

element.title属性提供了对元素的title属性的访问。这是当用户将鼠标放在元素上时出现在工具提示中的文本。

句法

var myTitle = targetElement.title;
targetElement.title = myTitle;

方法

对象的方法为管理元素和元素上发生的事件提供了有用的功能。

元素. addEventListener()

element.addEventListener()方法为目标元素上的指定事件类型注册一个事件处理程序。每当指定类型的事件被调度到元素时(无论是由浏览器还是由脚本手动调度),事件处理程序都会执行。该方法采用以下参数:

  • strEventType:您正在注册处理程序的事件类型。
  • eventHandler:将接收事件通知的对象。该对象可以是一个函数(在事件发生时执行),也可以是实现事件侦听器接口的对象(有关事件侦听器接口的详细信息,请参见下面的事件处理程序对象)。事件处理程序可以是命名函数或匿名内联函数表达式。
  • useCapture:一个布尔值,如果设置为true,表示事件处理程序应该在捕获阶段执行。如果设置为false(或者省略),事件处理程序将在冒泡阶段执行。这个参数在标准中被标记为可选的,但是直到最近许多浏览器还需要它,所以指定它是一个好主意。

事件处理程序对象

通常情况下,您会将函数视为事件处理程序,但是 DOM 标准规定处理程序也可以是对象,只要该对象实现了 DOM 标准所称的侦听器接口。监听器接口是一个名为handleEvent()的方法,它接收一个Event对象作为参数。当调用该方法来处理事件时,执行上下文(由this关键字引用)被设置为侦听器接口的父对象。

有关 DOM 事件的详细信息,请参见第三章。

句法

targetElement.addEventListener(strEventType, eventHandler, useCapture);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <p id="functionHandler">Click me to test an event handler function!</p>
        <p id="objectHandler">Click me to test using an object that implements a listener interface!</p>
        <script>
var functionHandler = document.getElementById("functionHandler"),
    objectHandler = document.getElementById("objectHandler"),
    objHandleClick;

// Implement the listener interface on objHandleClick
objHandleClick = {
    handleEvent: function(event) {
        alert('This is the event listener interface!');
        alert(this == objHandleClick);  // Will alert true
    }
}

// Create an event handler function
function fctHandleClick(event) {
    alert('This is the function event handler!');
    alert(this == functionHandler);     // Will alert true
}

functionHandler.addEventListener("click", fctHandleClick, false);
objectHandler.addEventListener("click", objHandleClick, false);
        </script>
    </body>
</html>

在这个例子中,我们创建了两个不同的事件处理程序:一个是函数,另一个是实现侦听器接口的对象。我们将每个处理程序绑定到它自己的目标。

另一个使用对象处理事件的例子见element.dispatchEvent()下的例子。

element.appendChild()

element.appendChild()方法将指定的 DOM 片段或元素作为目标元素的最后一个子元素追加。

句法

targetElement.appendChild(fragment);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <div id="myTarget">
            <p>This paragraph was already here.</p>
        </div>
        <script>
var myTarget = document.getElementById("myTarget"),
    textNode = document.createTextNode("This one is new."),
    myPar = document.createElement("p");

myPar.appendChild(textNode); // Append the text node to the paragraph
myTarget.appendChild(myPar); // Append the paragraph to the div as the last child
        </script>
    </body>

在这个例子中,我们创建了一个新的文本节点和一个新的段落,然后我们将文本节点追加到段落中。然后,我们将该段落作为目标<div>的最后一个子元素添加到 DOM 中,使其出现在原始段落之后。

element.blur()

方法从元素中移除键盘焦点。(对比element.focus()。)

句法

targetElement.blur();

element.click()

element.click()方法模拟元素上的点击事件。这是一种方便的触发点击事件的简写方法,不需要使用手动调度事件的标准方式。

句法

targetElement.click();

element.cloneNode()

element.cloneNode()方法返回目标元素的克隆。如果可选参数设置为true,则执行“深度”克隆,并且所有目标元素的后代节点也被克隆。结果是一个 DOM 片段,如果需要的话,可以对其进行进一步的操作,或者将其附加到文档中。

该方法不克隆已经在目标元素或其子元素上注册的任何事件处理程序,但是它克隆属性,包括 id(所以注意不要在文档中引入重复的 id)。

句法

var newClone = targetElement.cloneNode(boolDeep);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <div id="myTarget">
            <p>This paragraph was already here.</p>
        </div>
        <script>
var myTarget = document.getElementById("myTarget"),
    myClone = myTarget.cloneNode(true);

myClone.id="myNewTarget"; // update the ID so it is not a duplicate
myClone.querySelector("p").innerText = "This is a new clone." // Update the text in the paragraph.
document.body.appendChild(myClone); // Appends the clone to the document as the last child.
        </script>
    </body>
</html>

在这个例子中,我们执行目标 div 的深度克隆。然后,我们更改克隆的 ID(以防止在文档中引入重复的 ID)并更改段落中的文本。然后我们将克隆体作为最后一个子体添加到文档中。

element.dispatchEvent()

element.dispatchEvent()方法手动将指定的事件分派给目标元素。它可以用来模拟用户交互或调度自定义事件。有关手动调度事件的详细信息,请参见第三章。

句法

targetElement.dispatchEvent(eventObject);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <p id="targetMouseup">On mouseup here there will be an alert.</p>
        <p id="targetClick">Clicking here will manually dispatch a mouseup event to the paragraph above.</p>
        <script>
var targetMouseup = document.getElementById("targetMouseup"),
    targetClick = document.getElementById("targetClick"),
    eventObject;

// Implement an event listener interface on our object
eventObject = {
    handleEvent : function(event) {
        // Route the event to the correct handler
        if (event.type === "click") {
            this.handleClick(event);
        } else if (event.type === "mouseup") {
            this.handleMouseup(event);
        }
    },
    handleClick: function(event) {
        var myCustomEvent = document.createEvent("Event"); // use the generic event module
        myCustomEvent.initEvent("mouseup", true, true);    // Initialize the event as a mouseup event
        targetMouseup.dispatchEvent(myCustomEvent);        // Dispatch the event.
    },
    handleMouseup: function(event) {
        alert('A mouseup event was dispatched to this element!');
    }
}

targetMouseup.addEventListener("mouseup", eventObject, false);
targetClick.addEventListener("click", eventObject, false);

        </script>
    </body>
</html>

在这个例子中,我们使用一个eventObject来处理我们所有的事件处理需求。事件监听器接口(handleEvent()方法)检测事件的类型,并将事件路由到正确的方法。然后我们实现处理clickmouseup事件的方法。

click事件处理程序中,我们使用通用事件模块手动创建一个Event对象。(我们可以使用MouseEvent模块,但是通用事件更简单。)然后我们将新的Event对象初始化为一个mouseup事件,然后将该事件分派给目标元素。

element.focus( )

element.focus()方法将键盘焦点发送到目标元素。(对比element.blur()。)

句法

targetElement.focus();

element.getAttribute()

element.getAttribute()方法返回指定属性的值。如果元素上不存在该属性,它将返回null。(比较element.setAttribute()。)

注意,在大多数浏览器中,通过元素的命名属性(如果有的话)访问属性比使用getAttribute()方法要快。

句法

var myAttrValue = targetElement.getAttribute(strAttributeName);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1 id="headline" class="myclass" name="greeting">Hello World</h1>
        <script>
var headline = document.getElementById("headline");
alert(headline.getAttribute("id"));    // Will alert "headline"
alert(headline.getAttribute("name"));  // will alert "greeting"
alert(headline.getAttribute("class")); // will alert "myclass"
alert(headline.getAttribute("href"));  // will alert null
        </script>
    </body>
</html>

在这个例子中,我们使用getAttribute来查询元素的 ID、类和名称。我们还用getAttribute查询了元素上的href属性,却发现是null(未设置)。

element.getElementsByClassName( )

element.getElementsByClassName()方法的行为与document.getElementsByClassName()相同,但是搜索仅限于目标元素的后代。参见document.getElementsByClassName()中的例子。

句法

var myEls = targetElement.getElementsByClassName(strClass);

元素. get 元素切换 Name

element.getElementsByTagName()方法的行为与document.getElementsByTagName()相同,但是搜索仅限于目标元素的后代。参见document.getElementsByTagName()中的例子。

句法

var myEls = targetElement.getElementsByTagName(strTagName);

element.hasAttribute()

如果目标元素具有指定的属性,则element.hasAttribute()方法返回true,否则返回false

句法

var boolHasAttribute = targetElement.hasAttribute(strAttribute);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1 id="headline" class="myclass" name="greeting">Hello World</h1>
        <script>
var headline = document.getElementById("headline");
alert(headline.hasAttribute("id"));   // Will alert true
alert(headline.hasAttribute("href")); // will alert false
        </script>
    </body>
</html>

在这个例子中,我们查询目标元素,看它是否有 ID(如果有,那么hasAttribute("id")返回true)或 href(如果没有,那么hasAttribute("href")返回false)。

element.hasAttributes()

如果目标元素有任何属性,element.hasAttributes()方法返回true,如果没有,返回false

句法

var boolHasAttributes = targetElement.hasAttributes();

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1 id="headline" class="myclass" name="greeting">Hello World</h1>
        <h2>JavaScript is awesome</h2>
        <script>
var headline = document.getElementById("headline"),
    subHead = document.querySelector("h2");
alert(headline.hasAttributes()); // Will alert true
alert(subHead.hasAttributes());  // will alert false
        </script>
    </body>
</html>

在这个例子中,我们检查标题是否有属性(如果有,那么hasAttributes()返回true)以及副标题是否有属性(如果没有,那么hasAttributes()返回false)。

element . haschildnodes()元素

如果目标元素有任何子节点,则element.hasChildNodes()方法返回true,如果没有,则返回false。子节点可以是 HTML 元素,也可以是注释、cdata 节,甚至是表示标记中空白的文本节点。

句法

var boolHasChildNodes = targetElement.hasChildNodes();

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1 id="headline" class="myclass" name="greeting">Hello World</h1>
        <div id="div1">

        </div>
        <div id="div2">
            <p>JavaScript is awesome.</p>
        </div>
        <div id="div3"></div>
        <script>
var headline = document.getElementById("headline"),
    div1 = document.querySelector("#div1"),
    div2 = document.querySelector("#div2"),
    div3 = document.querySelector("#div3");
alert(headline.hasChildNodes()); // Will alert true; the headline has a text node as a child
alert(div1.hasChildNodes());     // will alert true; div1 has a text node representing empty space                                  in the markup
alert(div2.hasChildNodes());     // will alert true; div2 has a paragraph as a child node
alert(div3.hasChildNodes());     // will alert false.
        </script>
    </body>
</html>

在这个例子中,我们有几个不同的元素,我们检查它们是否有子节点。H1标签有一个文本节点作为子节点;div1有一个文本节点,将标记中的空白区域表示为子节点;div2有一个段落作为子节点;而div3没有子节点。

element.insertBefore()

element.insertBefore()方法在引用元素之前插入一个新元素作为目标元素的子元素。如果没有提供引用元素,新元素将被追加到目标元素的子元素的末尾。

句法

targetElement.insertBefore(newElement, referenceElement);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>4</li>
            <li>5</li>
        </ul>

        <script>
var targetList = document.querySelector("ul"),
    referenceElement = document.querySelectorAll("li")[2],
    myTextNode = document.createTextNode("3"),
    myLi = document.createElement("li");

myLi.appendChild(myTextNode);
targetList.insertBefore(myLi, referenceElement);
        </script>
    </body>
</html>

在本例中,我们有一个缺少数字 3 的连续数字列表,因此我们需要为数字 3 创建一个新的列表项,并将其作为无序列表的子列表插入到列表项 4 之前。

首先我们得到一个对无序列表的引用,然后通过查询所有列表项并使用索引 3 处的列表项,我们得到一个对列表项 4 的引用。然后我们创建一个文本节点和一个新的列表项,将下一个节点追加到列表项,然后使用insertBefore()将列表项作为无序列表的子项插入到我们选择的引用项之前。

element.querySelector()

除了搜索局限于目标元素的后代节点之外,element.querySelector()方法的行为与document.querySelector()相同。详情和例子见document.querySelector()

句法

var myEl = targetElement.querySelector(strSelector);

element.querySelectorAll( )

除了搜索局限于目标元素的后代节点之外,element.querySelectorAll()方法的行为与document.querySelectorAll()相同。详情和例子见document.querySelectorAll()

句法

var myEls = targetElement.querySelectorAll(strSelector);

element.removeAttribute( )

方法从目标元素中删除指定的属性。

句法

targetElement.removeAttribute(strAttribute);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1 name="headline">Hello World</h1>
        <script>
var targetEl = document.querySelector("h1");
alert(targetEl.hasAttributes());  // will alert true
targetEl.removeAttribute("name"); // removes the name.  Now the H1 should have no attributes.
alert(targetEl.hasAttributes());  // Will alert false
        </script>
    </body>
</html>

在这个例子中,我们从标题中删除了name属性。hasAttributes()方法第一次返回true,因为name属性存在,第二次返回false,因为name属性已被移除。

element.removeChild()

方法从目标元素中移除指定的子元素。如果指定元素不是目标元素的子元素,此方法将引发异常。

句法

targetElement.removeChild(childElement);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
            <li>5</li>
        </ul>

        <script>
var targetList = document.querySelector("ul"),
    targetElement = document.querySelectorAll("li")[2];

targetList.removeChild(targetElement);
        </script>
    </body>
</html>

在这个例子中,我们使用removeChild()从列表中删除第三个项目。首先我们得到一个对列表的引用,以及对我们想要移除的元素的引用,然后我们使用removeChild()来移除该元素。

元素. removeEventListener()

element.removeEventListener()方法“撤销”了element.addEventListener()方法。因此,它采用相同的参数,并将移除使用这些参数添加的事件处理程序。如果没有事件处理程序与提供的参数匹配,则该方法不执行任何操作。

句法

targetElement.removeEventListener(strEventType, eventHandler, useCapture);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <p>Click me!</p>
        <script>
var myPar = document.querySelector("p"),
    myEventObject;

// Implement a listener interface on myEventObject
myEventObject = {
    handleEvent : function(event) {
        alert('You clicked on the paragraph!');
        event.target.removeEventListener("click", this, false); // Unregister the event handler
    }
}

myPar.addEventListener("click", myEventObject, false);
        </script>
    </body>
</html>

在本例中,我们创建了一个只执行一次的事件处理程序,因为它在执行时会删除自己。我们首先创建一个实现 DOM 监听器接口的对象(详见element.addEventListener())。在 listener 接口中,我们有一个警报来指示处理程序被触发,然后我们从目标元素中移除事件处理程序。注意,在侦听器接口中,this的值被设置为包含该接口的对象;这就是为什么在removeEventListener()调用中我们将事件处理程序称为this,而在addEventListener()调用中我们将事件处理程序称为myEventObject——它们指的是同一个东西。

element.replaceChild()

element.replaceChild()方法用指定的新元素替换目标元素的指定子元素。

句法

targetElement.replaceChild(newElement, oldElement);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
            <li>5</li>
        </ul>

        <script>
var targetList = document.querySelector("ul"),
    targetElement = document.querySelectorAll("li")[2],
    newTextNode = document.createTextNode("three"),
    newLi = document.createElement("li");

newLi.appendChild(newTextNode);

targetList.replaceChild(newLi, targetElement);
        </script>
    </body>
</html>

在这个例子中,我们用生成的列表项替换第三个列表项。首先我们得到一个对无序列表(父元素)的引用,然后是一个对我们想要替换的元素的引用。然后我们构建新的列表项,并使用replaceChild()替换目标元素。

element.scrollIntoView()

如果滚动的结果是目标元素在视口之外,那么element.scrollIntoView()方法会使元素滚动到视图中,并与可视区域的顶部对齐。如果提供了可选的布尔参数,true使元素滚动到可视区域的顶部(默认行为),而false使元素与可视区域的底部对齐。

句法

targetElement.scrollIntoView(boolAlignWithTop);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
        <style>
.container {
    width: 300px;
    height: 500px;
    overflow: auto;
    border: 1px solid #000;
}
        </style>
    </head>
    <body>
        <div class="container">

        </div>
        <script>
// Create many elements to induce scroll.
var myDocFrag = document.createDocumentFragment();
for (var i = 0 ; i < 200; i++) {
    var newParagraph = document.createElement("p"),
        newTextNode = document.createTextNode("This is paragraph #" + i);
    newParagraph.id = "id" + i;
    newParagraph.appendChild(newTextNode);
    myDocFrag.appendChild(newParagraph);
}

// Get a reference to our container and append the fragment.
var targetDiv = document.querySelector(".container");
targetDiv.appendChild(myDocFrag);

// Get a reference to the element we want to scroll into view.
var targetParagraph = document.getElementById("id150");
targetParagraph.scrollIntoView();
        </script>
    </body>
</html>

在这个例子中,我们首先有一个具有单个 div 的文档,我们将它的样式设置为特定的宽度和高度。然后我们创建许多段落,并将它们作为子元素添加到 div 中。然后我们获取对其中一个段落的引用,并使用它的scrollIntoView()方法将其滚动到视图中。

element.setAttribute()

方法将指定的属性设置为指定的值。如果目标元素上不存在该属性,则将添加该属性。注意,通过元素的命名属性来改变属性比使用setAttribute()来设置它们的值要快。

句法

targetElement.setAttribute(strAttribute, strValue);

例子

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Programmer's Reference</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <script>
var headline = document.querySelector("h1");

alert(headline.hasAttribute("name"));      // Will alert false, the headline has no name attribute.
headline.setAttribute("name", "headline"); // Sets the name.
alert(headline.hasAttribute("name"));      // Will alert true
        </script>
    </body>
</html>

在本例中,我们将name属性添加到标题中。起初,标题没有name属性,所以hasAttribute("name")返回false。然后我们设置属性并再次检查,hasAttribute("name")返回true

摘要

在这一章中,我们提供了 DOM 指定的三个最常用对象的基本参考:

  • window,它模拟加载和显示文档的窗口,还包括对其他窗口的引用。它也是 JavaScript 的全局作用域。
  • document,模拟已载入窗口的文档。document对象具有访问文档中的元素并修改它们的属性和方法。

element,为元素提供抽象模板。DOM 元素具有访问和修改元素内容以及管理事件的属性和方法。关于这里涉及的许多主题的详细讨论,以及更多的例子,请参见第四章。

本书的参考资料部分到此结束。我们希望你会发现参考部分和讨论部分(由第一章到第四章组成)对你的 JavaScript 编程有所帮助。祝你好运!

posted @ 2024-08-19 17:12  绝不原创的飞龙  阅读(2)  评论(0编辑  收藏  举报