先导
个人在学习过程中,涉及和遇见的一些基础知识,对其进行了简单归纳总结,浅尝辄止,略显杂而不精,做个人参考用
注:内容基本都是摘抄自博客、网络或MDN、现代JavaScript教程等,持续更新
DOM篇
1、DOM节点
-
EventTarget — 是根的“抽象(
abstract)”类。该类的对象从未被创建。它作为一个基础,以便让所有 DOM 节点都支持所谓的“事件(event)” -
Node— 也是一个“抽象”类,充当
DOM节点的基础。它提供了树的核心功能:parentNode,nextSibling,childNodes等(它们都是getter)。Node类的对象从未被创建。有一些继承自它的具体的节点类,例如:文本节点的
Text,元素节点的Element,以及更多异域(exotic)类,例如注释节点的Comment。 -
Element — 是
DOM元素的基本类。它提供了元素级的导航(navigation),例如nextElementSibling,children,以及像getElementsByTagName和querySelector这样的搜索方法。浏览器中不仅有
HTML,还会有XML和SVG。Element类充当更多特定类的基本类:SVGElement,XMLElement 和 HTMLElement。 -
HTMLElement — 最终是所有 HTML 元素的基本类。各种 HTML 元素均继承自它:
- HTMLInputElement —
<input>元素的类, - HTMLBodyElement —
<body>元素的类, - HTMLAnchorElement —
<a>元素的类, - ……等,每个标签都有自己的类,这些类可以提供特定的属性和方法。
- HTMLInputElement —
2、DOM遍历
1)顶层documentElement 和 body
最顶层的树节点可以直接作为 document 的属性来使用
-
<html> = document.documentElement -
<body> = document.body -
<head> = document.head
2)子节点:childNodes,firstChild,lastChild
-
子节点: 对应的是直系的子元素。换句话说,它们被完全嵌套在给定的元素中。例如,
<head>和<body>就是<html>元素的子元素。 -
子孙元素:嵌套在给定元素中的所有元素,包括子元素,以及子元素的子元素等。
-
childNodes集合列出了所有子节点,包括文本节点。 -
firstChild和lastChild属性是访问第一个和最后一个子元素的快捷方式。
3)DOM集合
childNodes是一个类数组的可迭代对象,可以通过 for..of来遍历,但没有数组方法
特性:只读、实时
4)兄弟节点和父节点
兄弟节点(Sibling) 是指有同一个父节点的节点
-
nextSibling:下一个兄弟节点 -
previousSibling:上一个兄弟节点 -
parentNode:父节点
5)只关注元素节点(在词中间加了 Element)
children— 仅那些作为元素节点的子代的节点。firstElementChild,lastElementChild— 第一个和最后一个子元素。previousElementSibling,nextElementSibling— 兄弟元素。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、获取元素
document.getElementById(id)或者只使用id:给元素添加id相当于给全局对象添加了一个属性
(比如 window['elem-content'])
注:不要使用以 id 命名的全局变量来访问元素
elem.querySelectorAll(css):返回elem中与给定CSS选择器匹配的所有元素。elem.querySelector(css):返回给定 CSS 选择器的第一个元素。elem.matches(css):不会查找任何内容,它只会检查elem是否与给定的CSS选择器匹配。它返回true或false。elem.closest(css)方法会查找与 CSS 选择器匹配的最近的祖先。elem自己也会被搜索- 旧版本遗留:
elem.getElementsByTagName(tag)查找具有给定标签的元素,并返回它们的集合。tag 参数也可以是对于“任何标签”的星号 "*"。elem.getElementsByClassName(className)返回具有给定CSS类的元素。document.getElementsByName(name)返回在文档范围内具有给定name特性的元素。很少使用。
4、节点属性:type、tag和content
(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)支持 value,type,而 <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 中有两个概念:
- 计算 (computed) 样式值是所有 CSS 规则和 CSS 继承都应用后的值,这是 CSS 级联(cascade)的结果。它看起来像 height:1em 或 font-size:125%。
- 解析 (resolved) 样式值是最终应用于元素的样式值值。诸如 1em 或 125% 这样的值是相对的。浏览器将使用计算(computed)值,并使所有单位均为固定的,且为绝对单位,例如:height:20px 或 font-size:16px。对于几何属性,解析(resolved)值可能具有浮点,例如:width:50.5px。 很久以前,创建了 getComputedStyle 来获取计算(computed)值,但事实证明,解析(resolved)值要方便得多,标准也因此发生了变化。 所以,现在 getComputedStyle 实际上返回的是属性的解析值(resolved)。
8、元素大小和滚动
几何示意图:
-
offsetParent— 最接近的 CSS 定位((position为absolute,relative或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 方法处理的是以下两种坐标系中的一个:
- 相对于窗口 — 类似于
position:fixed,从窗口的顶部/左侧边缘计算得出。
- 我们将这些坐标表示为
clientX/clientY,当我们研究事件属性时,就会明白为什么使用这种名称来表示坐标。
- 相对于文档 — 与文档根(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)的元素。
页面上的任何点都有坐标:
- 相对于窗口的坐标 —
elem.getBoundingClientRect()。 - 相对于文档的坐标 —
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 对象上)基本相同。当嵌入的窗口的所有资源都完全加载完毕时触发。
-
二级域名相同可以视为同源,通过类似
document.domain = 'site.com';进行调整 -
集合:
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—— 对最顶级父窗口的引用。
- 通信(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对象:
window:核心对象,表示浏览器的一个实例,也是全局对象location:url地址,location.reload(),可以重新刷新当前页面navigator:获取浏览器的属性,区分浏览器类型screen:保存的纯粹是客户端能力信息,即浏览器窗口外的客户端显示器信息,比如像素宽度和像素高度history:操作浏览器URL的历史记录
history.go():接收一个整数数字或者字符串参数:
参数为字符串:向最近的一个记录中包含指定字符串的页面跳转,
参数为整数数字的时候,正数向前,负数向后history.forward():向前跳转一个页面history.back():向后跳转一个页面history.length:获取历史记录数
2、Window对象
window对象表示浏览器打开的窗口
如果文档包含框架(frame 或 iframe 标签),浏览器会为 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.body 是 ReadableStream 对象,它允许你逐块读取 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
过程:
- 创建
XMLHttpRequest对象; - 调用
open方法传入三个参数 请求方式(GET/POST) 、url、同步异步(true/false); - 监听
onreadystatechange事件,当readystate等于4时返回responseText; - 调用
send方法传递参数。 - 通过
XMLHttpRequest对象提供的onreadystatechange事件监听服务器端你的通信状态 - 接受并处理服务端向客户端响应的数据结果
- 将处理结果更新到 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 类似,其连接是持久的。
| WebSocket | EventSource |
|---|---|
| 双向:客户端和服务端都能交换消息 | 单向:仅服务端能发送消息 |
| 二进制和文本数据 | 仅文本数据 |
| WebSocket 协议 | 常规 HTTP 协议 |
1. 功能支持:
- 在可调的
retry超时内自动重新连接。 - 用于恢复事件的消息 id,重新连接后,最后接收到的标识符被在
Last-Event-IDheader 中发送出去。 - 当前状态位于
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"(转换)。
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(如果失败)。