JavaScript高级程序设计学习记录-DOM、DOM扩展及DOM事件

508 阅读5分钟

ES6-DOM

1. DOM

文档对象模型是 HTML 和 XML 文档的编程接口。DOM 表示由多层节点构成的文档。脱胎于网景和微软早期的动态 HTML,DOM 现在是真正跨平台、语言无关的表示和操作网页的方式。

1.1 节点层级

document 节点表示每个文档的根节点。根节点的唯一子节点是<html> 元素,我们称之为文档元素 ,其他元素都存在这个元素之内。在 HTML 页面,文档元素始终是html元素。

DOM 中总共有 12 中节点类型,这些类型都继承一种基本类型

  1. Node 类型
  2. Document 类型:表示文档节点
  3. Element 类型:nodeType = 1,nodeName = 标签名
  4. Text 类型: nodeType = 3, nodeName = '#text', 是 DOM 中的文本
  5. Comment:nodeType = 8, nodeName = '#Comment' ,是 DOM 中的注释
  6. CDATASection:nodeType = 4, nodeName = '#cdata-section'
  7. DocumentType:nodeType = 10
  8. DocumentFragment:nodeType = 11
  9. 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 由一些列节点类型构成,主要包括以下几种:

  1. Node是基准节点类型,是文档一个部分的抽象表示,所有其他类型都继承 Node
  2. Document类型表示整个文档,对应属性结构的根节点,在 JavaScript 中,document对象是Document的实例,拥有查询和获取节点的很多方法。
  3. Element节点表示文档中所有 HTML 或 XML 元素,可以用来操作内容和属性
  4. 其他节点分别表示文本内容、注释、文档类型、CDATA 区块和文档片段。

DOM 操作在 JavaScript 中是代价比较高的,NodeList 对象更甚,因为它是实时更新的,每次访问都会执行一次新的查询。

使用tationObserver 可以有效精准地监控 DOM 变化,而且 API 相对简单。

2. DOM 扩展

DOM 扩展到额两个标准: Selectors APIHTML5

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 个属性:

  1. childElementCount ,返回子元素数量(不包含文本节点和注释)
  2. firstElementChild ,指向第一个 Element 类型的子元素( Element 版 firstChild )
  3. lastElementChild ,指向最后一个 Element 类型的子元素( Element 版 lastChild )
  4. previousElementSibling , 指 向 前 一 个 Element 类 型 的 同 胞 元 素 ( Element 版 previousSibling )
  5. nextElementSibling ,指向后一个 Element 类型的同胞元素( Element 版 nextSibling )

2.3 HTML5

HTML5 代表着与以前的 HTML 截然不同的方向,HTML5 规范包含了与标记相关的大量 JavaScript API 定义。

2.3.1 CSS 类扩展

1.getElementsByClassName()

接收一个包含一个或多个类型的字符串参数,返回对应的 NodeList

2.classList

提供 4 个方法:

  1. add(value): 添加,如果已经存在则不操作
  2. contains(value):返回 boolean,是否存在
  3. remove(value):删除
  4. 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 专有扩展

  1. children 属性 ,只包含元素的 Element 类型的节点
  2. contains(value), 匹配后代 value 节点,有返回 true,否则返回 false
  3. innerText 和 outerText
  4. 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 小结

  • 限制一个页面中事件处理程序的数量
  • 利用事件冒泡,事件委托可以解决限制事件处理程序数量的问题
  • 在页面卸载之前删除所有事件处理程序