浏览器环境,规格

在这里,我们将学习如何使用 JavaScript 来操纵网页。
下面是 JavaScript 在浏览器中运行时的鸟瞰示意图:
image.png
window :“根”对象

文档对象模型(DOM)

文档对象模型(Document Object Model),简称 DOM。将所有页面内容表示为可以修改的对象。document 对象是页面的主要“入口点”。我们可以使用它来更改或创建页面上的任何内容。
DOM 不仅仅用于浏览器
DOM 规范解释了文档的结构,并提供了操作文档的对象。有的非浏览器设备也使用 DOM。
例如,下载 HTML 文件并对其进行处理的服务器端脚本也可以使用 DOM。但它们可能仅支持部分规范中的内容。

浏览器对象模型(BOM)

浏览器对象模型(Browser Object Model),简称 BOM,表示由浏览器(主机环境)提供的用于处理文档(document)之外的所有内容的其他对象。
例如:

  • navigator 对象提供了有关浏览器和操作系统的背景信息。navigator 有许多属性,但是最广为人知的两个属性是:navigator.userAgent — 关于当前浏览器,navigator.platform — 关于平台(可以帮助区分 Windows/Linux/Mac 等)。
  • location 对象允许我们读取当前 URL,并且可以将浏览器重定向到新的 URL。

    Mozilla 手册

    当你想要了解某个属性或方法时,Mozilla 手册 https://developer.mozilla.org/en-US/search 是一个很好的资源,但对应的规范可能会更好:它更复杂,且阅读起来需要更长的时间,但是会使你的基本知识更加全面,更加完整。

    DOM 树

    DOM 的例子

    自己看看

    要在实际中查看 DOM 结构,请尝试 Live DOM Viewer。只需输入文档,它将立即显示为 DOM。
    探索 DOM 的另一种方式是使用浏览器开发工具。

    Chrome 开发者工具

    https://developers.google.cn/web/tools/chrome-devtools 上有关于 Chrome 开发者工具的详细文档说明。

    总结

    HTML/XML 文档在浏览器内均被表示为 DOM 树。

  • 标签(tag)成为元素节点,并形成文档结构。

  • 文本(text)成为文本节点。
  • ……等,HTML 中的所有东西在 DOM 中都有它的位置,甚至对注释也是如此。

我们可以使用开发者工具来检查(inspect)DOM 并手动修改它。

遍历 DOM

DOM 让我们可以对元素和它们中的内容做任何事,但是首先我们需要获取到对应的 DOM 对象。
对 DOM 的所有操作都是以 document 对象开始。它是 DOM 的主“入口点”。从它我们可以访问任何节点。

在最顶层:documentElement 和 body

= document.documentElement
= document.body
= document.head
脚本无法访问在运行时不存在的元素。
在 DOM 的世界中,null 就意味着“不存在”

子节点:childNodes,firstChild,lastChild

childNodes 集合列出了所有子节点,包括文本节点。

  1. <html>
  2. <body>
  3. <div>Begin</div>
  4. <ul>
  5. <li>Information</li>
  6. </ul>
  7. <div>End</div>
  8. <script>
  9. for (let i = 0; i < document.body.childNodes.length; i++) {
  10. alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT
  11. }
  12. </script>
  13. ...more stuff...
  14. </body>
  15. </html>

DOM 集合

正如我们看到的那样,childNodes 看起来就像一个数组。但实际上它并不是一个数组,而是一个 集合 — 一个类数组的可迭代对象。

  1. 我们可以使用 for..of 来迭代它。这是因为集合是可迭代的(提供了所需要的 Symbol.iterator 属性)。

    1. for (let node of document.body.childNodes) {
    2. alert(node); // 显示集合中的所有节点
    3. }
  2. 无法使用数组的方法,因为它不是一个数组。如果我们想要使用数组的方法的话,我们可以使用 Array.from 方法来从集合创建一个“真”数组:

    1. alert( Array.from(document.body.childNodes).filter ); // function

    DOM 集合是只读的
    DOM 集合是实时的
    不要使用 for..in 来遍历集合,for..in 循环遍历的是所有可枚举的(enumerable)属性。

    兄弟节点和父节点

    纯元素导航

    更多链接:表格

    ```javascript

    onetwo
    threefour

  1. <a name="vpVmj"></a>
  2. # 搜索:getElement*,querySelector*
  3. <a name="V6jkq"></a>
  4. ## document.getElementById 或者只使用 id
  5. 如果一个元素有 id 特性(attribute),那我们就可以使用 document.getElementById(id) 方法获取该元素,<br />**请不要使用以 id 命名的全局变量来访问元素**<br />**id 必须是唯一的**
  6. <a name="eR89g"></a>
  7. ## querySelectorAll
  8. 到目前为止,最通用的方法是 elem.querySelectorAll(css),它返回 elem 中与给定 CSS 选择器匹配的所有元素。
  9. <a name="QC4ab"></a>
  10. ## querySelector
  11. elem.querySelector(css) 调用会返回给定 CSS 选择器的第一个元素。<br />换句话说,结果与 elem.querySelectorAll(css)[0] 相同,但是后者会查找 **所有** 元素,并从中选取一个,而 elem.querySelector 只会查找一个。因此它在速度上更快,并且写起来更短。
  12. <a name="oOijA"></a>
  13. ## matches
  14. 当我们遍历元素(例如数组或其他内容)并试图过滤那些我们感兴趣的元素时,这个方法会很有用。
  15. <a name="CODTt"></a>
  16. ## closest
  17. elem.closest(css) 方法会查找与 CSS 选择器匹配的最近的祖先。方法 closest 在元素中得到了提升,并检查每个父级。
  18. <a name="qR5hB"></a>
  19. ## getElementsBy*
  20. 如今,它们大多已经成为了历史,因为 querySelector 功能更强大,写起来更短。
  21. <a name="HHc7c"></a>
  22. ## 总结
  23. 有 6 种主要的方法,可以在 DOM 中搜索元素节点:
  24. | 方法名 | 搜索方式 | 可以在元素上调用? | 实时的? |
  25. | --- | --- | --- | --- |
  26. | querySelector | CSS-selector | ✔ | - |
  27. | querySelectorAll | CSS-selector | ✔ | - |
  28. | getElementById | id | - | - |
  29. | getElementsByName | name | - | ✔ |
  30. | getElementsByTagName | tag or '*' | ✔ | ✔ |
  31. | getElementsByClassName | class | ✔ | ✔ |
  32. 目前为止,最常用的是 querySelector 和 querySelectorAll,但是 getElement(s)By* 可能会偶尔有用,或者可以在旧脚本中找到。
  33. <a name="Tsf45"></a>
  34. # 节点属性:type,tag 和 content
  35. <a name="uE6Bd"></a>
  36. ## DOM 节点类
  37. 所有类型的 DOM 节点都形成了一个单一层次的结构(single hierarchy)。<br />每个 DOM 节点都属于相应的内建类。<br />层次结构(hierarchy)的根节点是 [EventTarget](https://dom.spec.whatwg.org/#eventtarget),[Node](http://dom.spec.whatwg.org/#interface-node) 继承自它,其他 DOM 节点继承自 Node。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/542445/1637672055509-c446cb88-7081-4ec4-91ee-4e4ea2b647a3.png#clientId=u1df83b73-ff08-4&from=paste&height=347&id=u04c8647a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=456&originWidth=599&originalType=binary&ratio=1&size=24428&status=done&style=none&taskId=u337565e3-126e-4d33-8337-c8da16f5e73&width=455.5)<br />类如下所示:
  38. - [EventTarget](https://dom.spec.whatwg.org/#eventtarget) — 是根的“抽象(abstract)”类。该类的对象从未被创建。它作为一个基础,以便让所有 DOM 节点都支持所谓的“事件(event)”,我们会在之后学习它。
  39. - [Node](http://dom.spec.whatwg.org/#interface-node) — 也是一个“抽象”类,充当 DOM 节点的基础。它提供了树的核心功能:parentNode,nextSibling,childNodes 等(它们都是 getter)。Node 类的对象从未被创建。但是有一些继承自它的具体的节点类,例如:文本节点的 Text,元素节点的 Element,以及更多异域(exotic)类,例如注释节点的 Comment。
  40. - [Element](http://dom.spec.whatwg.org/#interface-element) — 是 DOM 元素的基本类。它提供了元素级的导航(navigation),例如 nextElementSibling,children,以及像 getElementsByTagName 和 querySelector 这样的搜索方法。浏览器中不仅有 HTML,还会有 XML 和 SVG。Element 类充当更多特定类的基本类:SVGElement,XMLElement 和 HTMLElement。
  41. - [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) — 最终是所有 HTML 元素的基本类。各种 HTML 元素均继承自它:
  42. - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) — <input> 元素的类,
  43. - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) — <body> 元素的类,
  44. - [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) — <a> 元素的类,
  45. - ……等,每个标签都有自己的类,这些类可以提供特定的属性和方法。
  46. 因此,给定节点的全部属性和方法都是继承的结果。<br />DOM 节点是常规的 JavaScript 对象。它们使用基于原型的类进行继承。
  47. <a name="wpGqx"></a>
  48. ## “nodeType” 属性
  49. nodeType 属性提供了另一种“过时的”用来获取 DOM 节点类型的方法。
  50. <a name="EavPB"></a>
  51. ## 标签:nodeName 和 tagName
  52. - tagName 属性仅适用于 Element 节点。
  53. - nodeName 是为任意 Node 定义的:
  54. - 对于元素,它的意义与 tagName 相同。
  55. - 对于其他节点类型(text,comment 等),它拥有一个对应节点类型的字符串。
  56. <a name="jvRK8"></a>
  57. ## innerHTML:内容
  58. [innerHTML](https://w3c.github.io/DOM-Parsing/#the-innerhtml-mixin) 属性允许将元素中的 HTML 获取为字符串形式。<br />我们也可以修改它。因此,它是更改页面最有效的方法之一。
  59. <a name="ZKpn8"></a>
  60. ### 小心:“innerHTML+=” 会进行完全重写
  61. 我们可以使用 elem.innerHTML+="more html" 将 HTML 附加到元素上。<br />但我们必须非常谨慎地使用它,因为我们所做的 **不是** 附加内容,而且完全地重写。<br />换句话说,innerHTML+= 做了以下工作:
  62. 1. 移除旧的内容。
  63. 1. 然后写入新的 innerHTML(新旧结合)。
  64. **因为内容已“归零”并从头开始重写,因此所有的图片和其他资源都将重写加载。**<br />如果 chatDiv 有许多其他文本和图片,那么就很容易看到重新加载(译注:是指在有很多内容时,**重新加载会耗费更多的时间**,所以你就很容易看见页面重载的过程)。<br />并且还会有其他**副作用**。例如,如果现有的文本被用鼠标选中了,那么大多数浏览器都会在重写 innerHTML 时删除选定状态。如果这里有一个带有用户输入的文本的 <input>,那么这个被输入的文本将会被移除。
  65. <a name="v6ODA"></a>
  66. ## outerHTML:元素的完整 HTML
  67. **注意:与 innerHTML 不同,写入 outerHTML 不会改变元素。而是在 DOM 中替换它。**
  68. ```javascript
  69. <div>Hello, world!</div>
  70. <script>
  71. let div = document.querySelector('div');
  72. // 使用 <p>...</p> 替换 div.outerHTML
  73. div.outerHTML = '<p>A new element</p>'; // (*)
  74. // 蛤!'div' 还是原来那样!
  75. alert(div.outerHTML); // <div>Hello, world!</div> (**)
  76. </script>

在 div.outerHTML=… 中发生的事情是:

  • div 被从文档(document)中移除。
  • 另一个 HTML 片段

    A new element

    被插入到其位置上。
  • div 仍拥有其旧的值。新的 HTML 没有被赋值给任何变量。

    nodeValue/data:文本节点内容

    innerHTML 属性仅对元素节点有效。
    其他节点类型,例如文本节点,具有它们的对应项:nodeValue 和 data 属性。这两者在实际使用中几乎相同,只有细微规范上的差异。因此,我们将使用 data,因为它更短。

    textContent:纯文本

    “hidden” 属性

    “hidden” 特性(attribute)和 DOM 属性(property)指定元素是否可见。 ```javascript
    Both divs below are hidden
JavaScript assigned the property “hidden”

从技术上来说,hidden 与 style="display:none" 做的是相同的事。
<a name="D7alU"></a>
## 更多属性
大多数标准 HTML 特性(attribute)都具有相应的 DOM 属性,我们可以像这样访问它。<br />如果我们想知道给定类的受支持属性的完整列表,我们可以在规范中找到它们。例如,在 [https://html.spec.whatwg.org/#htmlinputelement](https://html.spec.whatwg.org/#htmlinputelement) 中记录了 HTMLInputElement。<br />如果我们想要快速获取它们,或者对具体的浏览器规范感兴趣 — 我们总是可以使用 console.dir(elem)输出元素并读取其属性。或者在浏览器的开发者工具的元素(Elements)标签页中探索“DOM 属性”。
<a name="SsDKn"></a>
## 总结
每个 DOM 节点都属于一个特定的类。这些类形成层次结构(hierarchy)。<br />完整的属性和方法集是继承的结果。
<a name="JLndR"></a>
# 特性和属性(Attributes and properties)
<a name="zmOfP"></a>
## DOM 属性
<a name="Q1bs9"></a>
## HTML 特性
HTML 特性有以下几个特征:

- 它们的名字是大小写不敏感的(id 与 ID 相同)。
- 它们的值总是字符串类型的。
<a name="N3J16"></a>
## 属性—特性同步
<a name="mpL5y"></a>
## DOM 属性是多类型的
<a name="vqtTl"></a>
## 非标准的特性,dataset
<a name="IB8du"></a>
# 修改文档(document)
<a name="MgFV9"></a>
## 创建一个元素
要创建 DOM 节点,这里有两种方法:<br />用给定的标签创建一个新 **元素节点(element node)**:
```javascript
let div = document.createElement('div');

用给定的文本创建一个 文本节点

let textNode = document.createTextNode('Here I am');

创建一条消息

// 1. 创建 <div> 元素
let div = document.createElement('div');

// 2. 将元素的类设置为 "alert"
div.className = "alert";

// 3. 填充消息内容
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

插入方法

append:document.body.append(div)
这里是更多的元素插入方法,指明了不同的插入位置:

  • node.append(…nodes or strings) —— 在 node 末尾 插入节点或字符串,
  • node.prepend(…nodes or strings) —— 在 node 开头 插入节点或字符串,
  • node.before(…nodes or strings) —— 在 node 前面 插入节点或字符串,
  • node.after(…nodes or strings) —— 在 node 后面 插入节点或字符串,
  • node.replaceWith(…nodes or strings) —— 将 node 替换为给定的节点或字符串。

    insertAdjacentHTML/Text/Element

    克隆节点:cloneNode

    样式和类

    通常有两种设置元素样式的方式:
  1. 在 CSS 中创建一个类,并添加它:
  2. 将属性直接写入 style:

JavaScript 既可以修改类,也可以修改 style 属性。

className 和 classList

元素样式

元素大小和滚动

JavaScript 中有许多属性可让我们读取有关元素宽度、高度和其他几何特征的信息。
我们在 JavaScript 中移动或定位元素时,我们会经常需要它们。

Window 大小和滚动

我们如何找到浏览器窗口(window)的宽度和高度呢?我们如何获得文档(document)的包括滚动部分在内的完整宽度和高度呢?我们如何使用 JavaScript 滚动页面?

窗口的 width/height

为了获取窗口(window)的宽度和高度,我们可以使用 document.documentElement 的 clientWidth/clientHeight:
不是 window.innerWidth/innerHeight
clientWidth/clientHeight 返回的是可用于内容的文档的可见部分的 width/height。
window.innerWidth/innerHeight 包括了滚动条。

文档的 width/height

为了可靠地获得完整的文档高度,我们应该采用以下这些属性的最大值:

let scrollHeight = Math.max(
  document.body.scrollHeight, document.documentElement.scrollHeight,
  document.body.offsetHeight, document.documentElement.offsetHeight,
  document.body.clientHeight, document.documentElement.clientHeight
);

alert('Full document height, with scrolled out part: ' + scrollHeight);

获得当前滚动

alert('Current scroll from the top: ' + window.pageYOffset);
alert('Current scroll from the left: ' + window.pageXOffset);

滚动:scrollTo,scrollBy,scrollIntoView

必须在 DOM 完全构建好之后才能通过 JavaScript 滚动页面。例如,如果我们尝试通过 中的脚本滚动页面,它将无法正常工作。
可以通过更改 scrollTop/scrollLeft 来滚动常规元素。
或者,有一个更简单的通用解决方案:使用特殊方法 window.scrollBy(x,y)window.scrollTo(pageX,pageY)

禁止滚动

要使文档不可滚动,只需要设置 document.body.style.overflow = “hidden”。该页面将“冻结”在其当前滚动位置上。

坐标

大多数 JavaScript 方法处理的是以下两种坐标系中的一个:

  1. 相对于窗口 — 类似于 position:fixed,从窗口的顶部/左侧边缘计算得出。
    • 我们将这些坐标表示为 clientX/clientY,当我们研究事件属性时,就会明白为什么使用这种名称来表示坐标。
  2. 相对于文档 — 与文档根(document root)中的 position:absolute 类似,从文档的顶部/左侧边缘计算得出。
    • 我们将它们表示为 pageX/pageY。

      元素坐标:getBoundingClientRect