原稿:语雀 · DOM & 事件 · 原目录:JavaScript › DOM & 事件

获取元素

id是文档中元素的不可重复属性,可以使用 document.getElementById(id) 来获取元素

<div id="elem">
  <div id="elem-content">Element</div>
</div>

<script>
  // 获取该元素
  let elem = document.getElementById('elem');
  // 将该元素背景改为红色
  elem.style.background = 'red';
</script>

最通用的方法是 elem.querySelectorAll(css),它返回 elem 中与给定 CSS 选择器匹配的所有元素(类数组NodeList);elem.querySelector(css) 会返回给定 CSS 选择器的第一个元素。其结果与 elem.querySelectorAll(css)

00

相同,但后者会查找所有元素后再返回,而 elem.querySelector 只会查找一个便结束

<ul>
  <li>The</li>
  <li>test</li>
</ul>
<ul>
  <li>has</li>
  <li>passed</li>
</ul>
<script>
  let elements = document.querySelectorAll('ul > li:last-child');
  for (let elem of elements) {
    alert(elem.innerHTML); // "test", "passed"
  }
</script>
  • 该搜索器支持伪类,如 :hover 和 :active,document.querySelectorAll(’:hover’),但是不支持伪元素,伪元素会使得返回结果始终为空列表
  • getElementById(id) 获取的是动态集合,querySelector 获取的是静态集合,即如果在脚本中修改DOM,以 querySelector 获取的元素并不会随之更新

事件流

DOM2事件规范规定事件流分为3 个阶段:事件捕获、到达目标和事件冒泡 2. 事件捕获:最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件。事件捕获最先发生,为提前拦截事件提供了可能 4. 到达目标:实际的目标元素接收到事件 6. 事件冒泡:从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档),最迟要在该阶段响应事件

event.png

三种事件模型

DOM0 事件处理程序

DOM0 级模型第是最早的事件模型,这种模型不会传播,所以没有事件流的概念,但是现代浏览器支持以冒泡的方式处理。通常是把一个函数赋值给一个元素的事件处理程序 onXXX:

window.onresize = function () {
    console.log('变了');
};

此外 DOM0 支持直接把处理程序写在 html 上,但会增加 html 和 js 的耦合

<input type="button" value="Click Me" onclick="console.log(value)">

DOM2 事件处理程序

该模型中,一次事件共有三个过程:事件捕获、到达目标和事件冒泡

DOM2 事件处理程序的赋值和移除方法为:addEventListener() 和 removeEventListener(),暴露在所有 DOM 节点上,接收3个参数:事件名、事件处理函数和一个布尔值,true表示在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序

默认事件处理程序会被添加到事件流的冒泡阶段,主要原因是跨浏览器兼容性好。如果不需要拦截,则不要使用事件捕获。

let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
    console.log(this.id);
}, false);

IE 事件处理程序

类似 DOM2,IE 实现了类似的方法,attachEvent() 和 detachEvent(),IE8 之前只支持冒泡,因此attachEvent() 将处理程序默认添加到冒泡阶段

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function () {
  console.log("Clicked");
});

比较

DOM0级 vs DOM2级 2. DOM0 级事件只有冒泡阶段,DOM2 级事件的第三个参数 false 表示冒泡,true 表示捕获 4. DOM0 只能为同时为相同事件绑定一个事件函数,后绑定的会覆盖掉前面的,而DOM2级事件可以绑定多个事件函数

IE vs DOM 2. 使用 DOM0 方式时,事件处理程序中的 this 值等于目标元素。而使用 attachEvent() 时,事件处理程序是在全局作用域中运行的,因此 this 等于 window 4. 与使用 addEventListener()一样,使用 attachEvent() 方法也可以给一个元素添加多个事件处理程序,但不同于DOM2,IE方式下事件处理程序会以添加它们的顺序反向触发

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function () {
  console.log("Clicked");
});
btn.attachEvent("onclick", function () {
  console.log("Hello world!");
});
// IE模型下:先输出Hello world!,再输出Clicked

事件对象

事件对象是事件触发时,浏览器传递给事件处理函数的实参,其中包含了本次事件触发的具体信息

可通过事件处理程序打印事件或其中属性

let btn = document.getElementById("myBtn");
// DOM0
btn.onclick = function(event) {
    console.log(event.type); // "click"
};
// DOM2
btn.addEventListener("click", (event) => {
    console.log(event.type); // "click"
}, false);

下面说明几个重要的事件对象属性,更多可见 w3school

  • bubbles:事件是否冒泡
  • currentTarget:当前事件处理程序所在的元素
  • target:事件实际目标元素
  • stopImmediatePropagation():取消所有后续事件捕获或冒泡,阻止调用任何事件处理程序
  • stopPropagation():取消所有后续事件捕获或冒泡,但 bubbles 必须为true

事件委托

在 JS 中,页面中事件处理程序的数量与页面整体性能直接相关,因为

  • 每个函数都是对象,都占用内存空间,对象越多,性能越差
  • 指定事件处理程序所需访问 DOM 的次数会先期造成整个页面交互的延迟(脚本与渲染过程抢占主线程)

针对过多处理程序的解决方法是事件委托机制:

  • 利用事件冒泡,可以把事件绑定给更高级元素,只使用一个事件处理程序来管理一种类型的事件
  • 对于不可冒泡事件(blur、focus、scroll),在捕获阶段进行委托处理

页面有一个 <ul> ,一个 <button>,要求每点击一次按钮就向 <ul> 中添加一个 <li>,要求每次监听到 <li> 的 hover 时,打印元素的位置和内容

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8"></meta>
        <title>事件委托</title>
        <style>
        </style>
    </head>
    <body>
        <button id="btn">增加 li 元素</button>
        <ul id="list"></ul>
    </body>
    <script src="./main.js"></script>
</html>
let btn = document.getElementById("btn");
let list = document.getElementById("list");
btn.addEventListener("click", () => {
  let item = document.createElement("li");
  item.innerText = "It's a new li";
  list.appendChild(item);
});

list.addEventListener("mouseover", (event) => {
  let { target } = event;
  let { left, right, top, bottom } = target.getBoundingClientRect();
  console.log(left, right, top, bottom);
  console.log(`Content:${target.innerHTML}`);
});

常用 DOM 操作

(1)创建新节点

document.createElement(node);

(2)添加、移除、替换、插入

ele.appendChild(node)
ele.removeChild(node)
ele.replaceChild(new,old)
ele.insertBefore(new,old)

(3)查找

// 基于名称搜索,获得动态结点列表
getElementById();
getElementsByName();
getElementsByTagName();
getElementsByClassName();

// 基于选择器搜索,获得静态结点列表,可选择伪类,不可选择伪元素
querySelector();
querySelectorAll();

// 兄弟节点
previousNode = node.previousSibling
nextNode = node.nextSibling

(4)属性操作

getAttribute(key);
setAttribute(key, value);
hasAttribute(key);
removeAttribute(key);

(5)元素坐标

2119443-20200909153534871-221163338.png
var box=document.getElementById('box');         // 获取元素
alert(box.getBoundingClientRect().top);         // 元素上边距离页面上边的距离
alert(box.getBoundingClientRect().right);       // 元素右边距离页面左边的距离
alert(box.getBoundingClientRect().bottom);      // 元素下边距离页面上边的距离
alert(box.getBoundingClientRect().left);        // 元素左边距离页面左边的距离