Web前端基础知识:DOM/BOM/通信/杂项篇

420 阅读33分钟

先导

个人在学习过程中,涉及和遇见的一些基础知识,对其进行了简单归纳总结,浅尝辄止,略显杂而不精,做个人参考用

注:内容基本都是摘抄自博客、网络或MDN、现代JavaScript教程等,持续更新

DOM篇

1、DOM节点

Document结构

  • EventTarget — 是根的“抽象(abstract)”类。该类的对象从未被创建。它作为一个基础,以便让所有 DOM 节点都支持所谓的“事件(event)”

  • Node— 也是一个“抽象”类,充当 DOM 节点的基础。它提供了树的核心功能:parentNodenextSiblingchildNodes 等(它们都是 getter)。Node 类的对象从未被创建。

    有一些继承自它的具体的节点类,例如:文本节点的 Text,元素节点的 Element,以及更多异域(exotic)类,例如注释节点的 Comment

  • Element — 是 DOM 元素的基本类。它提供了元素级的导航(navigation),例如 nextElementSiblingchildren,以及像 getElementsByTagNamequerySelector 这样的搜索方法。

    浏览器中不仅有 HTML,还会有 XMLSVGElement 类充当更多特定类的基本类:SVGElement,XMLElement 和 HTMLElement。

  • HTMLElement — 最终是所有 HTML 元素的基本类。各种 HTML 元素均继承自它:

    • HTMLInputElement — <input> 元素的类,
    • HTMLBodyElement — <body> 元素的类,
    • HTMLAnchorElement — <a> 元素的类,
    • ……等,每个标签都有自己的类,这些类可以提供特定的属性和方法。

2、DOM遍历

1)顶层documentElement body

最顶层的树节点可以直接作为 document 的属性来使用

  • <html> = document.documentElement

  • <body> = document.body

  • <head> = document.head

2)子节点:childNodesfirstChildlastChild

  • 子节点: 对应的是直系的子元素。换句话说,它们被完全嵌套在给定的元素中。例如,<head><body> 就是 <html> 元素的子元素。

  • 子孙元素:嵌套在给定元素中的所有元素,包括子元素,以及子元素的子元素等。

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

  • firstChild lastChild 属性是访问第一个和最后一个子元素的快捷方式。

3)DOM集合

childNodes是一个类数组的可迭代对象,可以通过 for..of来遍历,但没有数组方法

特性:只读、实时

4)兄弟节点和父节点

兄弟节点(Sibling) 是指有同一个父节点的节点

  • nextSibling :下一个兄弟节点

  • previousSibling :上一个兄弟节点

  • parentNode :父节点

5)只关注元素节点(在词中间加了 Element

  • children — 仅那些作为元素节点的子代的节点。
  • firstElementChildlastElementChild — 第一个和最后一个子元素。
  • previousElementSiblingnextElementSibling — 兄弟元素。
  • parentElement — 父元素。

! 为什么是 parentElement? 父节点可以不是一个元素吗?

parentElement 属性返回的是“元素类型”的父节点,而 parentNode 返回的是“任何类型”的父节点。这些属性通常来说是一样的:它们都是用于获取父节点。

唯一的例外就是 document.documentElement

alert( document.documentElement.parentNode ); // document
alert( document.documentElement.parentElement ); // null

因为根节点 document.documentElement<html>)的父节点是 document。但 document 不是一个元素节点,所以 parentNode 返回了 document,但 parentElement 返回的是 null

当我们想从任意节点 elem <html> 而不是到 document 时,这个细节可能很有用:

while(elem = elem.parentElement) { // 向上,直到 `<html>`
  alert( elem );
}

3、获取元素

  1. document.getElementById(id) 或者只使用id:给元素添加id相当于给全局对象添加了一个属性

(比如 window['elem-content']

注:不要使用以 id 命名的全局变量来访问元素

  1. elem.querySelectorAll(css):返回 elem 中与给定 CSS 选择器匹配的所有元素。
  2. elem.querySelector(css):返回给定 CSS 选择器的第一个元素。
  3. elem.matches(css):不会查找任何内容,它只会检查 elem 是否与给定的 CSS 选择器匹配。它返回 true false
  4. elem.closest(css)方法会查找与 CSS 选择器匹配的最近的祖先。elem自己也会被搜索
  5. 旧版本遗留:
  • elem.getElementsByTagName(tag) 查找具有给定标签的元素,并返回它们的集合。tag 参数也可以是对于“任何标签”的星号 "*"。
  • elem.getElementsByClassName(className) 返回具有给定CSS类的元素。
  • document.getElementsByName(name) 返回在文档范围内具有给定 name 特性的元素。很少使用。

4、节点属性:typetagcontent

(1)tagName 和 nodeName 之间区别

  • tagName 属性仅适用于 Element 节点。
  • nodeName 是为任意 Node 定义的:
    • 对于元素,它的意义与 tagName 相同。
    • 对于其他节点类型(text,comment 等),它拥有一个对应节点类型的字符串

(2)innerHTML 和 outerHTML

outerHTML 属性包含了元素的完整 HTML。就像 innerHTML 加上元素本身一样

innerHTML 不同,写入 outerHTML 不会改变元素。而是在 DOM 中替换它

注:“innerHTML+=” 会进行完全重写

(3)总结

每个 DOM 节点都属于一个特定的类。这些类形成层次结构(hierarchy)。完整的属性和方法集是继承的结果。

主要的 DOM 节点属性有:

  • nodeType

    我们可以使用它来查看节点是文本节点还是元素节点。它具有一个数值型值(numeric value):1 表示元素,3 表示文本节点,其他一些则代表其他节点类型。只读

  • nodeName/tagName

    用于元素名,标签名(除了 XML 模式,都要大写)。对于非元素节点,nodeName 描述了它是什么。只读。

  • innerHTML

    元素的 HTML 内容。可以被修改。

  • outerHTML

    元素的完整 HTML。对 elem.outerHTML 的写入操作不会触及 elem 本身。而是在外部上下文中将其替换为新的 HTML。

  • nodeValue/data

非元素节点(文本、注释)的内容。两者几乎一样,我们通常使用 data。可以被修改。

  • textContent

    元素内的文本:HTML 减去所有 <tags>。写入文本会将文本放入元素内,所有特殊字符和标签均被视为文本。可以安全地插入用户生成的文本,并防止不必要的 HTML 插入。

  • hidden

    当被设置为 true 时,执行与 CSS display:none 相同的事。

DOM 节点还具有其他属性,具体有哪些属性则取决于它们的类

例如,<input> 元素(HTMLInputElement)支持 valuetype,而 <a> 元素(HTMLAnchorElement)则支持 href等。大多数标准 HTML 特性(attribute)都具有相应的 DOM 属性


5、属性与特性(attribute 和 property)

  • 特性attribute)— 写在 HTML 中的内容。
  • 属性(property)— DOM 对象中的内容。

所有以 “data-” 开头的特性均被保留供程序员使用。它们可在 dataset 属性中使用

1)DOM属性:

DOM 节点是常规的 JavaScript 对象,可以修改和新增其属性

2)HTML特性:

标签拥有特性(attributes),浏览器解析 HTML 文本,并根据标签创建 DOM 对象时,浏览器会辨别 标准的 特性并以此创建 DOM 属性。即当一个元素有 id 或其他 标准的 特性,那么就会生成对应的 DOM 属性。但是非 标准的 特性则不会。

一个元素的标准的特性对于另一个元素可能是未知的。

所有特性都可以通过使用以下方法进行访问:

  • elem.hasAttribute(name) — 检查特性是否存在。
  • elem.getAttribute(name) — 获取这个特性值。
  • elem.setAttribute(name, value) — 设置这个特性值。
  • elem.removeAttribute(name) — 移除这个特性。

HTML 特性有以下几个特征:

  • 它们的名字是大小写不敏感的(id 与 ID 相同)。
  • 它们的值总是字符串类型的。

3)属性—特性同步:

当一个标准的特性被改变,对应的属性也会自动更新,(除了几个特例)反之亦然。

4)DOM 属性是多类型的:

DOM 属性不总是字符串类型的

5)非标准(自定义)特性

所有以 “data-” 开头的特性均被保留供程序员使用。它们可在 dataset 属性中使用。

data-order-state 这样的多词特性可以以驼峰式进行调用:dataset.orderState


6、修改Document

1)创建元素

用给定的标签创建一个新元素节点(element node):document.createElement(tag)

用给定的文本创建一个 文本节点:document.createTextNode(text)

2)插入

append方法:elem.append(tag),例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 替换为给定的节点或字符串。

这些方法的参数可以是一个要插入的任意的 DOM 节点列表,或者文本字符串(会被自动转换成文本节点)。

即这些方法只能用来插入 DOM 节点或文本片段。

3)将内容“作为 HTML 代码插入”

可以使用另一个非常通用的方法:elem.insertAdjacentHTML(where, html)

第一个参数是代码字(code word) ,指定相对于 elem 的插入位置。必须为以下之一:

  • "beforebegin" — 将 html 插入到 elem 前插入,
  • "afterbegin" — 将 html 插入到 elem 开头,
  • "beforeend" — 将 html 插入到 elem 末尾,
  • "afterend" — 将 html 插入到 elem 后。

第二个参数是 HTML 字符串,该字符串会被“作为 HTML” 插入。

示意图:

  • elem.insertAdjacentText(where, text) — 语法一样,但是将 text 字符串“作为文本”插入而不是作为 HTML,
  • elem.insertAdjacentElement(where, elem) — 语法一样,但是插入的是一个元素。

4)节点移除

想要移除一个节点,可以使用node.remove()

注:所有插入方法都会自动从旧位置删除该节点。

5)克隆节点:cloneNode

调用 elem.cloneNode(true) 来创建元素的一个“深”克隆 — 具有所有特性(attribute)和子元素。

如果我们调用 elem.cloneNode(false),那克隆就不包括子元素。

6)DocumentFragment

DocumentFragment 是一个特殊的 DOM 节点,用作来传递节点列表的包装器(wrapper)

我们可以向其附加其他节点,但是当我们将其插入某个位置时,则会插入其内容。

使用:let fragment = new DocumentFragment();

7) 老式的 insert/remove 方法

  • parentElem.appendChild(node)

    node 附加为 parentElem 的最后一个子元素。

  • parentElem.insertBefore(node, nextSibling)

    parentElem nextSibling 前插入 node

  • parentElem.replaceChild(node, oldChild)

    parentElem 的后代中的 oldChild 替换为 node

  • parentElem.removeChild(node)

    parentElem 中删除 node(假设 node parentElem 的后代)。

8)document.write

调用 document.write(html) 意味着将 html “就地马上”写入页面。

document.write 调用只在页面加载时工作。


7、样式和类

elem.className 对应于"class" 特性

elem.classList 是一个特殊的对象,它具有 add/remove/toggle 单个类的方法。

  • elem.classList.add/remove(class) — 添加/移除类。
  • elem.classList.toggle(class) — 如果类不存在就添加类,存在就移除它。
  • elem.classList.contains(class) — 检查给定类,返回 true/false。

elem.style 属性是一个对象,它对应于 "style" 特性(attribute)中所写的内容。

重置样式属性:不要用delete,而是将其赋值为空

修改样式要加上单位(px等)

计算样式:

`getComputedStyle(element, [pseudo])`

// element:需要被读取样式值的元素。
// pseudo:伪元素(如果需要),例如`::before`。空字符串或无参数则意味着元素本身。
// 结果:一个具有样式属性的对象,像 `elem.style`

在 CSS 中有两个概念:

  1. 计算 (computed) 样式值是所有 CSS 规则和 CSS 继承都应用后的值,这是 CSS 级联(cascade)的结果。它看起来像 height:1em 或 font-size:125%。
  2. 解析 (resolved) 样式值是最终应用于元素的样式值值。诸如 1em 或 125% 这样的值是相对的。浏览器将使用计算(computed)值,并使所有单位均为固定的,且为绝对单位,例如:height:20px 或 font-size:16px。对于几何属性,解析(resolved)值可能具有浮点,例如:width:50.5px。 很久以前,创建了 getComputedStyle 来获取计算(computed)值,但事实证明,解析(resolved)值要方便得多,标准也因此发生了变化。 所以,现在 getComputedStyle 实际上返回的是属性的解析值(resolved)。

8、元素大小和滚动

几何示意图:

几何示意图:

  • offsetParent — 最接近的 CSS 定位((position absoluterelative fixed))的祖先,或者是 <td><th><table><body>

  • offsetLeft/offsetTop — 是相对于 offsetParent 的左上角边缘的坐标。

  • offsetWidth/offsetHeight — 元素的“外部”宽高, width/height + 边框(border)+ padding。

  • clientLeft/clientTop — 从元素左上角外角到左上角内角的距离。对于从左到右显示内容的操作系统来说,它们始终是左侧/顶部 border 的宽度。而对于从右到左显示内容的操作系统来说,垂直滚动条在左边,所以 clientLeft 也包括滚动条的宽度。

  • clientWidth/clientHeight — 内容的 width/height,包括 padding,但不包括滚动条(scrollbar)和border。

  • scrollWidth/scrollHeight — 内容的 width/height,就像 clientWidth/clientHeight 一样,但还包括元素的滚动出的不可见的部分。

  • scrollLeft/scrollTop — 从元素的左上角开始,滚动出元素的上半部分的 width/height。

除了 scrollLeft/scrollTop 外,所有属性都是只读的。如果我们修改 scrollLeft/scrollTop,浏览器会滚动对应的元素。


9、Window 大小和滚动

几何:

文档可见部分的 width/height(内容区域的 width/height):

document.documentElement.clientWidth/clientHeight

整个文档的 width/height,其中包括滚动出去的部分

因为要考虑到滚动条

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

10、坐标

1)坐标系

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

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

当文档滚动时:

  • pageY — 元素在文档中的相对坐标保持不变,从文档顶部(现在已滚动出去)开始计算。
  • clientY — 窗口相对坐标确实发生了变化(箭头变短了),因为同一个点越来越靠近窗口顶部。

2)元素坐标:

方法elem.getBoundingClientRect() 返回最小矩形的窗口坐标,该矩形将 elem 作为内建 DOMRect 类的对象。

主要的 DOMRect 属性:

  • x/y — 矩形原点相对于窗口的 X/Y 坐标,
  • width/height — 矩形的 width/height(可以为负)。

此外,还有派生(derived)属性:

  • top/bottom — 顶部/底部矩形边缘的 Y 坐标,
  • left/right — 左/右矩形边缘的 X 坐标。

elementFromPoint(x, y)

document.elementFromPoint(x, y) 的调用会返回在窗口坐标 (x, y) 处嵌套最多(the most nested)的元素。

页面上的任何点都有坐标

  1. 相对于窗口的坐标 — elem.getBoundingClientRect()
  2. 相对于文档的坐标 — elem.getBoundingClientRect() 加上当前页面滚动

窗口坐标非常适合和 position:fixed 一起使用,文档坐标非常适合和 position:absolute 一起使用。

3)当前滚动:

读取当前的滚动:

window.pageYOffset/pageXOffset

更改当前的滚动:

  • window.scrollTo(pageX,pageY) — 绝对坐标,
  • window.scrollBy(x,y) — 相对当前位置进行滚动,
  • elem.scrollIntoView(top) — 滚动以使 elem 可见(elem 与窗口的顶部/底部对齐)

11、页面生命周期

HTML 页面的生命周期包含三个重要事件:

  • DOMContentLoaded —— 浏览器已完全加载 HTML,并构建了 DOM 树,但像 和样式表之类的外部资源可能尚未加载完成。
  • load —— 浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等。
  • beforeunload/unload —— 当用户正在离开页面时。

页面生命周期事件:

  • 当 DOM 准备就绪时,document 上的 DOMContentLoaded 事件就会被触发。在这个阶段,我们可以将 JavaScript 应用于元素。

    • 诸如 <script>...</script> <script src="..."></script> 之类的脚本会阻塞 DOMContentLoaded,浏览器将等待它们执行结束。

    不会阻塞 DOMContentLoaded 的脚本:

    • 具有 async 特性(attribute)的脚本不会阻塞 DOMContentLoaded,稍后 我们会讲到。
    • 使用 document.createElement('script') 动态生成并添加到网页的脚本也不会阻塞 DOMContentLoaded
    • 图片和其他资源仍然可以继续被加载。

    外部样式表不会影响 DOM,一般 DOMContentLoaded 不会等待它们。但是如果在样式后面有一个脚本,那么该脚本必须等待样式表加载完成:原因是,脚本可能想要获取元素的坐标和其他与样式相关的属性,这就会导致DOMContentLoaded 阻塞

  • 当页面和所有资源都加载完成时,window 上的 load 事件就会被触发。我们很少使用它,因为通常无需等待那么长时间。

  • 当用户想要离开页面时,window 上的 beforeunload 事件就会被触发。如果我们取消这个事件,浏览器就会询问我们是否真的要离开(例如,我们有未保存的更改)。

  • 当用户最终离开时,window 上的 unload 事件就会被触发。在处理程序中,我们只能执行不涉及延迟或询问用户的简单操作。正是由于这个限制,它很少被使用。我们可以使用 navigator.sendBeacon 来发送网络请求。

  • document.readyState 是文档的当前状态,可以在 readystatechange 事件中跟踪状态更改:

    • loading —— 文档正在被加载。
    • interactive —— 文档已被解析完成,与 DOMContentLoaded 几乎同时发生,但是在 DOMContentLoaded 之前发生。
    • complete —— 文档和资源均已加载完成,与 window.onload 几乎同时发生,但是在 window.onload 之前发生。

12、DOM 变动观察器(Mutation observer)

MutationObserver 是一个内建对象,它观察 DOM 元素,并在检测到更改时触发回调。

1)语法:

// 创建
let observer = new MutationObserver(callback);

//附加到某个DOM节点
observer.observe(node, config);

config 是一个具有布尔选项的对象,该布尔选项表示“将对哪些更改做出反应”:

  • childList —— node 的直接子节点的更改,
  • subtree —— node 的所有后代的更改,
  • attributes —— node 的特性(attribute),
  • attributeFilter —— 特性名称数组,只观察选定的特性。
  • characterData —— 是否观察 node.data(文本内容),

其他几个选项:

  • attributeOldValue —— 如果为 true,则将特性的旧值和新值都传递给回调(参见下文),否则只传新值(需要 attributes 选项),
  • characterDataOldValue —— 如果为 true,则将 node.data 的旧值和新值都传递给回调(参见下文),否则只传新值(需要 characterData 选项)。

2)回调

然后,在发生任何更改后,将执行“回调”:更改被作为一个 MutationRecord 对象列表传入第一个参数,而观察器自身作为第二个参数。

MutationRecord 对象具有以下属性:

  • type —— 变动类型,以下类型之一:
    • "attributes":特性被修改了,
    • "characterData":数据被修改了,用于文本节点,
    • "childList":添加/删除了子元素。
  • target —— 更改发生在何处:"attributes" 所在的元素,或 "characterData" 所在的文本节点,或 "childList" 变动所在的元素,
  • addedNodes/removedNodes —— 添加/删除的节点,
  • previousSibling/nextSibling —— 添加/删除的节点的上一个/下一个兄弟节点,
  • attributeName/attributeNamespace —— 被更改的特性的名称/命名空间(用于 XML),
  • oldValue —— 之前的值,仅适用于特性或文本更改,如果设置了相应选项 attributeOldValue/characterDataOldValue

3)示例:

这里有一个 <div>,它具有 contentEditable 特性。该特性使我们可以聚焦和编辑元素。

<div contentEditable id="elem">Click and <b>edit</b>, please</div>

<script>
let observer = new MutationObserver(mutationRecords => {
  console.log(mutationRecords); // console.log(the changes)
});

// 观察除了特性之外的所有变动
observer.observe(elem, {
  childList: true, // 观察直接子节点
  subtree: true, // 及其更低的后代节点
  characterDataOldValue: true // 将旧的数据传递给回调
});
</script>

// 打印结果:
mutationRecords = [{
  type: "characterData",
  oldValue: "edit",
  target: <text node>,
  // 其他属性为空
}];

// 如果进行更复杂的编辑操作,例如删除 <b>edit</b>,那么变动事件可能会包含多个变动记录:
mutationRecords = [{
  type: "childList",
  target: <div#elem>,
  removedNodes: [<b>],
  nextSibling: <text node>,
  previousSibling: <text node>
  // 其他属性为空
}, {
  type: "characterData"
  target: <text node>
  // ...变动的详细信息取决于浏览器如何处理此类删除
  // 它可能是将两个相邻的文本节点 "edit " 和 ", please" 合并成一个节点,
  // 或者可能将它们留在单独的文本节点中
}];

4)停止观察节点:

  • observer.disconnect() —— 停止观察。

当我们停止观察时,观察器可能尚未处理某些更改。在种情况下,我们使用:

  • observer.takeRecords() —— 获取尚未处理的变动记录列表,表中记录的是已经发生,但回调暂未处理的变动。

13、Frame 和 window

1) Frame和window

(1)创建、打开一个窗口

window.open(url, name, params)

参数:

  • url

    要在新窗口中加载的 URL。

  • name

    新窗口的名称。每个窗口都有一个 window.name,在这里我们可以指定哪个窗口用于弹窗。如果已经有一个这样名字的窗口 —— 将在该窗口打开给定的 URL,否则会打开一个新窗口。

  • params

    新窗口的配置字符串。它包括设置,用逗号分隔。参数之间不能有空格,例如:width=200,height=100

    params 的设置项:

    • 位置:

      • left/top(数字)—— 屏幕上窗口的左上角的坐标。这有一个限制:不能将新窗口置于屏幕外(offscreen)。
      • width/height(数字)—— 新窗口的宽度和高度。宽度/高度的最小值是有限制的,因此不可能创建一个不可见的窗口。
    • 窗口功能:

      • menubar(yes/no)—— 显示或隐藏新窗口的浏览器菜单。
      • toolbar(yes/no)—— 显示或隐藏新窗口的浏览器导航栏(后退,前进,重新加载等)。
      • location(yes/no)—— 显示或隐藏新窗口的 URL 字段。Firefox 和 IE 浏览器不允许默认隐藏它。
      • status(yes/no)—— 显示或隐藏状态栏。同样,大多数浏览器都强制显示它。
      • resizable(yes/no)—— 允许禁用新窗口大小调整。不建议使用。
      • scrollbars(yes/no)—— 允许禁用新窗口的滚动条。不建议使用。

(2)窗口访问弹窗

open 调用会返回对新窗口的引用。它可以用来操纵弹窗的属性,更改位置,甚至更多操作。

let newWin = window.open("about:blank", "hello", "width=200,height=200"

(3)弹窗访问窗口

window.opener,除了弹窗之外,对其他所有窗口来说,window.opener 均为 null

(4)关闭

关闭一个窗口:win.close(),检查一个窗口是否被关闭:win.closed

(5)移动调整窗口大小

win.moveBy(x,y)

将窗口相对于当前位置向右移动 x 像素,并向下移动 y 像素。允许负值(向上/向左移动)。

win.moveTo(x,y)

将窗口移动到屏幕上的坐标 (x,y)处。

win.resizeBy(width,height)

根据给定的相对于当前大小的 width/height 调整窗口大小。允许负值。

win.resizeTo(width,height)

将窗口调整为给定的大小。

还有 window.onresize事件。

(6)滚动窗口:

win.scrollBy(x,y)

相对于当前位置,将窗口向右滚动 x 像素,并向下滚动 y 像素。允许负值。

win.scrollTo(x,y)

将窗口滚动到给定坐标 (x,y)

elem.scrollIntoView(top = true)

滚动窗口,使 elem 显示在 elem.scrollIntoView(false) 的顶部(默认)或底部。

这里也有 window.onscroll 事件。

2) iframe

优点:

  • iframe能够原封不动的把嵌入的网页展现出来。
  • 如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
  • 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
  • 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。

缺点:

  • iframe会阻塞主页面的onload事件
  • iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。会产生很多页面,不容易管理。
  • iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
  • 代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化(SEO)。
  • 很多的移动设备无法完全显示框架,设备兼容性差。
  • iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。

建议:通过 javascript动态给iframe添加 src 属性值,这样可以绕开以上一些问题。

3)跨窗口通信

一个 <iframe> 标签承载了一个单独的嵌入的窗口,它具有自己的 document window

我们可以使用以下属性访问它们:

  • iframe.contentWindow来获取 <iframe> 中的 window。
  • iframe.contentDocument来获取 <iframe> 中的 document,是 iframe.contentWindow.document 的简写形式。

iframe.onload 事件(在 <iframe> 标签上)与 iframe.contentWindow.onload(在嵌入的 window 对象上)基本相同。当嵌入的窗口的所有资源都完全加载完毕时触发。

  1. 二级域名相同可以视为同源,通过类似document.domain = 'site.com';进行调整

  2. 集合:window.frames

获取 <iframe> 的 window 对象的另一个方式是从命名集合 window.frames中获取:

  • 通过索引获取:window.frames[0] —— 文档中的第一个 iframe 的 window 对象。
  • 通过名称获取:window.frames.iframeName —— 获取 name="iframeName" 的 iframe 的 window 对象。

一个 iframe 内可能嵌套了其他的 iframe。相应的 window对象会形成一个层次结构(hierarchy)。

可以通过以下方式获取:

  • window.frames —— “子”窗口的集合(用于嵌套的 iframe)。
  • window.parent —— 对“父”(外部)窗口的引用。
  • window.top —— 对最顶级父窗口的引用。
  1. 通信(postMessage

发送:win.postMessage(data, targetOrigin)

参数:

  • data

    要发送的数据。可以是任何对象,数据会被通过使用“结构化序列化算法(structured serialization algorithm)”进行克隆。IE 浏览器只支持字符串,因此我们需要对复杂的对象调用JSON.stringify 方法进行处理,以支持该浏览器。

  • targetOrigin

    指定目标窗口的源,以便只有来自给定的源的窗口才能获得该消息。

  • targetOrigin

    是一种安全措施。请记住,如果目标窗口是非同源的,我们无法在发送方窗口读取它的 location。因此,我们无法确定当前在预期的窗口中打开的是哪个网站:用户随时可以导航离开,并且发送方窗口对此一无所知。

    指定 targetOrigin 可以确保窗口仅在当前仍处于正确的网站时接收数据。在有敏感数据时,这非常重要。

接收:

  • addEventListener 监听message事件

    window.addEventListener("message", function(event){ })

event对象属性:

  • data

    从 postMessage 传递来的数据。字符串对象

  • origin

    发送方的源,例如 http://javascript.info

  • source

    对发送方窗口的引用。如果我们想,我们可以立即 source.postMessage(...) 回去。

BOM/浏览器篇

1、BOM

BOM (Browser Object Model),浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象

浏览器的全部内容可以看成DOM,整个浏览器可以看成BOM。区别如下

BOM对象:

  1. window:核心对象,表示浏览器的一个实例,也是全局对象
  2. location:url地址,location.reload(),可以重新刷新当前页面
  3. navigator:获取浏览器的属性,区分浏览器类型
  4. screen:保存的纯粹是客户端能力信息,即浏览器窗口外的客户端显示器信息,比如像素宽度和像素高度
  5. history:操作浏览器URL的历史记录
  • history.go():接收一个整数数字或者字符串参数:
    参数为字符串:向最近的一个记录中包含指定字符串的页面跳转,
    参数为整数数字的时候,正数向前,负数向后
  • history.forward():向前跳转一个页面
  • history.back():向后跳转一个页面
  • history.length:获取历史记录数

2、Window对象

window对象表示浏览器打开的窗口

如果文档包含框架(frameiframe 标签),浏览器会为 HTML 文档创建一个 window 对象,并为每个框架创建一个额外的 window 对象。

在客户端 JavaScript 中,Window 对象是全局对象,所有的表达式都在当前的环境中计算

关于top/parent/self/opener

在应用有frameset或者iframe的页面时,

  • parent是父窗口,

  • top是最顶级父窗口(有的窗口中套了好几层frameset或者iframe),

  • self是当前窗口,

  • opener是用window.open方法打开当前窗口的那个窗口引用

网络请求篇

1、Fetch

1)基本语法:

let promise = fetch(url, [options])

/*
  url —— 要访问的 URL。
  options —— 可选参数:method,header 等。
*/

2)获取响应:

  • 当服务器发送了响应头(response header), fetch 返回的 promise 就使用内建的 Response class 对象来对响应头进行解析

    这个阶段可以查看响应头,判断请求是否成功,但还没有响应体

    • response.status —— response 的 HTTP 状态码,
    • response.ok —— HTTP 状态码为 200-299,则为 true。
    • response.headers —— 类似于 Map 的带有 HTTP header 的对象。

(1)获取响应体(response body)

Response 提供了多种基于 promise的方法,来以不同的格式访问 body:

  • response.text() —— 读取 response,并以文本形式返回 response,
  • response.json() —— 将 response 解析为 JSON,
  • response.formData() —— 以 FormData 对象的形式返回 response,
  • response.blob() —— 以 Blob(具有类型的二进制数据)形式返回 response,
  • response.arrayBuffer() —— 以 ArrayBuffer(低级别的二进制数据)形式返回 response,

另外,response.bodyReadableStream 对象,它允许你逐块读取 body

(2)获取响应头

response.headers:类似于 Map 的 header 对象,可以迭代遍历。

3)发送请求

(1)设置请求头

使用headers选项

let response = fetch(protectedUrl, {
  headers: {
    Authentication: 'secret'
  }
});

4)示例

(1)POST请求

(async () => {
  let user = {
    name: 'John',
    surname: 'Smith'
  };
  
  let response = await fetch('/article/fetch/post/user', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json;charset=utf-8'	// 因为发送的是JSON数据
    },
    body: JSON.stringify(user)
  });
  
  let result = await response.json();
  alert(result.message);
})()

5)FormData

FormData 对象用于捕获 HTML 表单

可以从 HTML 表单创建 new FormData(form),也可以创建一个完全没有表单的对象,然后使用以下方法附加字段:

  • formData.append(name, value)
  • formData.append(name, blob, fileName)
  • formData.set(name, value)
  • formData.set(name, blob, fileName)

6)中止请求

AbortController 对象:let controller = new AbortController();

controller:它具有单个方法 abort(),和单个属性 signal,

abort() 被调用时:

  • controller.signal 就会触发 abort 事件。
  • controller.signal.aborted 属性变为 true。

关联fetch:AbortController signal 属性作为 fetch 的一个可选参数(option)进行传递:

let controller = new AbortController();
fetch(url, {
  signal: controller.signal
});

AbortController 是可伸缩的,它允许一次取消多个 fetch。


7)全部配置选项

let promise = fetch(url, {
  method: "GET", // POST,PUT,DELETE,等。
  headers: {
    // 内容类型 header 值通常是自动设置的
    // 取决于 request body
    "Content-Type": "text/plain;charset=UTF-8"
  },
  body: undefined // string,FormData,Blob,BufferSource,或 URLSearchParams
  referrer: "about:client", // 或 "" 以不发送 Referer header,
  // 或者是当前源的 url
  referrerPolicy: "no-referrer-when-downgrade", // no-referrer,origin,same-origin...
  mode: "cors", // same-origin,no-cors
  credentials: "same-origin", // omit,include
  cache: "default", // no-store,reload,no-cache,force-cache,或 only-if-cached
  redirect: "follow", // manual,error
  integrity: "", // 一个 hash,像 "sha256-abcdef1234567890"
  keepalive: false, // true
  signal: undefined, // AbortController 来中止请求
  window: window // null
});

2、URL对象

用于编码/解码 URL 的内建函数:

  • encodeURI —— 编码整个 URL。
  • decodeURI —— 解码为编码前的状态。
  • encodeURIComponent —— 编码 URL 组件/参数,例如搜索参数,或者 hash,或者 pathname。
  • decodeURIComponent —— 解码为编码前的状态。

3、XMLHttpRequest

1)基础

XMLHttpRequest可以跟踪上传进度,而fetch不行

使用 XMLHttpRequest的典型代码:

let xhr = new XMLHttpRequest();

xhr.open('GET', '/my/url');

xhr.responseType = 'json'; // 显示设置响应格式:
// 可选类型:'text'/'arraybuffer'/'blob'/'document'/'json'

xhr.send();

xhr.onload = function() {
  if (xhr.status != 200) { // HTTP error?
    // 处理 error
    alert( 'Error: ' + xhr.status);
    return;
  }

  // 获取来自 xhr.response 的响应
  console.log(xhr.response)
};

// 
xhr.onprogress = function(event) {
  // 报告进度
  alert(`Loaded ${event.loaded} of ${event.total}`);
};

xhr.onerror = function() {
  // 处理非 HTTP error(例如网络中断)
};

/*
UNSENT = 0; // 初始状态
OPENED = 1; // open 被调用
HEADERS_RECEIVED = 2; // 接收到 response header
LOADING = 3; // 响应正在被加载(接收到一个数据包)
DONE = 4; // 请求完成
*/
xhr.onreadystatechange = function() {
  if (xhr.readyState == 3) {
    // 加载中
  }
  if (xhr.readyState == 4) {
    // 请求完成
  }
};

// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/json');
// 获取响应头
xhr.getResponseHeader('Content-Type')
xhr.getAllResponseHeaders() // header 之间的换行符始终为 "\r\n"

2)上传下载

progress 事件仅在下载阶段触发,上传进度跟踪使用 xhr.upload

它会生成事件,类似于 xhr,但是 xhr.upload 仅在上传时触发它们:

  • loadstart —— 上传开始。
  • progress —— 上传期间定期触发。
  • abort —— 上传中止。
  • error —— 非 HTTP 错误。
  • load —— 上传成功完成。
  • timeout —— 上传超时(如果设置了 timeout 属性)。
  • loadend —— 上传完成,无论成功还是 error。

示例:带有进度指示的文件上传:

<input type="file" onchange="upload(this.files[0])">

<script>
function upload(file) {
  let xhr = new XMLHttpRequest();

  // 跟踪上传进度
  xhr.upload.onprogress = function(event) {
    console.log(`Uploaded ${event.loaded} of ${event.total}`);
  };

  // 跟踪完成:无论成功与否
  xhr.onloadend = function() {
    if (xhr.status == 200) {
      console.log("success");
    } else {
      console.log("error " + this.status);
    }
  };

  xhr.open("POST", "/article/xmlhttprequest/post/upload");
  xhr.send(file);
}
</script>

3)跨源请求:

xhr.withCredentials 设置为 true

4)AJAX

过程:

  1. 创建XMLHttpRequest对象;
  2. 调用open方法传入三个参数 请求方式(GET/POST)url同步异步(true/false);
  3. 监听onreadystatechange事件,当readystate等于4时返回responseText;
  4. 调用send方法传递参数。
  5. 通过 XMLHttpRequest对象提供的 onreadystatechange事件监听服务器端你的通信状态
  6. 接受并处理服务端向客户端响应的数据结果
  7. 将处理结果更新到 HTML页面中

完整示例:

// 1. 创建一个 new XMLHttpRequest 对象
let xhr = new XMLHttpRequest();

// 2. 配置它:从 URL /article/.../load GET-request
xhr.open('GET', '/article/xmlhttprequest/example/load');

// 3. 通过网络发送请求
xhr.send();

// 4. 当接收到响应后,将调用此函数
xhr.onload = function() {
  if (xhr.status != 200) { // 分析响应的 HTTP 状态
    alert(`Error ${xhr.status}: ${xhr.statusText}`); // 例如 404: Not Found
  } else { // 显示结果
    alert(`Done, got ${xhr.response.length} bytes`); // response 是服务器响应
  }
};

// 在下载响应期间定期触发,报告已经下载了多少
xhr.onprogress = function(event) {
  if (event.lengthComputable) {
    alert(`Received ${event.loaded} of ${event.total} bytes`);
  } else {
    alert(`Received ${event.loaded} bytes`); // 没有 Content-Length
  }

};

// 当无法发出请求,例如网络中断或者无效的 URL。
xhr.onerror = function() {
  alert("Request failed");
};

4、恢复文件上传

xhr.upload.onprogress 来跟踪上传进度,但不会帮助我们在此处恢复上传,因为它会在数据 被发送 时触发,但是服务器是否接收到了?浏览器并不知道。

所以要恢复上传,我们需要 确切地 知道服务器接收的字节数。而且只有服务器能告诉我们,因此,我们将发出一个额外的请求。

算法:

1. 首先,创建一个文件 id,以唯一地标识我们要上传的文件:

let fileId = file.name + '-' + file.size + '-' + +file.lastModifiedDate;

在恢复上传时需要用到它,以告诉服务器我们要恢复的内容。

如果名称,或大小,或最后一次修改事件发生了更改,则将有另一个 fileId

2. 向服务器发送一个请求,询问它已经有了多少字节,像这样:

let response = await fetch('status', {
  headers: {
    'X-File-Id': fileId
  }
});

// 服务器已有的字节数
let startByte = +await response.text();

这假设服务器通过 X-File-Id header 跟踪文件上传。应该在服务端实现。

如果服务器上尚不存在该文件,则服务器响应应为 0。

3. 然后,我们可以使用 Blob slice 方法来发送从 startByte 开始的文件:

xhr.open("POST", "upload", true);

// 文件 id,以便服务器知道我们要恢复的是哪个文件
xhr.setRequestHeader('X-File-Id', fileId);

// 发送我们要从哪个字节开始恢复,因此服务器知道我们正在恢复
xhr.setRequestHeader('X-Start-Byte', startByte);

xhr.upload.onprogress = (e) => {
  console.log(`Uploaded ${startByte + e.loaded} of ${startByte + e.total}`);
};

// 文件可以是来自 input.files[0],或者另一个源
xhr.send(file.slice(startByte));

这里我们将文件 id 作为 X-File-Id 发送给服务器,所以服务器知道我们正在上传哪个文件,并且,我们还将起始字节作为 X-Start-Byte 发送给服务器,所以服务器知道我们不是重新上传它,而是恢复其上传。

服务器应该检查其记录,如果有一个上传的该文件,并且当前已上传的文件大小恰好是 X-Start-Byte,那么就将数据附加到该文件。


5、长轮询实现

客户端

async function subscribe() {
  let response = await fetch("/subscribe");

  if (response.status == 502) {
    // 状态 502 是连接超时错误,
    // 连接挂起时间过长时可能会发生,
    // 远程服务器或代理会关闭它
    // 让我们重新连接
    await subscribe();
  } else if (response.status != 200) {
    // 一个 error —— 让我们显示它
    showMessage(response.statusText);
    // 一秒后重新连接
    await new Promise(resolve => setTimeout(resolve, 1000));
    await subscribe();
  } else {
    // 获取并显示消息
    let message = await response.text();
    showMessage(message);
    // 再次调用 subscribe() 以获取下一条消息
    await subscribe();
  }
}

subscribe();

6、Websocket

由于Http的单向请求,客户端只能轮询去获取服务器更新信息,效率低且浪费资源,所以发明websocket,全双工通信,HTML5引入

特点:

  • 握手阶段采用 HTTP 协议
  • 没有同源限制,客户端可以与任意服务器通信。

1) 创建:

let socket = new WebSocket("ws://javascript.info");(始终使用 wss://

2)监听:

  • open —— 连接已建立,
  • message —— 接收到数据,
  • error —— WebSocket 错误,
  • close —— 连接已关闭。

3)发送数据:

socket.send(data)

4)示例:

let socket = new WebSocket("wss://javascript.info/article/websocket/demo/hello");

socket.onopen = function(e) {
  alert("[open] Connection established");
  alert("Sending to server");
  socket.send("My name is John");
};

socket.onmessage = function(event) {
  alert(`[message] Data received from server: ${event.data}`);
};

socket.onclose = function(event) {
  if (event.wasClean) {
    alert(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
  } else {
    // 例如服务器进程被杀死或网络中断
    // 在这种情况下,event.code 通常为 1006
    alert('[close] Connection died');
  }
};

socket.onerror = function(error) {
  alert(`[error] ${error.message}`);
};

5)数据类型

WebSocket 通信由 “frames”(即数据片段)组成
  • text frames —— 包含各方发送给彼此的文本数据。

  • binary data frames —— 包含各方发送给彼此的二进制数据。

  • ping/pong frames 被用于检查从服务器发送的连接,浏览器会自动响应它们。

  • connection close frame 及其他服务 frames。

6)webSocket.readyState

  • CONNECTING:值为0,表示正在连接。
  • OPEN:值为1,表示连接成功,可以通信了。
  • CLOSING:值为2,表示连接正在关闭。
  • CLOSED:值为3,表示连接已经关闭,或者打开连接失败。

7、Server Sent Events

Server-Sent Events 规范描述了一个内建的类 EventSource,它能保持与服务器的连接,并允许从中接收事件。与 WebSocket 类似,其连接是持久的。

WebSocketEventSource
双向:客户端和服务端都能交换消息单向:仅服务端能发送消息
二进制和文本数据仅文本数据
WebSocket 协议常规 HTTP 协议

1. 功能支持:

  • 在可调的 retry 超时内自动重新连接。
  • 用于恢复事件的消息 id,重新连接后,最后接收到的标识符被在 Last-Event-ID header 中发送出去。
  • 当前状态位于 readyState 属性中。

2. 语法:

let source = new EventSource(url, [credentials]);

第二个参数只有一个可选项:{ withCredentials: true },它允许发送跨源凭证。

3. 属性: readyState

当前连接状态:

  • EventSource.CONNECTING (=0)

  • EventSource.OPEN (=1)

  • EventSource.CLOSED (=2)

  • lastEventId

最后接收到的 id。重新连接后,浏览器在 header Last-Event-ID 中发送此 id。

4. 方法:

  • close():关闭连接。

5. 事件:

  • message

    接收到的消息,消息数据在 event.data 中。

  • open

    连接已建立。

  • error

    如果发生错误,包括连接丢失(将会自动重连)以及其他致命错误。我们可以检查 readyState 以查看是否正在尝试重新连接。

    服务器可以在 event: 中设置自定义事件名称。应该使用 addEventListener 来处理此类事件,而不是使用 on<event>

6. 服务器响应格式:

服务器发送由 \n\n 分隔的消息。

一条消息可能有以下字段:

  • data: —— 消息体(body),一系列多个 data 被解释为单个消息,各个部分之间由 \n 分隔。
  • id: —— 更新 lastEventId,重连时以 Last-Event-ID 发送此 id。
  • retry: —— 建议重连的延迟,以 ms 为单位。无法通过 JavaScript 进行设置。
  • event: —— 事件名,必须在 data: 之前。

杂项篇

1、文件操作

1)Blob

new Blob(blobParts, options);

  • blobParts Blob/BufferSource/String 类型的值的数组。
  • options 可选对象:
    • type —— Blob 类型,通常是 MIME 类型,例如 image/png
    • endings —— 是否转换换行符,使 Blob 对应于当前操作系统的换行符(\r\n 或 \n)。默认为 "transparent"(啥也不做),不过也可以是 "native"(转换)。

Blob具体信息

2)File和 FileReader

File 对象继承自 Blob,并扩展了与文件系统相关的功能

new File(fileParts, fileName, [options])

  • fileParts —— Blob/BufferSource/String 类型值的数组。
  • fileName —— 文件名字符串。
  • options —— 可选对象:
    • lastModified —— 最后一次修改的时间戳(整数日期)。

<input type="file"> 或拖放或其他浏览器接口来获取文件

FileReader 是一个对象,其唯一目的是从 Blob(因此也从 File)对象中读取数据。

let reader = new FileReader(); // 没有参数

主要方法:

  • readAsArrayBuffer(blob) —— 将数据读取为二进制格式的 ArrayBuffer。
  • readAsText(blob, [encoding]) —— 将数据读取为给定编码(默认为 utf-8 编码)的文本字符串。
  • readAsDataURL(blob) —— 读取二进制数据,并将其编码为 base64 的 data url。
  • abort() —— 取消操作。

read* 方法的选择,取决于我们喜欢哪种格式,以及如何使用数据。

  • readAsArrayBuffer —— 用于二进制文件,执行低级别的二进制操作。对于诸如切片(slicing)之类的高级别的操作,File 是继承自 Blob 的,所以我们可以直接调用它们,而无需读取。
  • readAsText —— 用于文本文件,当我们想要获取字符串时。
  • readAsDataURL —— 当我们想在 src 中使用此数据,并将其用于 img 或其他标签时。正如我们在 Blob 一章中所讲的,还有一种用于此的读取文件的替代方案:URL.createObjectURL(file)

读取过程中,有以下事件:

  • loadstart —— 开始加载。
  • progress —— 在读取过程中出现。
  • load —— 读取完成,没有 error。
  • abort —— 调用了 abort()。
  • error —— 出现 error。
  • loadend —— 读取完成,无论成功还是失败。

读取完成后,我们可以通过以下方式访问读取结果:

  • reader.result 是结果(如果成功)
  • reader.error 是 error(如果失败)。