ES6-DOM
1. DOM
文档对象模型是 HTML 和 XML 文档的编程接口。DOM 表示由多层节点构成的文档。脱胎于网景和微软早期的动态 HTML,DOM 现在是真正跨平台、语言无关的表示和操作网页的方式。
1.1 节点层级
document 节点表示每个文档的根节点。根节点的唯一子节点是<html> 元素,我们称之为文档元素 ,其他元素都存在这个元素之内。在 HTML 页面,文档元素始终是html元素。
DOM 中总共有 12 中节点类型,这些类型都继承一种基本类型
- Node 类型
- Document 类型:表示文档节点
- Element 类型:nodeType = 1,nodeName = 标签名
- Text 类型: nodeType = 3, nodeName = '#text', 是 DOM 中的文本
- Comment:nodeType = 8, nodeName = '#Comment' ,是 DOM 中的注释
- CDATASection:nodeType = 4, nodeName = '#cdata-section'
- DocumentType:nodeType = 10
- DocumentFragment:nodeType = 11
- Attr 类型:nodeType = 2
1.2 DOM 编程
操作 DOM 以使用JavaScript实现,使用<script>元素用于向网页中插入 Javascript 代码,也可以使用<link>元素用于包含 css 外部文件,而<style>元素可以添加嵌入样式
1.3 MutationObserver
MutationObserver接口可以在 DOM 被修改时异步执行回调,可以观察整个文档、DOM 树或某个元素以及观察元素属性、子节点、文本或者前三者任意组合的变化。
调用MutationObserver 构造函数并传入一个回调函数来创建实例
新创建的实例需要使用observe()方法把实例与 DOM 关联,接收两个必须的参数:要观察的 DOM 节点、一个MutationObserverInit 对象
注意:回调中的 console.log 是后执行的,表明回掉并非与实际的 DOM 变化同步执行
每个回调会受到一个MutationRecord实例的数组,包括发生的变化及受到影响的 DOM。
提前终止监听可调用disconnect() 方法
let observer = new MutationObserver( mutationRecords => {
console.log('<body> attributes changed')
console.log(mutationRecords)
})
observer.observe(document.body, { attributes: true })
document.className = 'foo'
console.log('change')
// change
// body attrbutes changed
1.4 小结
文档对象模型是语言中立的 HTML 和 XML 文档的 API。
DOME 由一些列节点类型构成,主要包括以下几种:
Node是基准节点类型,是文档一个部分的抽象表示,所有其他类型都继承 NodeDocument类型表示整个文档,对应属性结构的根节点,在 JavaScript 中,document对象是Document的实例,拥有查询和获取节点的很多方法。Element节点表示文档中所有 HTML 或 XML 元素,可以用来操作内容和属性- 其他节点分别表示文本内容、注释、文档类型、CDATA 区块和文档片段。
DOM 操作在 JavaScript 中是代价比较高的,NodeList 对象更甚,因为它是实时更新的,每次访问都会执行一次新的查询。
使用tationObserver 可以有效精准地监控 DOM 变化,而且 API 相对简单。
2. DOM 扩展
DOM 扩展到额两个标准: Selectors API 和 HTML5
2.1 Selectors API
Selectors API 规定了浏览器原生支持的 CSS 查询 API,核心方法有两个: querySelector() 和querySelectorAll() 。lever2 规范新增了matches()
querySelector()方法接收 CSS 选择符参数,返回匹配该模式的第一个后代元素,如果没有则返回 null
querySelectorAll() 接收参数同上,但回返回所有匹配的节点,返回的是一个NodeList 的静态实例。静态实例并非实时的查询,避免了性能问题。
matches() 接收一个 CSS 选择符参数,如果匹配则返回 true,否则返回 false
2.2 元素遍历
Element Traversal API 为 DOM 元素添加了 5 个属性:
- childElementCount ,返回子元素数量(不包含文本节点和注释)
- firstElementChild ,指向第一个 Element 类型的子元素( Element 版 firstChild )
- lastElementChild ,指向最后一个 Element 类型的子元素( Element 版 lastChild )
- previousElementSibling , 指 向 前 一 个 Element 类 型 的 同 胞 元 素 ( Element 版 previousSibling )
- nextElementSibling ,指向后一个 Element 类型的同胞元素( Element 版 nextSibling )
2.3 HTML5
HTML5 代表着与以前的 HTML 截然不同的方向,HTML5 规范包含了与标记相关的大量 JavaScript API 定义。
2.3.1 CSS 类扩展
1.getElementsByClassName()
接收一个包含一个或多个类型的字符串参数,返回对应的 NodeList
2.classList
提供 4 个方法:
- add(value): 添加,如果已经存在则不操作
- contains(value):返回 boolean,是否存在
- remove(value):删除
- toggle(value):如果存在,删除;如果不存在则添加
2.3.2 自定义数据属性
使用前缀data- 自定义数据属性,通过dataset属性来访问
// 本例中使用的方法仅用于示范
let div = document.getElementById("myDiv");
// 取得自定义数据属性的值
let appId = div.dataset.appId;
let myName = div.dataset.myname;
// 设置自定义数据属性的值
div.dataset.appId = 23456;
div.dataset.myname = "Michael";
// 有"myname"吗?
if (div.dataset.myname){
console.log(`Hello, ${div.dataset.myname}`);
}
2.4 专有扩展
- children 属性 ,只包含元素的 Element 类型的节点
- contains(value), 匹配后代 value 节点,有返回 true,否则返回 false
- innerText 和 outerText
- scrollIntoView
3. DOM2 和 DOM3
3.1 样式
HTML 中的样式有 3 中定义方式:link 标签、style 标签和行内 style 属性
3.1.1 存取元素样式
HTML 元素在 JavaScript 中会有一个对应的 style 属性,是CSSStyleDeclaration类型的实例,在 JavaScript 中使用 CSS 属性名是必须转换位驼峰大小写形式,如background-image在 JavaScript 中要写成backgroundImage ,float 比较特殊,对应的是cssFloat。
<div id="div" style="backgournd-color: pink; height: 10px; width: 10px"></div>
let div = querySelector('#div')
console.log(div.style.backgroundColor) // pink
console.log(div.style.height) // 10px
3.1.2 元素尺寸
1.偏移尺寸
偏移尺寸,包含元素在屏幕上占用的所有视觉空间,包括所有内边距、滚动条和边框,不包含外边距。以下 4 个属性用于取得元素的偏移尺寸。
offsetHeight,元素在垂直方向上占用的像素尺寸,包括它的高度、水平滚动条高度和上下边框的高度。offsetLeft,原色在左边框外侧距离包含元素左边框内侧的像素数。offsetTop,元素上边框外侧距离包含元素上边框内侧的像素数offsetWidth,元素在水平方向上占用的像素尺寸,高阔它的宽度、吹吃滚动条宽度和左右边框的宽度。
偏移尺寸是只读的,每次访问重新计算,性能成本高。
2.客户端尺寸
包含元素内容及其内边距所占用的空间,只有两个相关属性:
clientWidth,内容区宽度加左右内边距宽度。clientHeight,内容区高度加上下内边距高度。
客户端尺寸是元素内部的空间,不包含滚动条。用于确定浏览器吃口尺寸,即检测 document.documentElement 的宽和高,表示或的尺寸。
客户端尺寸是只读的,每次访问都会重新计算
3.滚动尺寸
提供元素内容滚动距离的信息,包含 4 个属性:
scrollHeight,没有滚动条出现时,元素内容的总高度。scrollLeft,内容区左侧隐藏的像素数,设置属性可以改变元素的 x 轴滚动位置。scrollTop,内容区顶部隐藏的像素数,设置属性可以改变元素的 y 轴滚动位置scrollWidth,没有滚动条出现时,元素内容的总宽度。
4.确定元素尺寸
每个元素都暴露了getBoundingClientRect()方法,返回一个 DOMRect 对象,包含留个属性:left、top、right、bottom、height 和 width。
4. DOM 事件
4.1 事件流
事件流分两种:事件冒泡和事件捕获。
事件冒泡即从最具体的元素开始出发,向上传播至没有那么具体的元素。
事件捕获是最不具体的节点先触发,最具体的元素最后触发
<!DOCTYPE html>
<html>
<head>
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html
事件冒泡的顺序是 div > body > html > document。
事件捕获是 document > html > body > div
DOM2 Events 规定的事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。
4.2 事件处理程序
为响应事件而调用的函数被称为事件处理程序(事件监听器 ),比如点击 click、加载 load、鼠标悬停 mouseover。名字以“on”开头,所以 click 的处理程序叫做 onclick。
给 dom 添加事件处理程序有两种方法:
- 在 dom 上添加 onclick 属性
<div onclick="console.log('click me')"></div> - 把事件处理程序赋值为一个函数
let div = document.querySelector('div')
div.onclick = function() { console.log('help me') }
事件处理程序的赋值和移除有两个方法:addEventListener()和 remove-EventListener()。这两个方法接收 3 个参数:事件名、事件处理函数、一个布尔值:true 表示在捕获阶段调用事件处理程序,false(默认)表示在冒泡阶段调用。添加多个事件处理程序会按照添加程序来触发。
removeEventListener()用来移除通过 addEventListener()添加的事件处理程序,但无法移除匿名函数。
div.addEventListener('click', () => {
console.log('help me')
}, false)
4.3 事件对象
DOM 中发生的事件,所有相关信息都会被收集并存储在event对象中。所有浏览器都支持。主要包含以下几种属性和方法:
currentTarget当前所在的元素preventDefault()用于取消事件的默认行为target事件目标type被触发的事件类型
4.4 事件类型
- 用户界面事件(UIEvent):设计与 BOM 交互的通用浏览器事件
- 焦点事件(FocusEvent):在元素获得和失去焦点时触发。
- 鼠标事件(MouseEvent):使用鼠标滚轮时触发
- 输入事件(InputEvent):向文档中输入文本时触发。
- 键盘事件(keyboardEvent):键盘触发
- 和城市间(CompositionEvent):使用 IME 输入字符时触发
4.4.1 用户界面事件
load:window 上的页面加载完成后触发,img 的图片加载完成,frame 的多有创个加载完成触发unload:卸载完成后触发error:报错时触发select:input 或 textarea 上选择时触发resize:窗口缩放时触发scroll:滚动包含滚动条的元素时触发
window.addEventListener('load', () => {
let image = new Image();
image.addEventListener('load', event =>{
console.log('img loaded')
});
image.src = 'happy.png'
})
4.4.2 焦点事件
blur:失去焦点触发,不冒泡focus:获得焦点触发,不冒泡focusin:获得焦点触发,冒泡focusout:失去焦点触发,冒泡
4.4.3 鼠标和滚轮事件
- click:单击鼠标主键或回车时触发
- dblclick: 双击鼠标主键触发
- mousedown:按下任意鼠标键时触发
- mouseenter:鼠标光标从元素外部移到元素内部时触发,不冒泡
- mouseleave:鼠标从元素内部移到元素外部时触发,不冒泡
- mousemove:鼠标光标在元素上移动时反复触发
- mouseout:鼠标从一个元素移到另一个元素上时触发
- mouseover:鼠标从元素外部移动到元素内部时触发
- mouseup:鼠标键释放时触发
事件触发顺序:
mousedown → mouseup → click → mousedown → mouseup → click → dblclick
4.4.4 HTML5 事件
1.contextmenu 事件
上下文菜单,在 windows 上市鼠标右键,在 mac 上市 Ctrl+单击。contextmenu 事件冒泡,所以只要指定一个事件处理程序就可以处理页面上的所有同类事件,可以使用 event.preventDefault()取消。
window.addEventListener('load', () => {
let div = document.getElementById('#myDiv')
div.addEventListener('contextmenu', event => {
event.preventDefault()
let menu = document.getElementById('myMenu')
menu.style.left = event.clientX + 'px'
menu.style.top = event.clientY + 'px'
menu.style.display = 'block'
})
document.addEventListener('click', () => {
document.getElementById('myMenu').style.display = 'none'
})
})
2.beforeunload 事件
在 window 上触发,用意是给开发者提供阻止页面被卸载的机会,在页面被卸载时触发。
window.addEventListener('beforeunload', (event) => {
event.returnValue = '';
});
3.haschange 事件
在 URL 散列值(URL 最后#后面的部分)发生变化时触发。调用时 event 对象有两个新属性,oldURL 和 newURL。
window.addEventListener('haschange', event => {
console.log(`oldurl: ${event.oldURL},newurl: ${event.newURL}`)
console.log(`current hash: ${location.hash}`)
})
4.4.5 设备事件
1 orientationchange
判断设备处于垂直模式还是水平模式,window 上暴露 window.orientation 属性,有以下 3 种值:0 表示垂直,90 表示左转水平,-90 表示右转水平。
window.addEventListener('orientationchange', event => {
div.innerHTML = "Current orientation is " + window.orientation
})
2 deviceorientation 事件
反映设备在空间中的朝向
3 devicemotion 事件
用于提示设备实际上在移动
window.addEventListener("devicemotion", (event) => {
let output = document.getElementById("output");
if (event.rotationRate !== null) {
output.innerHTML += `Alpha=${event.rotationRate.alpha}` +
`Beta=${event.rotationRate.beta}` +
`Gamma=${event.rotationRate.gamma}`;
}
});
4.4.6 触摸及手势事件
1 触摸事件
当手指放在屏幕上、在屏幕上滑动或从屏幕移开时,触摸事件即会触发。事件有以下几种:
touchstart:手指放到屏幕上时触发touchmove:手指在屏幕上滑动时连续触发。调用 preventDefault()可以阻止滚动touched:手指从屏幕上移开时触发touchcancel:系统停止跟踪触摸时触发
这些时间段偶会冒泡,也都可被取消。每个触摸事件的event对象都提供了鼠标事件的公共属性:bubbles 、cancelable 、 view 、 clientX 、 clientY 、 screenX 、 screenY 、 detail 、 altKey 、 shiftKey 、ctrlKey 和 metaKey 。
触摸事件还提供了 3 个属性用于跟踪触电:
touches:Touch 对象的数组,表示当前屏幕上的每个触点。targetTouches:Touch 对象的数组,表示特定于事件目标的触点。changedTouches:Touch 对象的数组,表示自上次用户动作之后变化的触点。
每个 Touch 对象都包含下列属性:
- clientX:触点在视口中的 x 坐标
- clientY:触点在视口中的 y 坐标
- identifier:触点 ID
- pageX:触点在页面上的 x 坐标
- pageY:触点在页面上的 x 坐标
- screenX:触点在屏幕上的 x 坐标
- screenY:触点在屏幕上的 y 坐标
- target:触摸事件的目标
function handleTouchEvent(event) {
if(event.touches.length == 1) {
let output = document.querySelector('#output')
switch(event.type) {
case 'touchstart':
output.innerHtml += `Touch started ${event.touches[0].clientX}`
break;
case 'touched':
output.innerHtml += `Touch ended ${event.changedTouches[0].clientX}`
break;
case 'touchmove':
output.innerHtml += `Touch move ${event.changedTouches[0].clientX}`
}
}
}
document.addEventListener("touchstart", handleTouchEvent);
document.addEventListener("touchend", handleTouchEvent);
document.addEventListener("touchmove", handleTouchEvent);
当手指点触屏幕上的元素时,事件执行顺序为:
touchestart → mouseover → mousemove → mosedown → mouseup → click → touchend
2 手势事件
getsturestart:一个手指已经在屏幕上,另一个手指放屏幕上时触发gesturechange:任何一个手指在屏幕上的位置发生变化时触发。gestureend:其中一个手指离开屏幕时触发。
4.5 内存与性能
4.5.1 事件委托
利用事件冒泡,只是用一个事件管理程序来管理一种类型的事件。
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
let list = document.querySelector('#myLinks')
list.addEventListener('click', event => {
let { target } = event
switch(target.id) {
case 'doSomething':
console.log('doSomething')
break;
case 'goSomeWhere':
console.log('goSomeWhere')
break;
case 'sayHi':
console.log('Hi')
break;
}
})
给 ul 元素添加一个 onclick 事件,所有 li 都是他的后代,li 的事件会向上冒泡,最终由这个事件来处理。这种方式占用内存更少,所有使用按钮的事件都适用于这个解决方案。
4.5.2 删除事件处理程序
在 onunload 事件处理程序中删除所有的事件处理程序:在 onload 里做了什么,在 onunload 中删除。
4.6 小结
- 限制一个页面中事件处理程序的数量
- 利用事件冒泡,事件委托可以解决限制事件处理程序数量的问题
- 在页面卸载之前删除所有事件处理程序