# 脚本化文档

客户端的 javascript 的存在使得静态的 HTML 文档变成了交互式的 Web 应用。每个 Window 对象有一个 document 属性引用了 Document 对象，它是一个巨大的 API 中核心对象，叫做文档对象模型(DOM)，它代表操作文档的内容。

### 选取文档元素

DOM 定义了多种方式来选取元素：

1. 用指定的 id 属性`document.getElementById()`;
2. 用指定的 name 属性`document.getElementsByName()`;
3. 用指定的标签名`document.getElementsByTagName()`;
4. 用指定的 CSS 类`document.getElementsByClassName()`;
5. 用 CSS 选择器`querySelector(),querySelectorAll()`。

#### 通过标签名选取元素

HTMLDocument 类定义了一些快捷属性来访问各种各样的节点。例如，`images`、`forms` 和 `links` 等属性指向类似只读数组的 `img`、`form` 和 `a` 元素集合。这些属性指代 `HTMLCollection` 对象，它们可以用元素的 ID 或名字来索引：

```javascript
document.forms.id
```

HTMLDocument 对象还定义了两个属性，它们指代单个元素而非元素的集合。`document.body`是一个 HTML 文档的 `body` 元素，`document.head` 是 `head` 元素。这些属性总是对定义：如果文档源代码未显示地包含 `head` 和 `body` 元素，浏览器将隐式地创建它们。Document 类的 `documentElement` 属性指代文档的根元素。在 HTML 文档中，它总是指代 html 元素。

#### 节点列表和 HTML 集合

`document.getElementsByName()`和`document.getElementsByTagName()`都返回 `NodeList` 对象，而类似 `document.images` 和 `document.forms` 的属性为 `HTMLCollection` 对象。

`NodeList` 和 `HTMLCollection` 对象不是历史文档状态的一个静态快照，而通常是实时的，并且当文档变化时它们所包含的元素列表能随之改变。

#### 通过 CSS 类选取元素

`querySelectorAll()`接受包含一个 `CSS` 选择器的字符串参数，其返回的 `NodeList` 对象并不是实时的：它包含在调用时刻选择器所匹配的元素。`querySelector()`的只返回第一个匹配的元素。这两个方法在 `Element` 节点中也有定义。在元素上调用是，指定的选择器仍在整个文档中进行匹配，然后过滤出结果集以便它只包含指定元素的后代元素。这意味着选择器字符串能包含元素的祖先元素而不仅仅是上述匹配元素。

#### 文档结构和遍历

**作为节点树的文档**

Document 对象、它的 Element 对象和文档中表示文本的 `Text`(注意不仅仅是节点，纯文字也算进去了)对象都是 `Node` 对象。`Node` 定义了以下属性：

1. `parentNode`：该节点的父节点，或者针对类似 `Document` 对象应该是 `null`。
2. `firstChild`、`lastChild`：该节点的子节点中的第一个和最后一个，如果该节点的没有子节点则为 `null`。
3. `nextSibling`、`previousSibling`：该节点的兄弟节点中的前一个和下一个。
4. `nodeType`：该节点的类型。`9` 代表 Document 节点，1 代表 `Element` 节点，3 代表 Text 节点，8 代表 `Comment` 节点，11 代表 `DocumentFragment` 节点。
5. `nodeValue`：`Text` 节点或 `Comment` 节点的文本内容。
6. `nodeName`：元素的标签名，以大写形式表示。

**作为元素树的文档**

基于元素的文档遍历 API 的第二部分是 `Element` 属性，后者类似 `Node` 对象的子属性和兄弟属性：

1. `firstElementChild`，`lastElementChild`：代表子 `Element`；
2. `nextElementSibling`，`previousElementSibling`：代表兄弟 `Element`。
3. `ChildElementCount`：子元素的数量。

### 属性

HTML 元素由一个标签和一组称为属性的名/值对组成。

例如，查询一张图片的 `URL`，可以用表示元素的 `HTMLElement` 对象的 `src` 属性：

```javascript
var image = document.getElementById('img')
var imgurl = image.src
```

HTML 属性名不区分大小写，但 javascript 属性名则大小写敏感。从 HTML 属性名转换到 javascript 属性名应该小写。如果属性名不止包含一个单词，则将除了第一个单词以外的单词首字母大写。

有些 HTML 属性名在 javascript 中是保留字。对于这些属性，一般规则是为属性名加前缀“html”。例如，HTML 的 `for` 属性在 javascript 中变为 `htmlFor` 属性。“class”在 javascript 中是保留字，它是 HTML 非常重要的 `class` 属性，是上面规则的一个例外：在 javascript 代码中它变为了 `ClassName`。

#### 获取和设置非标准 HTML 属性

HTMLElement 和其子类定义了一些属性，它们对应于元素的标准 HTML 属性。`Element` 类型还定义了 `getAttribute()`和 `setAttribute()`方法来查询和设置非标准的 HTML 属性，也可用来查询和设置 XML 文档中元素上的属性。

```javascript
var image = document.images[0]
var width = image.getAttribute('width')
image.setAttribute('class', 'thumbnail')
```

Element 类型还定义了两个相关的方法，`hasAttribute()`和 `removeAttribute()`，它们用来检测命名属性是否存在和完全删除属性。

#### 数据集属性

HTML5 还在 Element 对象上 `dataset` 属性。该属性指代一个对象，它的各个属性对应于去掉前缀的 `data-`属性。因此 `dataset.x` 应该保存 `data-x` 属性的值。带连字符的属性对应于驼峰命名法：data-jquery-test 属性就变成 `dataset.jqueryTest` 属性。`dataset` 属性是元素的 `data-`属性的实时、双向接口。设置或删除 `dataset` 的一个属性就等于同于设置或移除对应元素的 `data-`属性。

#### 作为 Attr 节点的属性

Node 类型定义了 `attribute` 属性，`attribute` 对象也是实时的。它可以用数字索引访问，这意味着可以枚举元素的所有属性。并且，它也可以用属性名索引(前提是在 html 中设置过了)：

```javascript
document.body.attributes[0] // <body>元素的第一个属性
document.body.attributes.bgcolor // <body>元素的bgcolor属性
document.body.attributes['ONLOAD'] // <body>元素的onload属性
```

`Attr` 的 `name` 和 `value` 属性属性返回该属性的名字和值。

### 元素的内容

#### 作为 HTML 的元素内容

读取 `Element` 的 `innerHTML` 属性作为字符串标记返回那个元素的内容。在元素上设置该属性调用了 `web` 浏览器的解析器，用新字符串的内容的解析展现形式替换元素当前内容。

HTML5 还标准化了 `outerHTML` 属性。当查询 `outerHTML` 时，返回的 HTML 或 XML 标记的字符串包含被查询元素的开头和结尾标签。当设置元素的 `outerHTML` 时，元素本身被新的内容所替换。

另一个 HTNL5 标准化的特性是 `insertAdjacentHTML()`方法，它将任意的 HTML 标记字符串插入到指定的元素“相邻”的位置。第一个参数为具有以下值之一的字符串：`beforeBegin`、`afterBegin`、`beforeEnd`、`afterEnd`。

#### 作为纯文本的元素内容

有时需要查询纯文本形式的元素内容，或者在文档中插入纯文本。标准方法是用 `Node` 的 `textContent` 属性来实现。`textContent` 属性就是将指定元素的所有 `Text` 节点简单地串联在一起，而且会返回 `script` 元素的内容。

#### script 元素中的文本

内联的 `script` 元素有一个 `text` 属性来获取它们的文本。浏览器不显示 `script` 元素的内容，并且 HTML 解析器忽略脚本中的尖括号和星号。这使得 `script` 元素称为应用程序用来嵌入任意文本的一个理想的地方。简单的将元素的 `type` 设置为某些值。就表明了脚本为不可执行的 javascript 代码。如果这样做，javascript 解释器将忽略该脚本，但该元素将仍存在于文档树，它的 `text` 属性还将返回数据给你。

### 节点创建插入删除

#### 创建节点

创建新的节点可以用 `Document` 对象 `createElement()`方法，`text` 节点则使用 `document` 的 `createTextNode` 方法。例如：

```javascript
var d = document.createElement('p')
var e = document.createTextNode('文本')
d.appendChild(e) // d为<p>文本</p>
```

每个节点有一个 `cloneNode()`方法来返回该节点的一个全新副本。给方法传递参数 `true` 也能递归地复制所有的后代节点，或者传递参数 `false` 只是执行一个浅复制。在除 IE 的其他浏览器中，`Document` 对象还定义了一个类似的方法叫 `importNode()`。如果传递给它另一个文档的一个节点，他将返回一个适合本文档插入的节点的副本。传入 `true` 作为第二个参数，该方法将递归地导入所有的后代节点。

#### 插入节点

一旦有了一个新节点，就可以用 `Node` 的方法 `appendChild()`或 `insertBefore()`将它插入文档中。`appendChild()`是在需要插入的 `Element` 节点上调用的。而 `insertBefore()`接受两个参数，第一个参数是待插入的节点，第二个参数是已存在的节点，新节点将插入该节点的前面。该方法也是在新节点的父节点上调用，方法的第二个参数必须是该父节点的子节点。如果传递 `null` 作为第二个参数，`insertBefore()`的行为类似 `appendChild()`,它将节点插入在最后。

如果调用 `appendChild()`或 `insertBefore()`将已存在文档中的一个节点再次插入，那个节点自动从当前位置删除并在新的位置重新插入。

#### 删除和替换节点

`removeChild()`方法是从文档树中删除一个节点。但是请小心：该方法不是在待删除的节点上调用，而是在其父节点上调用。在父节点上调用该方法，并将需要删除的子节点作为方法参数传递给它。

`replaceChild()`方法删除一个子节点并用一个新的节取而代之。在父节点上调用该方法，第一个参数是新节点，第二个参数是需要要代替的节点。

#### 使用 DocumentFragment

`DocumentFragment` 是一种特殊的 `Node`，它作为其他节点的一个临时的容器。例如：

```javascript
var frag = document.createDocumentFragment()
```

像 `Document` 节点一样，`DocumentFragment` 是独立的，而不是任何其他文件的一部分。它的 `parentNode` 总是为 `null`。但类似 `Element`，它可以有任意多的子节点，可以用 `appendChild()`，`insertBefore()`等方法来操作它们。

`DocumentFragment` 的特殊之处在于它使得一组节点被当做一个节点看待：如果给 `appendChild()`、`insertBefore()`或 `replaceChild()`传递一个 `DocumentFragment`。其实是将该文档的所有子节点插入到文档。

### 几何形状和滚动

#### 文档坐标和视口坐标

两个不同的点坐标系的原点：元素的 `X` 和 `Y` 坐标可以相对文档的左上角或者相对于在其中显示文档的视口的左上角。在顶级窗口和标签页中，”视口“只是实际显示文档内容的浏览器的一部分。

#### 查询元素的几何尺寸

`getBoundingClientRext()`方法用来判定一个元素的尺寸和位置。它不需要参数，返回一个由 `left`、`right`、`top`、`bottom` 属性的对象。`left` 和 `top` 属性表示元素的左上角的 `X` 和 `Y` 坐标，`right` 和 `bottom` 属性表示元素的右下角的 `X` 和 `Y` 坐标。`getBoundingClientRext()`返回对象还包含 `width` 和 `height` 属性，且返回的坐标包含元素的边框和内边距，但不包含元素的外边距。

如果想查询内联元素每个独立的矩形，调用 `getClientRects()`方法来获得一个只读的类数组对，它的每个元素类似 `getBoundingClientRext()`返回的矩形对象。

`getBoundingClientRext()`和 `getClientRects()`所返回的矩形对象并不是实时的。它们只是调用方法时文档视觉状态的静态快照，在用户滚动或改变浏览器窗口大小时不会改变更新它们。

#### 判定元素在某点

`Document` 对象的 `elementFromPoint()`方法用来判定指定位置上有什么元素。通过传递 `X` 和 `Y` 坐标，该方法返回在指定位置的一个元素。该方法的意图就是返回在那个点的最里面的和最上面的元素。如果指定的点在视口以外，`elementFromPoint()`返回 `null`，即使点在转换为文档坐标后是完美有效地，返回值也是一样。

#### 滚动

`Window` 对象的 `scrollTop()`方法(同义词 scroll())接受一个点 `X` 和 `Y` 坐标，并作为滚动条的偏移量设置它们。Window 的 `scrollBy()`方法和 `scroll()`和 `scrollTo()`类似，但是它的参数是相对的，并在当前滚动条的偏移量上增加。

在需要显示的 HTML 元素调用 `scrollIntoView()`方法(最好配好事件使用)保证元素能在视口中可见。默认情况下，它试图将元素的上边缘放在或尽量接近视口的的上边缘。如果只传递 `false` 作为参数，他将试图将元素的下边缘放在或尽量接近视口的下边缘。其余设置 `window.location.hash` 为一个命名锚点的名字后浏览器产生的行为类似。

#### 关于元素尺寸、位置和溢出的更多信息

任何 HTML 元素的只读属性 `offsetWidth` 和 `offsetHeight` 以 `CSS` 像素返回它的屏幕尺寸。返回的尺寸包含元素的边框和内边距，除去了外边距。所有的 HTML 元素拥有 `offsetLeft` 和 `offsetTop` 属性来返回元素的 `X` 和 `Y` 坐标。对于很多元素，这些值是文档坐标并直接指定元素的位置。但对于已定位元素的后代元素和一些其他元素，这些属性返回的坐标是相对于祖先元素的而非文档。`offsetParent` 属性指定这些属性所相对的父元素。如果 `offsetParent` 为 `null`，这些属性都是文档坐标。

除了这些名字以 `offset` 开头的属性以外，所有的文档元素定义了其他两组属性，其名称一组以 `client` 开头，另一组以 `scroll` 开头。即，每个 HTML 元素都有以下这些属性：

```javascript
offsetWidth    clientWidth    scrollWidth
offsetHeight   clientHeight   scrollHeight
offsetLeft     clientLeft     scrollLeft
offsetTop      clientTop      scrollTop
offsetParent
```

`clientWidth` 和 `clientHeight` 类似 `offsetWidth` 和 `offsetHeight`，不同的是它们不包含边框大小，只包含内容和它的内边距。对于类似`<i>`、`<code>`和`<span>`这些内联元素，`clientWidth` 和 `clientHeight` 总是返回 `0`。

### HTML 表单

#### 选取表单和表单元素

在支持 `querySelectorAll()`的浏览器中，从一个表单中选取所有的单选按钮或有所同名的元素的代码如下：

```javascript
// id为"shipping"的中所有单选按钮
document.querySelectorAll('#shipping input[tyoe="radio"]')
// 名字为"method"的单选按钮
document.querySelectorAll('#shipping input[type="radio"][name="method"]')
```

有 `name` 或 `id` 属性的`<form>`元素能够通过很多方法来选取。`name="address"`属性的`<form>`可以用以下任何方法来选取：

```javascript
document.forms.address // 显式访问有name或id的表单
```

#### 表单和元素的属性

javascript 的 `From` 对象支持两个方法：`submit()`和 `reset()`,它们完成同样的目的。调用 `From` 对象的 `submit()`方法来提交表单，调用 `reset()`方法来重置表单元素的值。

所有表单通常都有以下属性：

1. `type`：标识表单元素类型的只读字符串。
2. `form`：对包含元素的 `Form` 对象的只读引用，或者如果元素没有包含在一个`<form>`元素中则其值为 `null`。
3. `name`：只读的字符串，由 HTML 属性那么指定。
4. `value`：可读/写的字符串，指定了表单元素包含或代表的“值”。

#### 表单和元素的事件处理程序

每个 `Form` 元素都有一个 `onsubmit` 事件处理程序来侦测表单提交，还有一个 `onrest` 事件处理程序来侦测表单重置。表单提交前调用 `onsubmit` 程序：它通过返回 `false` 能够取消提交动作。注意，`onsubmit` 事件处理程序只能通过点击“提交”按钮来触发。直接调用表单的 `submit()`方法不会触发。

既然在`<form>`元素中的元素都有一个 `form` 属性引用了该包含的表单这些元素的事件处理程序总是能够通过 `this.form` 来得到 `Form` 对象的引用。更进一步，这意味着某个表单元素的事件处理程序能够通过 `this.form.x` 得到该表单中以 `x` 命名的元素。

#### 开关按钮

单选和复选框元素都定义了 `checked` 属性。该属性是可读/写的布尔值，它指定了元素当前是否选中。`defaultChecked` 属性也是布尔值，它是 HTML 属性 `checked` 的值；它指定元素在第一次加载页面时是否选中。

#### 选择框和选项元素

`Select` 元素能以两种不同的方式运作，这取决于它的 `type` 属性值是如何设置的。如果 `select` 元素有 `nultiple` 属性，也就是 `Select` 对象的属性值为`select-multiple`,那就允许用户选取多个选项。否则没有多选属性，那只能选取单个选项，它的 `type` 属性值为`select-one`。针对`select-one`的`Select` 元素，单个 `selectedIndex` 指定了哪个选项被选中。针对`select-multiple`元素，单个 `selectedIndex` 属性不足以表示被选中的一组选项。在这种情况下，要判定哪些选项被选中，就必须遍历 `options[]`数组的元素，并检测每个 `Option` 对象的 `selected` 属性值。

除了其 `selected` 属性，每个 `Option` 对象有一个 `text` 属性，它指定了在 `Select` 元素中的选项所显示的纯文本字符串。设置该属性可以改变显示给用户的文本。`value` 属性指定了在提交表单时发送到 `Web` 服务器的文本字符串，它也是可读/写的。

对于一些专用的 `Select` 的 API，例如`new Option()`等，这些 API 已经很老了，可以用标准的调用更明确地插入和移除选项元素：`Document.createElement()`、`Node.insertBefore()`、`Node.removeChild()`等。

### 其他文档特性

#### Document 的属性

文档定义了一些有趣的属性：

1. `cookie`：允许 javascript 程序读、写 `HTTP cookie` 的特殊的属性。
2. `domain`：该属性允许当 `Web` 页面之间交互时，相同域名下互相信任的 `Web` 服务器之间协作放宽同源策略安全限制。
3. `lastModified`：包含文档修改时间的字符串。
4. `location`：与 `Window` 对象的 `location` 属性引用同一个 `Location` 对象。
5. `referrer`：如果有，它表示浏览器导航到当前链接的上一个文档。该属性值和 `HTTP` 的 `Referer` 头信息的内容相同，只是拼写上有两个 `r`。
6. `title`：文档的 `title` 标签之间的内容。
7. 文档的 `URL`，只读字符串而不是 `Location` 对象。该属性值与 `location.href` 的初始值相同，只是不包含 `Location` 对象的动态变化。

#### 查询选取的文本

标准的 `window.getSelection()`方法返回一个 `Selection` 对象，后者描述了当前选取的一系列一个或多个 `Range` 对象。

#### 可编辑的内容

有两种方法来启用编辑功能。其一，设置任何标签的 `HTML contenteditable` 属性；其二，设置对应元素的 `javascript contenteditable` 属性。

为元素添加 `spellcheck` 属性来显式开启拼写检查，而使用`spellcheck=false`来显式关闭该功能。

将 `Document` 对象的 `designMode` 属性设置为字符串`on`使得整个文档可编辑。

浏览器定义了多项文本编辑器命令，大部分没有键盘快捷键。为了执行这些命令，应该使用 `Document` 对象的 `execCommand()`方法。可用此方法制作富文本编辑器。点击[这里](https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand)了解更多。
