type
status
date
slug
summary
tags
category
icon
password
Edited
Sep 15, 2022 01:17 PM
Created
Aug 30, 2022 11:57 AM
备忘录
querySelectorAll
到目前为止,最通用的方法是
elem.querySelectorAll(css)
,它返回 elem
中与给定 CSS 选择器匹配的所有元素。querySelector
elem.querySelector(css)
调用会返回给定 CSS 选择器的第一个元素。换句话说,结果与
elem.querySelectorAll(css)[0]
相同,但是后者会查找 所有 元素,并从中选取一个,而 elem.querySelector
只会查找一个。因此它在速度上更快,并且写起来更短。elem.closest(css)
方法会查找与 CSS 选择器匹配的最近的祖先。elem
自己也会被搜索。方法
closest
在元素中得到了提升,并检查每个父级。如果它与选择器匹配,则停止搜索并返回该祖先主要的 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 属性。元素插入方法
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
替换为给定的节点或字符串。
node.remove()
—— 移除node
。
elem.insertAdjacentHTML(where, html)
—— 第一个参数是代码字(code word),指定相对于elem
的插入位置,第二个参数是 HTML 字符串,该字符串会被“作为 HTML” 插入。"beforebegin"
—— 将html
插入到elem
之前,"afterbegin"
—— 将html
插入到elem
开头,"beforeend"
—— 将html
插入到elem
末尾,"afterend"
—— 将html
插入到elem
之后。elem.insertAdjacentText(where, text)
—— 语法一样,但是将text
字符串“作为文本”插入而不是作为 HTML,elem.insertAdjacentElement(where, elem)
—— 语法一样,但是插入的是一个元素。
这个方法有两个兄弟:
classList
的方法:
elem.classList.add/remove(class)
—— 添加/移除类。
elem.classList.toggle(class)
—— 如果类不存在就添加类,存在就移除它。
elem.classList.contains(class)
—— 检查给定类,返回true/false
。
此外,
classList
是可迭代的getComputedStyle(elem, [pseudo])
返回与 style
对象类似的,且包含了所有类的对象。只读元素的几何属性:
offsetParent
—— 是最接近的 CSS 定位的祖先,或者是td
,th
,table
,body
。
offsetLeft/offsetTop
—— 是相对于offsetParent
的左上角边缘的坐标。
offsetWidth/offsetHeight
—— 元素的“外部” width/height,边框(border)尺寸计算在内。
clientLeft/clientTop
—— 从元素左上角外角到左上角内角的距离。对于从左到右显示内容的操作系统来说,它们始终是左侧/顶部 border 的宽度。而对于从右到左显示内容的操作系统来说,垂直滚动条在左边,所以clientLeft
也包括滚动条的宽度。
clientWidth/clientHeight
—— 内容的 width/height,包括 padding,但不包括滚动条(scrollbar)。
scrollWidth/scrollHeight
—— 内容的 width/height,就像clientWidth/clientHeight
一样,但还包括元素的滚动出的不可见的部分。
scrollLeft/scrollTop
—— 从元素的左上角开始,滚动出元素的上半部分的 width/height。
除了
scrollLeft/scrollTop
外,所有属性都是只读的。如果我们修改 scrollLeft/scrollTop
,浏览器会滚动对应的元素。最近的祖先为下列之一:
- CSS 定位的(
position
为absolute
,relative
或fixed
),
- 或
<td>
,<th>
,<table>
,
- 或
<body>
。
页面几何
- 文档可见部分的 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
);
滚动
- 读取当前的滚动:
window.pageYOffset/pageXOffset
。
- 更改当前的滚动:
window.scrollTo(pageX,pageY)
—— 绝对坐标,window.scrollBy(x,y)
—— 相对当前位置进行滚动,elem.scrollIntoView(top)
—— 滚动以使elem
可见(elem
与窗口的顶部/底部对齐)。
坐标
页面上的任何点都有坐标:
- 相对于窗口的坐标 ——
elem.getBoundingClientRect()
。
- 相对于文档的坐标 ——
elem.getBoundingClientRect()
加上当前页面滚动。
- 在窗口坐标
(x, y)
处嵌套最多(the most nested)的元素 ——elementFromPoint(x, y)
窗口坐标非常适合和
position:fixed
一起使用,文档坐标非常适合和 position:absolute
一起使用。方法
elem.getBoundingClientRect()
返回最小矩形的窗口坐标,该矩形将 elem
作为内建 DOMRect 类的对象。主要的
DOMRect
属性:x/y
—— 矩形原点相对于窗口的 X/Y 坐标,
width/height
—— 矩形的 width/height(可以为负)。
此外,还有派生(derived)属性:
top/bottom
—— 顶部/底部矩形边缘的 Y 坐标,
left/right
—— 左/右矩形边缘的 X 坐标。
很容易地从它们计算出派生(derived)属性:
left = x
top = y
right = x + width
bottom = y + height
最有用的 DOM 事件
element.addEventListener(event, handler[, options])
event
事件名,例如:
"click"
。handler
处理程序。
options
once
:如果为true
,那么会在被触发后自动删除监听器。capture
:事件处理的阶段, 冒泡和捕获。由于历史原因,options
也可以是false/true
,它与{capture: false/true}
相同。passive
:如果为true
,那么处理程序将不会调用preventDefault()
,浏览器默认行为 。
具有以下属性的附加可选对象:
element.removeEventListener(event, handler[, options])
鼠标事件:
click
—— 当鼠标点击一个元素时(触摸屏设备会在点击时生成)。
contextmenu
—— 当鼠标右键点击一个元素时。
mouseover
/mouseout
—— 当鼠标指针移入/离开一个元素时。
mousedown
/mouseup
—— 当在元素上按下/释放鼠标按钮时。
mousemove
—— 当鼠标移动时。
键盘事件:
keydown
和keyup
—— 当按下和松开一个按键时。
表单(form)元素事件:
submit
—— 当访问者提交了一个<form>
时。
focus
—— 当访问者聚焦于一个元素时,例如聚焦于一个<input>
。
Document 事件:
DOMContentLoaded
—— 当 HTML 的加载和处理均完成,DOM 被完全构建完成时。
CSS 事件:
transitionend
—— 当一个 CSS 动画完成时。
鼠标事件
mousedown/mouseup
在元素上点击/释放鼠标按钮。
mouseover/mouseout
鼠标指针从一个元素上移入/移出。
mousemove
鼠标在元素上的每个移动都会触发此事件。
click
如果使用的是鼠标左键,则在同一个元素上的
mousedown
及 mouseup
相继触发后,触发该事件。dblclick
在短时间内双击同一元素后触发。如今已经很少使用了。
contextmenu
在鼠标右键被按下时触发。还有其他打开上下文菜单的方式,例如使用特殊的键盘按键,在这种情况下它也会被触发,因此它并不完全是鼠标事件。
oncopy
如果我们想禁用选择以保护我们页面的内容不被复制粘贴
在单个动作触发多个事件时,事件的顺序是固定的。也就是说,会遵循mousedown
→mouseup
→click
的顺序调用处理程序。
鼠标事件有以下属性:
- 按钮:
button
。该属性允许获取确切的鼠标按钮
鼠标按键状态 | event.button |
左键 (主要按键) | 0 |
中键 (辅助按键) | 1 |
右键 (次要按键) | 2 |
X1 键 (后退按键) | 3 |
X2 键 (前进按键) | 4 |
- 组合键(如果被按下则为
true
):altKey
,ctrlKey
,shiftKey
和metaKey
(Mac)。
如果你想处理 ,那么不要忘记 Mac 用户,他们通常使用的是 ,所以最好检查
if (e.metaKey || e.ctrlKey)
。- 窗口相对坐标:
clientX/clientY
。
- 文档相对坐标:
pageX/pageY
。
mousedown
的默认浏览器操作是文本选择,如果它对界面不利,则应避免它。
mouseover/mouseout
这些事件很特别,因为它们具有
relatedTarget
属性。此属性是对 target
的补充。当鼠标从一个元素离开并去往另一个元素时,其中一个元素就变成了 target
,另一个就变成了 relatedTarget
。对于
mouseover
:event.target
—— 是鼠标移过的那个元素。
event.relatedTarget
—— 是鼠标来自的那个元素(relatedTarget
→target
)。
mouseout
则与之相反:event.target
—— 是鼠标离开的元素。
event.relatedTarget
—— 是鼠标移动到的,当前指针位置下的元素(target
→relatedTarget
)。
mouseout
一个重要功能 —— 当鼠标指针从元素移动到其后代时触发,例如从 #parent
到 #child
如果我们在 #parent
上,然后将鼠标指针更深入地移入 #child
,在 #parent
上我们会得到 mouseout
!事件
mouseenter/mouseleave
类似于 mouseover/mouseout
。它们在鼠标指针进入/离开元素时触发。但是有两个重要的区别:
- 元素内部与后代之间的转换不会产生影响。
- 事件
mouseenter/mouseleave
不会冒泡。
知识点
浏览器环境
JavaScript 在浏览器中运行时的鸟瞰示意图
有一个叫做
window
的“根”对象。它有两个角色:- 首先,它是 JavaScript 代码的全局对象,如 全局对象 一章所述。
- 其次,它代表“浏览器窗口”,并提供了控制它的方法。
文档对象模型(DOM)
文档对象模型(Document Object Model),简称 DOM,将所有页面内容表示为可以修改的对象。
document
对象是页面的主要“入口点”。我们可以使用它来更改或创建页面上的任何内容。浏览器对象模型(BOM)
浏览器对象模型(Browser Object Model),简称 BOM,表示由浏览器(主机环境)提供的用于处理文档(document)之外的所有内容的其他对象。
用于样式的 CSSOM
另外也有一份针对 CSS 规则和样式表的、单独的规范 CSS Object Model (CSSOM),这份规范解释了如何将 CSS 表示为对象,以及如何读写这些对象。
规范
DOM 规范
描述文档的结构、操作和事件,详见 https://dom.spec.whatwg.org。
CSSOM 规范
描述样式表和样式规则,对它们进行的操作,以及它们与文档的绑定,详见 https://www.w3.org/TR/cssom-1/。
HTML 规范
描述 HTML 语言(例如标签)以及 BOM(浏览器对象模型)— 各种浏览器函数:
setTimeout
,alert
,location
等,详见 https://html.spec.whatwg.org。它采用了 DOM 规范,并使用了许多其他属性和方法对其进行了扩展。此外,某些类被分别描述在 https://spec.whatwg.org/。
DOM 树
HTML 文档的主干是标签(tag)。
根据文档对象模型(DOM),每个 HTML 标签都是一个对象。嵌套的标签是闭合标签的“子标签(children)”。标签内的文本也是一个对象。
所有这些对象都可以通过 JavaScript 来访问,我们可以使用它们来修改页面。
标签被称为 元素节点(或者仅仅是元素),并形成了树状结构:
<html>
在根节点,<head>
和 <body>
是其子项,等。元素内的文本形成 文本节点,被标记为
#text
。一个文本节点只包含一个字符串。它没有子项,并且总是树的叶子。注意文本节点中的特殊字符:
- 换行符:
↵
(在 JavaScript 中为\n
)
- 空格:
␣
空格和换行符都是完全有效的字符,就像字母和数字。它们形成文本节点并成为 DOM 的一部分。
没有空格的文本节点:
字符串开头/结尾处的空格,以及只有空格的文本节点,通常会被工具隐藏
节点类型
HTML 中的所有内容,甚至注释,都会成为 DOM 的一部分。
甚至 HTML 开头的
<!DOCTYPE...>
指令也是一个 DOM 节点。它在 DOM 树中位于 <html>
之前。很少有人知道这一点。我们不会触及那个节点,我们甚至不会在图表中绘制它,但它确实就在那里。表示整个文档的
document
对象,在形式上也是一个 DOM 节点。一共有 12 种节点类型。实际上,我们通常用到的是其中的 4 种:
document
—— DOM 的“入口点”。
- 元素节点 —— HTML 标签,树构建块。
- 文本节点 —— 包含文本。
- 注释 —— 有时我们可以将一些信息放入其中,它不会显示,但 JS 可以从 DOM 中读取它。
遍历 DOM
对象间链接,通过这些链接我们可以在 DOM 节点之间移动。
导航(navigation)属性引用 所有 节点。例如,在
childNodes
中我们可以看到文本节点,元素节点,甚至包括注释节点(如果它们存在的话)。纯元素导航
只考虑 元素节点 的导航链接(navigation link)
DOM 节点类
每个 DOM 节点都属于相应的内建类。
层次结构(hierarchy)的根节点是 EventTarget, Node 继承自它,其他 DOM 节点继承自 Node。
- EventTarget —— 是一切的根“抽象(abstract)”类。
该类的对象从未被创建。它作为一个基础,以便让所有 DOM 节点都支持所谓的“事件(event)”,我们会在之后学习它。
- Node —— 也是一个“抽象”类,充当 DOM 节点的基础。
它提供了树的核心功能:
parentNode
,nextSibling
,childNodes
等(它们都是 getter)。Node
类的对象从未被创建。但是还有一些继承自它的其他类(因此继承了 Node
的功能)。- Document 由于历史原因通常被
HTMLDocument
继承(尽管最新的规范没有规定)—— 是一个整体的文档。
全局变量
document
就是属于这个类。它作为 DOM 的入口。- CharacterData —— 一个“抽象”类,被下述类继承:
- Text —— 对应于元素内部文本的类,例如
<p>Hello</p>
中的Hello
。 - Comment —— 注释类。它们不会被展示出来,但每个注释都会成为 DOM 中的一员。
- Element —— 是 DOM 元素的基础类。
它提供了元素级导航(navigation),如
nextElementSibling
,children
,以及搜索方法,如 getElementsByTagName
和 querySelector
。浏览器不仅支持 HTML,还支持 XML 和 SVG。因此,
Element
类充当的是更具体的类的基础:SVGElement
,XMLElement
(我们在这里不需要它)和 HTMLElement
。- 最后,HTMLElement —— 是所有 HTML 元素的基础类。我们大部分时候都会用到它。
- HTMLInputElement ——
<input>
元素的类, - HTMLBodyElement ——
<body>
元素的类, - HTMLAnchorElement ——
<a>
元素的类, - ……等。
它会被更具体的 HTML 元素继承:
例如:一个
<input>
元素的 DOM 对象。它属于 HTMLInputElement 类它获取属性和方法,并将其作为下列类(按继承顺序列出)的叠加:
HTMLInputElement
—— 该类提供特定于输入的属性,
HTMLElement
—— 它提供了通用(common)的 HTML 元素方法(以及 getter 和 setter)
Element
—— 提供通用(generic)元素方法,
Node
—— 提供通用 DOM 节点属性,
EventTarget
—— 为事件(包括事件本身)提供支持,
- ……最后,它继承自
Object
,因为像hasOwnProperty
这样的“普通对象”方法也是可用的。
DOM 节点是常规的 JavaScript 对象。它们使用基于原型的类进行继承
特性和属性(Attributes and properties)
如果标签是
<body id="page">
,那么 DOM 对象就会有 body.id="page"
。但特性—属性映射并不是一一对应的!
- 特性(attribute)—— 写在 HTML 中的内容。
- 属性(property)—— DOM 对象中的内容。
DOM 属性
DOM 属性和方法的行为就像常规的 Javascript 对象一样:
- 它们可以有很多值。
- 它们是大小写敏感的(要写成
elem.nodeType
,而不是elem.NoDeTyPe
)。
HTML 特性
在 HTML 中,标签可能拥有特性(attributes)。当浏览器解析 HTML 文本,并根据标签创建 DOM 对象时,浏览器会辨别 标准的 特性并以此创建 DOM 属性。
所以,当一个元素有
id
或其他 标准的 特性,那么就会生成对应的 DOM 属性。但是非 标准的 特性则不会。注意,一个元素的标准的特性对于另一个元素可能是未知的
所有特性都可以通过使用以下方法进行访问:
elem.hasAttribute(name)
—— 检查特性是否存在。
elem.getAttribute(name)
—— 获取这个特性值。
elem.setAttribute(name, value)
—— 设置这个特性值。
elem.removeAttribute(name)
—— 移除这个特性。
HTML 特性有以下几个特征:
- 它们的名字是大小写不敏感的(
id
与ID
相同)。
- 它们的值总是字符串类型的。
属性—特性同步
当一个标准的特性被改变,对应的属性也会自动更新,(除了几个特例)反之亦然。
dataset
所有以 “data-” 开头的特性均被保留供程序员使用。它们可在
dataset
属性中使用。例如,如果一个
elem
有一个名为 "data-about"
的特性,那么可以通过 elem.dataset.about
取到它。事件对象
当事件发生时,浏览器会创建一个
event
对象,将详细信息放入其中,并将其作为参数传递给处理程序。event
对象的一些属性:event.type
事件类型,这里是
"click"
。event.currentTarget
处理事件的元素。这与
this
相同,除非处理程序是一个箭头函数,或者它的 this
被绑定到了其他东西上,之后我们就可以从 event.currentTarget
获取元素了。event.clientX / event.clientY
指针事件(pointer event)的指针的窗口相对坐标。
还有很多属性。其中很多都取决于事件类型
冒泡和捕获
冒泡
当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序。
假设我们有 3 层嵌套
FORM > DIV > P
,它们各自拥有一个处理程序点击内部的
<p>
会首先运行 onclick
:- 在该
<p>
上的。
- 然后是外部
<div>
上的。
- 然后是外部
<form>
上的。
- 以此类推,直到最后的
document
对象。
p
→ div
→ form
这个过程被称为“冒泡(bubbling)”,因为事件从内部元素“冒泡”到所有父级,就像在水里的气泡一样。
event.target & event.currentTarget
引发事件的那个嵌套层级最深的元素被称为目标元素,可以通过
event.target
访问。注意与
this
(=event.currentTarget
)之间的区别:event.target
—— 是引发事件的“目标”元素,它在冒泡过程中不会发生变化(是实际被点击的元素)
this
—— 是“当前”元素,其中有一个当前正在运行的处理程序。
捕获
它很少被用在实际开发中,但有时是有用的。
DOM 事件标准描述了事件传播的 3 个阶段:
- 捕获阶段(Capturing phase)—— 事件(从 Window)向下走近元素。
- 目标阶段(Target phase)—— 事件到达目标元素。
- 冒泡阶段(Bubbling phase)—— 事件从元素上开始冒泡。
event.eventPhase
—— 当前阶段(capturing=1,target=2,bubbling=3)任何事件处理程序都可以通过调用
event.stopPropagation()
来停止事件冒泡,但不建议这样做,因为我们不确定是否确实不需要冒泡上来的事件,也许是用于完全不同的事情。事件委托
如果我们有许多以类似方式处理的元素,那么就不必为每个元素分配一个处理程序 —— 而是将单个处理程序放在它们的共同祖先上。
在处理程序中,我们获取
event.target
以查看事件实际发生的位置并进行处理。算法:
- 在容器(container)上放一个处理程序。
- 在处理程序中 —— 检查源元素
event.target
。
- 如果事件发生在我们感兴趣的元素内,那么处理该事件。
例如:
好处:
- 我们不需要编写代码来为每个按钮分配一个处理程序。只需要创建一个方法并将其放入标记(markup)中即可。
- HTML 结构非常灵活,我们可以随时添加/移除按钮。
“行为”模式
使用事件委托将“行为(behavior)”以 声明方式 添加到具有特殊特性(attribute)和类的元素中。
行为模式分为两个部分:
- 我们将自定义特性添加到描述其行为的元素。
- 用文档范围级的处理程序追踪事件,如果事件发生在具有特定特性的元素上 —— 则执行行为(action)。
好处:
- 简化初始化并节省内存:无需添加许多处理程序。
- 更少的代码:添加或移除元素时,无需添加/移除处理程序。
- DOM 修改 :我们可以使用
innerHTML
等,来批量添加/移除元素。
局限性:
- 首先,事件必须冒泡。而有些事件不会冒泡。此外,低级别的处理程序不应该使用
event.stopPropagation()
。
- 其次,委托可能会增加 CPU 负载,因为容器级别的处理程序会对容器中任意位置的事件做出反应,而不管我们是否对该事件感兴趣。但是,通常负载可以忽略不计,所以我们不考虑它。
浏览器默认行为
有很多默认的浏览器行为:
mousedown
—— 开始选择(移动鼠标进行选择)。
- 在
<input type="checkbox">
上的click
—— 选中/取消选中的input
。
submit
—— 点击<input type="submit">
或者在表单字段中按下 键会触发该事件,之后浏览器将提交表单。
Enter
keydown
—— 按下一个按键会导致将字符添加到字段,或者触发其他行为。
contextmenu
—— 事件发生在鼠标右键单击时,触发的行为是显示浏览器上下文菜单。
- ……还有更多……
想要阻止默认行为 —— 可以使用
event.preventDefault()
或 return false
。第二个方法只适用于通过 on<event>
分配的处理程序。addEventListener
的 passive: true
选项告诉浏览器该行为不会被阻止。这对于某些移动端的事件(像 touchstart
和 touchmove
)很有用,用以告诉浏览器在滚动之前不应等待所有处理程序完成。如果默认行为被阻止,
event.defaultPrevented
的值会变成 true
,否则为 false
。鼠标拖放
基础:
实例
document.elementFromPoint(clientX, clientY)
返回在给定的窗口相对坐标处的嵌套的最深的元素(如果给定的坐标在窗口外,则返回
null
)hero:
指针事件
除非你写的代码需要兼容旧版本的浏览器,例如 IE 10 或 Safari 12 或更低的版本,否则无需继续使用鼠标事件或触摸事件 —— 我们可以使用指针事件
指针事件(Pointer Events)是一种用于处理来自各种输入设备(例如鼠标、触控笔和触摸屏等)的输入信息的现代化解决方案。
指针事件类型
指针事件 | 类似的鼠标事件 |
pointerdown | mousedown |
pointerup | mouseup |
pointermove | mousemove |
pointerover | mouseover |
pointerout | mouseout |
pointerenter | mouseenter |
pointerleave | mouseleave |
pointercancel | - |
gotpointercapture | - |
lostpointercapture | - |
指针事件属性
指针事件具备和鼠标事件完全相同的属性,包括
clientX/Y
和 target
等,以及一些其他属性:pointerId
—— 触发当前事件的指针唯一标识符。
浏览器生成的。使我们能够处理多指针的情况,例如带有触控笔和多点触控功能的触摸屏(下文会有相关示例)。
pointerType
—— 指针的设备类型。必须为字符串,可以是:“mouse”、“pen” 或 “touch”。
我们可以使用这个属性来针对不同类型的指针输入做出不同响应。
isPrimary
—— 当指针为首要指针(多点触控时按下的第一根手指)时为true
。
有些指针设备会测量接触面积和点按压力(例如一根手指压在触屏上),对于这种情况可以使用以下属性:
width
—— 指针(例如手指)接触设备的区域的宽度。对于不支持的设备(如鼠标),这个值总是1
。
height
—— 指针(例如手指)接触设备的区域的长度。对于不支持的设备,这个值总是1
。
pressure
—— 触摸压力,是一个介于 0 到 1 之间的浮点数。对于不支持压力检测的设备,这个值总是0.5
(按下时)或0
。
tangentialPressure
—— 归一化后的切向压力(tangential pressure)。
tiltX
,tiltY
,twist
—— 针对触摸笔的几个属性,用于描述笔和屏幕表面的相对位置。
多点触控:
当用户用一根手指触摸触摸屏的某个位置,然后将另一根手指放在该触摸屏的其他位置时,会发生以下情况:
- 第一个手指触摸:
pointerdown
事件触发,isPrimary=true
,并且被指派了一个pointerId
。
- 第二个和后续的更多个手指触摸(假设第一个手指仍在触摸):
pointerdown
事件触发,isPrimary=false
,并且每一个触摸都被指派了不同的pointerId
。
请注意:
pointerId
不是分配给整个设备的,而是分配给每一个触摸的。如果 5 根手指同时触摸屏幕,我们会得到 5 个 pointerdown
事件和相应的坐标以及 5 个不同的 pointerId
。pointercancel
pointercancel
事件将会在一个正处于活跃状态的指针交互由于某些原因被中断时触发。也就是在这个事件之后,该指针就不会继续触发更多事件了。导致指针中断的可能原因如下:
- 指针设备硬件在物理层面上被禁用。
- 设备方向旋转(例如给平板转了个方向)。
- 浏览器打算自行处理这一交互,比如将其看作是一个专门的鼠标手势或缩放操作等。
例如 鼠标拖放事件时浏览器有自己原生的图片拖放操作,接管了之前的拖放过程,于是触发了
pointercancel
事件问题就在于浏览器”劫持“了这一个互动操作:在“拖放”过程开始时触发了
pointercancel
事件,并且不再有 pointermove
事件会被生成。阻止浏览器的默认行为来防止
pointercancel
触发。- 阻止原生的拖放操作发生:
- 正如我们在 鼠标拖放事件 中描述的那样,我们可以通过设置
ball.ondragstart = () => false
来实现这一需求。 - 这种方式也适用于鼠标事件。
- 对于触屏设备,还有其他和触摸相关的浏览器行为(除了拖放)。为了避免它们所引发的问题:
- 我们可以通过在 CSS 中设置
#ball { touch-action: none }
来阻止它们。 - 之后我们的代码便可以在触屏设备中正常工作了。
指针捕获
elem.setPointerCapture(pointerId)
- 将给定的
pointerId
绑定到elem
。在调用之后,所有具有相同pointerId
的指针事件都将elem
作为目标(就像事件发生在elem
上一样),无论这些elem
在文档中的实际位置是什么。
- 换句话说,
elem.setPointerCapture(pointerId)
将所有具有给定pointerId
的后续事件重新定位到elem
。
绑定会在以下情况下被移除:
- 当
pointerup
或pointercancel
事件出现时,绑定会被自动地移除。
- 当
elem
被从文档中移除后,绑定会被自动地移除。
- 当
elem.releasePointerCapture(pointerId)
被调用,绑定会被移除。
指针捕获可以被用于简化拖放类的交互
setPointerCapture
适用的场景- 我们可以在
pointerdown
事件的处理程序中调用thumb.setPointerCapture(event.pointerId)
,
- 这样接下来在
pointerup/cancel
之前发生的所有指针事件都会被重定向到thumb
上。
- 当
pointerup
发生时(拖动完成),绑定会被自动移除,我们不需要关心它。
(捕获仅影响
target/currentTarget
)键盘事件
Keydown 和 keyup
当一个按键被按下时,会触发
keydown
事件,而当按键被释放时,会触发 keyup
事件。event.code 和 event.key
事件对象的
key
属性允许获取字符,而事件对象的 code
属性则允许获取“物理按键代码”。例如,同一个按键 Z,可以与或不与
Shift
一起按下。我们会得到两个不同的字符:小写的 z
和大写的 Z
。event.key
正是这个字符,并且它将是不同的。但是,event.code
是相同的:Key | event.key | event.code |
Z | z (小写) | KeyZ |
Shift+Z | Z (大写) | KeyZ |
按键代码:
- 字符键的代码为
"Key<letter>"
:"KeyA"
,"KeyB"
等。
- 数字键的代码为:
"Digit<number>"
:"Digit0"
,"Digit1"
等。
- 特殊按键的代码为按键的名字:
"Enter"
,"Backspace"
,"Tab"
等。
Key | event.key | event.code |
F1 | F1 | F1 |
Backspace | Backspace | Backspace |
Shift | Shift | ShiftRight 或 ShiftLeft |
为了可靠地跟踪与受键盘布局影响的字符,使用
event.key
可能是一个更好的方式。另一方面,
event.code
的好处是,绑定到物理键位置的 event.code
会始终保持不变。因此,即使在切换了语言的情况下,依赖于它的热键也能正常工作。我们想要处理与布局有关的按键?那么
event.key
是我们必选的方式。或者我们希望一个热键即使在切换了语言后,仍能正常使用?那么
event.code
可能会更好。对于由自动重复触发的事件,
event
对象的 event.repeat
属性被设置为 true
。默认行为
默认行为各不相同,因为键盘可能会触发很多可能的东西。
例如:
- 出现在屏幕上的一个字符(最明显的结果)。
- 一个字符被删除(
Delete
键)。
- 滚动页面(
PageDown
键)。
- 浏览器打开“保存页面”对话框(
Ctrl+S
)
- ……等。
滚动
scroll
事件允许对页面或元素滚动作出反应。我们可以在这里做一些有用的事情。例如:
- 根据用户在文档中的位置显示/隐藏其他控件或信息。
- 当用户向下滚动到页面末端时加载更多数据。
防止滚动
我们不能通过在
onscroll
监听器中使用 event.preventDefault()
来阻止滚动,因为它会在滚动发生 之后 才触发。但是我们可以在导致滚动的事件上,例如在 pageUp 和 pageDown 的
keydown
事件上,使用 event.preventDefault()
来阻止滚动。如果我们向这些事件中添加事件处理程序,并向其中添加
event.preventDefault()
,那么滚动就不会开始。启动滚动的方式有很多,使用 CSS 的
overflow
属性更加可靠。表单
document.forms
一个表单元素可以通过
document.forms[name/index]
访问到。form.elements
表单元素可以通过
form.elements[name/index]
的方式访问,或者也可以使用 form[name/index]
。elements
属性也适用于 <fieldset>
。element.form
元素通过
form
属性来引用它们所属的表单。Fieldset 作为“子表单”一个表单内会有一个或多个<fieldset>
元素。它们也具有elements
属性,该属性列出了<fieldset>
中的表单控件。
更简短的表示方式:form.name
value
可以被通过 input.value
,textarea.value
,select.value
等来获取到。(对于单选按钮(radio button)和复选框(checkbox),可以使用 input.checked
来确定是否选择了一个值。<select>
元素有 3 个重要的属性:select.options
——<option>
的子元素的集合,
select.value
—— 当前所选择的<option>
的 value,
select.selectedIndex
—— 当前所选择的<option>
的编号。
focus/blur 聚焦事件
当元素聚焦时,会触发
focus
事件,当元素失去焦点时,会触发 blur
事件。elem.focus()
和 elem.blur()
方法可以设置和移除元素上的焦点。例如,如果输入值无效,我们可以让焦点无法离开这个
input
字段允许在任何元素上聚焦:tabindex
默认情况下,很多元素不支持聚焦。
任何具有
tabindex
特性的元素,都会变成可聚焦的。该特性的 value
是当使用 Tab(或类似的东西)在元素之间进行切换时,元素的顺序号。切换顺序为:从
1
开始的具有 tabindex
的元素排在前面(按 tabindex
顺序),然后是不具有 tabindex
的元素(例如常规的 <input>
)。不具有
tabindex
的元素按文档源顺序(默认顺序)切换。有两个特殊的值:
tabindex="0"
会使该元素被与那些不具有tabindex
的元素放在一起。也就是说,当我们切换元素时,具有tabindex="0"
的元素将排在那些具有tabindex ≥ 1
的元素的后面。
通常,它用于使元素具有焦点,但是保留默认的切换顺序。使元素成为与
<input>
一样的表单的一部分。tabindex="-1"
只允许以编程的方式聚焦于元素。Tab 键会忽略这样的元素,但是elem.focus()
有效。
属性elem.tabIndex
也有效我们可以使用elem.tabIndex
通过 JavaScript 来添加tabindex
。效果是一样的。
focus/blur不会冒泡。但是可以改为在捕获阶段触发,或者使用
focusin/focusout
。可以通过
document.activeElement
来获取当前所聚焦的元素。事件:change,input,cut,copy,paste
事件 | 描述 | 特点 |
change | 值被改变。 | 对于文本输入,当失去焦点时触发。 |
input | 文本输入的每次更改。 | 立即触发,与 change 不同。 |
cut/copy/paste | 剪贴/拷贝/粘贴行为。 | 行为可以被阻止。 event.clipboardData 属性可以用于访问剪贴板。除了火狐(Firefox)之外的浏览器都支持 navigator.clipboard 。
使用 document.getSelection() 来得到被选中的文本 |
表单提交
提交表单时,会触发
submit
事件,它通常用于在将表单发送到服务器之前对表单进行校验,或者中止提交,并使用 JavaScript 来处理表单。form.submit()
方法允许从 JavaScript 启动表单发送。我们可以使用此方法动态地创建表单,并将其发送到服务器。提交表单主要有两种方式:
- 第一种 —— 点击
<input type="submit">
或<input type="image">
。
- 第二种 —— 在
input
字段中按下 键。
Enter
奇特点
实时的集合
实时的集合
所有的
"getElementsBy*"
方法都会返回一个 实时的(live) 集合。这样的集合始终反映的是文档的当前状态,并且在文档发生更改时会“自动更新”。相反,
querySelectorAll
返回的是一个 静态的 集合。就像元素的固定数组。有 6 种主要的方法,可以在 DOM 中搜索元素节点:
方法名 | 搜索方式 | 可以在元素上调用? | 实时的? |
querySelector | CSS-selector | ✔ | - |
querySelectorAll | CSS-selector | ✔ | - |
getElementById | id | - | - |
getElementsByName | name | - | ✔ |
getElementsByTagName | tag or '*' | ✔ | ✔ |
getElementsByClassName | class | ✔ | ✔ |
规范中的 IDL
规范中的 IDL
在规范中,DOM 类不是使用 JavaScript 来描述的,而是一种特殊的 接口描述语言(Interface description language),简写为 IDL,它通常很容易理解。
在 IDL 中,所有属性以其类型开头。例如,
DOMString
和 boolean
等。以下是摘录(excerpt),并附有注释:
“innerHTML+=” 会进行完全重写
“innerHTML+=” 会进行完全重写
我们可以使用
elem.innerHTML+="more html"
将 HTML 附加到元素上。但我们必须非常谨慎地使用它,因为我们所做的 不是 附加内容,而且完全地重写。
换句话说,
innerHTML+=
做了以下工作:- 移除旧的内容。
- 然后写入新的
innerHTML
(新旧结合)。
因为内容已“归零”并从头开始重写,因此所有的图片和其他资源都将重写加载。
并且还会有其他副作用。例如,如果现有的文本被用鼠标选中了,那么大多数浏览器都会在重写
innerHTML
时删除选定状态。如果这里有一个带有用户输入的文本的 <input>
,那么这个被输入的文本将会被移除。诸如此类。设置元素样式的方式
设置元素样式的方式
通常有两种设置元素样式的方式:
- 在 CSS 中创建一个类,并添加它:
<div class="...">
- 将属性直接写入
style
:<div style="...">
。
JavaScript 既可以修改类,也可以修改
style
属性。相较于将样式写入
style
属性,我们应该首选通过 CSS 类的方式来添加样式。仅当类“无法处理”时,才应选择使用 style
属性的方式。自定义事件
自定义事件
事件构造器
内建事件类形成一个层次结构(hierarchy),类似于 DOM 元素类。根是内建的 Event 类。
我们可以像这样创建
Event
对象:参数:
- type —— 事件类型,可以是像这样
"click"
的字符串,或者我们自己的像这样"my-event"
的参数。
- options —— 具有两个可选属性的对象:
bubbles: true/false
—— 如果为true
,那么事件会冒泡。cancelable: true/false
—— 如果为true
,那么“默认行为”就会被阻止。稍后我们会看到对于自定义事件,它意味着什么。
默认情况下,以上两者都为 false:
{bubbles: false, cancelable: false}
。事件对象被创建后,我们应该使用
elem.dispatchEvent(event)
调用在元素上“运行”它。event.isTrusted
对于来自真实用户操作的事件,
event.isTrusted
属性为 true
,对于脚本生成的事件,event.isTrusted
属性为 false
。其他像
MouseEvent
和 KeyboardEvent
这样的原生事件的构造器,都接受特定于该事件类型的属性。例如,鼠标事件的 clientX
。自定义事件
对于我们自己的全新事件类型,例如
"hello"
,我们应该使用 new CustomEvent
。从技术上讲,CustomEvent 和 Event
一样。除了一点不同。在第二个参数(对象)中,我们可以为我们想要与事件一起传递的任何自定义信息添加一个附加的属性
detail
。默认行为:
通过调用
event.preventDefault()
,事件处理程序可以发出一个信号,指出这些行为应该被取消。在这种情况下,
elem.dispatchEvent(event)
的调用会返回 false
。那么分派(dispatch)该事件的代码就会知道不应该再继续。事件中的事件是同步的
通常事件是在队列中处理的。也就是说:如果浏览器正在处理
onclick
,这时发生了一个新的事件,例如鼠标移动了,那么它的处理程序会被排入队列,相应的 mousemove
处理程序将在 onclick
事件处理完成后被调用。值得注意的例外情况就是,一个事件是在另一个事件中发起的。例如使用
dispatchEvent
。这类事件将会被立即处理,即在新的事件处理程序被调用之后,恢复到当前的事件处理程序。页面生命周期
页面生命周期
HTML 页面的生命周期包含三个重要事件:
DOMContentLoaded
—— 浏览器已完全加载 HTML,并构建了 DOM 树,但像<img>
和样式表之类的外部资源可能尚未加载完成。
load
—— 浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等。
beforeunload/unload
—— 当用户正在离开页面时。
每个事件都是有用的:
DOMContentLoaded
事件 —— DOM 已经就绪,因此处理程序可以查找 DOM 节点,并初始化接口。
load
事件 —— 外部资源已加载完成,样式已被应用,图片大小也已知了。
beforeunload
事件 —— 用户正在离开:我们可以检查用户是否保存了更改,并询问他是否真的要离开。
event.preventDefault()
在 beforeunload
处理程序中不起作用unload
事件 —— 用户几乎已经离开了,但是我们仍然可以启动一些操作,例如发送统计数据。
当用户要离开的时候,我们希望通过
unload
事件将数据保存到我们的服务器上。有一个特殊的
navigator.sendBeacon(url, data)
方法可以满足这种需求它在后台发送数据,转换到另外一个页面不会有延迟:浏览器离开页面,但仍然在执行
sendBeacon
。浏览器内建的自动填充
Firefox,Chrome 和 Opera 都会在
DOMContentLoaded
中自动填充表单。例如,如果页面有一个带有登录名和密码的表单,并且浏览器记住了这些值,那么在
DOMContentLoaded
上,浏览器会尝试自动填充它们(如果得到了用户允许)。因此,如果
DOMContentLoaded
被需要加载很长时间的脚本延迟触发,那么自动填充也会等待。你可能在某些网站上看到过(如果你使用浏览器自动填充)—— 登录名/密码字段不会立即自动填充,而是在页面被完全加载前会延迟填充。这实际上是 DOMContentLoaded
事件之前的延迟。readyState
document.readyState
属性可以为我们提供当前加载状态的信息。它有 3 个可能值:
loading
—— 文档正在被加载。
interactive
—— 文档被全部读取。
complete
—— 文档被全部读取,并且所有资源(例如图片等)都已加载完成。
还有一个
readystatechange
事件,会在状态发生改变时触发,因此我们可以打印所有这些状态输出:
- [1] initial readyState:loading
- [2] readyState:interactive
- [2] DOMContentLoaded
- [3] iframe onload
- [4] img onload
- [4] readyState:complete
- [4] window onload
脚本:async,defer
脚本:async,defer
当浏览器加载 HTML 时遇到
<script>...</script>
标签,浏览器就不能继续构建 DOM。它必须立刻执行此脚本。对于外部脚本 <script src="..."></script>
也是一样的:浏览器必须等脚本下载完,并执行结束,之后才能继续处理剩余的页面。这会导致两个重要的问题:
- 脚本不能访问到位于它们下面的 DOM 元素,因此,脚本无法给它们添加处理程序等。
- 如果页面顶部有一个笨重的脚本,它会“阻塞页面”。在该脚本下载并执行结束前,用户都不能看到页面内容:
defer
defer
特性告诉浏览器不要等待脚本。相反,浏览器将继续处理 HTML,构建 DOM。脚本会“在后台”下载,然后等 DOM 构建完成后,脚本才会执行。换句话说:
- 具有
defer
特性的脚本不会阻塞页面。
- 具有
defer
特性的脚本总是要等到 DOM 解析完毕,但在DOMContentLoaded
事件之前执行。(DOMContentLoaded
事件处理程序等待具有defer
特性的脚本执行完成。它仅在脚本下载且执行结束后才会被触发。)
具有
defer
特性的脚本保持其相对顺序,就像常规脚本一样。defer
特性仅适用于外部脚本如果
<script>
脚本没有 src
,则会忽略 defer
特性。async
async
特性与 defer
有些类似。它也能够让脚本不阻塞页面。但是,在行为上二者有着重要的区别。async
特性意味着脚本是完全独立的:- 浏览器不会因
async
脚本而阻塞(与defer
类似)。
- 其他脚本不会等待
async
脚本加载完成,同样,async
脚本也不会等待其他脚本。
DOMContentLoaded
和异步脚本不会彼此等待:DOMContentLoaded
可能会发生在异步脚本之前(如果异步脚本在页面完成后才加载完成)DOMContentLoaded
也可能发生在异步脚本之后(如果异步脚本很短,或者是从 HTTP 缓存中加载的)
换句话说,
async
脚本会在后台加载,并在加载就绪时运行。DOM 和其他脚本不会等待它们,它们也不会等待其它的东西。async
脚本就是一个会在加载完成时执行的完全独立的脚本。async
特性仅适用于外部脚本就像
defer
一样,如果 <script>
标签没有 src
特性(attribute),那么 async
特性会被忽略。动态脚本
使用 JavaScript 动态地创建一个脚本,并将其附加(append)到文档(document)中
默认情况下,动态脚本的行为是“异步”的。
也就是说:
- 它们不会等待任何东西,也没有什么东西会等它们。
- 先加载完成的脚本先执行(“加载优先”顺序)。
顺序 | DOMContentLoaded | |
async | 加载优先顺序。脚本在文档中的顺序不重要 —— 先加载完成的先执行 | 不相关。可能在文档加载完成前加载并执行完毕。如果脚本很小或者来自于缓存,同时文档足够长,就会发生这种情况。 |
defer | 文档顺序(它们在文档中的顺序) | 在文档加载和解析完成之后(如果需要,则会等待),即在 DOMContentLoaded 之前执行。 |
DOM 变动观察器(Mutation observer)
DOM 变动观察器(Mutation observer)
MutationObserver
是一个内建对象,它观察 DOM 元素,并在检测到更改时触发回调。config
是一个具有布尔选项的对象,该布尔选项表示“将对哪些更改做出反应”:childList
——node
的直接子节点的更改,
subtree
——node
的所有后代的更改,
attributes
——node
的特性(attribute),
attributeFilter
—— 特性名称数组,只观察选定的特性。
characterData
—— 是否观察node.data
(文本内容),
attributeOldValue
—— 如果为true
,则将特性的旧值和新值都传递给回调(参见下文),否则只传新值(需要attributes
选项),
characterDataOldValue
—— 如果为true
,则将node.data
的旧值和新值都传递给回调(参见下文),否则只传新值(需要characterData
选项)。
然后,在发生任何更改后,将执行“回调”:更改被作为一个 MutationRecord 对象列表传入第一个参数,而观察器自身作为第二个参数。
MutationRecord 对象具有以下属性:
type
—— 变动类型,以下类型之一:"attributes"
:特性被修改了,"characterData"
:数据被修改了,用于文本节点,"childList"
:添加/删除了子元素。
target
—— 更改发生在何处:"attributes"
所在的元素,或"characterData"
所在的文本节点,或"childList"
变动所在的元素,
addedNodes/removedNodes
—— 添加/删除的节点,
previousSibling/nextSibling
—— 添加/删除的节点的上一个/下一个兄弟节点,
attributeName/attributeNamespace
—— 被更改的特性的名称/命名空间(用于 XML),
oldValue
—— 之前的值,仅适用于特性或文本更改,如果设置了相应选项attributeOldValue
/characterDataOldValue
。
有一个方法可以停止观察节点:
observer.disconnect()
—— 停止观察。
当我们停止观察时,观察器可能尚未处理某些更改。在种情况下,我们使用:
observer.takeRecords()
—— 获取尚未处理的变动记录列表,表中记录的是已经发生,但回调暂未处理的变动。
选择(Selection)和范围(Range)
选择(Selection)和范围(Range)
用于选择的两种不同的 API:
- 对于文档:
Selection
和Range
对象。
- 对于
input
,textarea
:其他方法和属性。
第二个 API 非常简单,因为它处理的是文本。
第二个 API 非常简单,因为它处理的是文本。
最常用的方案一般是:
- 获取选择:
- 设置选择
最后,关于光标。在诸如
<textarea>
之类的可编辑元素中,光标的位置始终位于选择的起点或终点。我们可以通过设置 elem.selectionStart
和 elem.selectionEnd
来获取光标位置或移动光标。详细查看:
事件循环
事件循环
事件循环 的概念非常简单。它是一个在 JavaScript 引擎等待任务,执行任务和进入休眠状态等待更多任务这几个状态之间转换的无限循环。
每个宏任务之后,引擎会立即执行微任务队列中的所有任务,然后再执行其他的宏任务,或渲染,或进行其他任何操作。
更详细的事件循环算法:
- 从 宏任务 队列(例如 “script”)中出队(dequeue)并执行最早的任务。
- 执行所有 微任务:
- 当微任务队列非空时:
- 出队(dequeue)并执行最早的微任务。
- 如果有变更,则将变更渲染出来。
- 如果宏任务队列为空,则休眠直到出现宏任务。
- 转到步骤 1。
安排(schedule)一个新的 宏任务:
- 使用零延迟的
setTimeout(f)
。
它可被用于将繁重的计算任务拆分成多个部分,以使浏览器能够对用户事件作出反应,并在任务的各部分之间显示任务进度。
此外,也被用于在事件处理程序中,将一个行为(action)安排(schedule)在事件被完全处理(冒泡完成)后。
安排一个新的 微任务:
- 使用
queueMicrotask(f)
。
- promise 处理程序也会通过微任务队列。
在微任务之间没有 UI 或网络事件的处理:它们一个立即接一个地执行。
参考链接:
- 作者:JinSo
- 链接:https://jinso365.top/article/modern-javascript-browser
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。