文档对象模型(DOM,Document Object Model)是 HTML 和 XML 文档的编程接口。DOM表示多层节点构成的文档,通过它开发者可以添加、删除和修改页面的各个部分。
节点
Node类型
| 常量 | 值 | 中文 |
|---|---|---|
ELEMENT_NODE | 1 | 元素节点 |
ATTRIBUTE_NODE | 2 | 属性节点 |
TEXT_NODE | 3 | 文本节点 |
| CDATA_SECTION_NODE | 4 | CDATA 区块(xml) |
| ENTITY_REFERENCE_NODE | 5 | 实体引用节点(xml) |
| ENTITY_NODE | 6 | 实体节点(xml) |
| PROCESSING_INSTRUCTION_NODE | 7 | 处理指令节点(xml) |
COMMENT_NODE | 8 | 注释节点 |
DOCUMENT_NODE | 9 | 文档节点 |
DOCUMENT_TYPE_NODE | 10 | 文档类型节点(doctype) |
DOCUMENT_FRAGMENT_NODE | 11 | fragment节点 |
| NOTATION_NODE | 12 | 记号节点(xml) |
在JavaScript中,所有的节点类型都继承Node类型,因此 所有类型 共享想同的基本属性和方法。浏览器并不支持所有节点类型。开发者最常用到的是元素节点和文本节点
节点属性
| 属性名 | 节点信息 | 注意点 |
|---|---|---|
| nodeType | 节点类型 | 节点类型为 上图中的类型,输出的节点值 |
| nodeName | 节点名称 | 取决于节点类型 element(标签名)/document("#document")/text("#text") |
| nodeValue | 节点值 | text节点类型为文本内容 |
| childNodes | 节点值 | 所有的子节点 |
| firstChild | 节点值 | 第一个子节点 |
| firstElementChild | 节点值 | 第一个元素子节点 |
| lastChild | 节点值 | 最后一个子节点 |
| lastElementChild | 节点值 | 最后一个元素子节点 |
| nextSibling | 节点值 | 下一个兄弟节点 |
| nextElementSibling | 节点值 | 下一个兄弟元素节点 |
| previousSibling | 节点值 | 上一个兄弟节点 |
| previousElementSibling | 节点值 | 上一个兄弟元素节点 |
| childElementCount | 节点值 | 所有的元素子节点数目 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node类型</title>
<style>
#node {
background-color: rgba(255,255,255,.8);
}
</style>
</head>
<body>
<div id="node" style="fontSize: 18px;">
<div id="first">firstElementChild</div>
<ul class="ul">
<li class="li li1">第一个li</li>
<li class="li li2">第二个li</li>
<li class="li li3">第三个li</li>
<li class="li li4">第四个li</li>
</ul>
Node类型的nodeName/nodeValue/nodeType
</div>
<script>
const node = document.getElementById('node');
const ulNode = document.getElementsByClassName('ul')[0];
// 所有的元素子节点
console.log('childrenCount', ulNode.childElementCount);
// 所有的li节点
console.log('childNodes', ulNode.childNodes);
// #text 空白文本
console.log('ulNode', ulNode.firstChild);
// <li class="li li1">第一个li</li>
console.log('ulNode', ulNode.firstElementChild);
const firstElementChild = ulNode.firstElementChild;
// #text li1与li2中间的空白文本
console.log('li1-nextSibling', firstElementChild.nextSibling);
// <li class="li li2">第二个li</li>
console.log('li1-nextElementSibling', firstElementChild.nextElementSibling);
// 1 'DIV' null NodeList(5) [text, div#first, text, ul.ul, text] true
console.log(node.nodeType, node.nodeName, node.nodeValue, node.childNodes, node.hasChildNodes())
</script>
</body>
</html>
节点方法
主要节点属性和方法
| 方法名 | 返回值 | 作用 |
|---|---|---|
| appendChild(childNode) | 当前插入的节点 | 将指定的 childNode 参数添加到childNodes列表的末尾 |
| insertBefore(newNode, referenceNode) | 当前插入的节点 | 在当前节点下增加一个子节点 Node,并使该子节点位于参考节点的前面 |
| replaceChild(newChild, oldChild) | 被替换的节点 | 对选定的节点,替换一个子节点 oldChild 为新节点newChild |
| removeChild(removeChild) | 被删除的节点 | 移除当前节点的一个子节点(这个子节点必须存在于当前节点中) |
| cloneNode([deep]) | 当前被克隆的节点 | 克隆一个 Node,并且可以选择是否克隆这个节点下的所有内容(不会克隆事件) |
| hasChildNodes() | Boolean 布尔值 | 表示该元素是否包含有子节点 |
| normalize() | 文本节点的方法,将同胞文本节点合并为一个 | |
contains(node) HTML5扩展 | Boolean 布尔值 | 来表示传入的节点是否为该节点的后代节点 |
isEqualNode(node) HTML5扩展 | Boolean 布尔值 | 当两个 node 节点为相同类型的节点且定义的数据点匹配时(即属性和属性值相同,节点值相同 |
isSameNode(node) HTML5扩展 | Boolean 布尔值 | 两个节点的引用比较结果 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node类型</title>
<style>
#node {
background-color: rgba(255, 255, 255, .8);
}
</style>
</head>
<body>
<div id="node" style="fontSize: 18px;">
<div id="first">firstElementChild</div>
<ul class="ul" id="ul">
<li class="li li1">第一个li</li>
<li class="li li2">第二个li</li>
<li class="li li3">第三个li</li>
<li class="li li4">第四个li</li>
<li>我是新插入的li节点</li>
</ul>
Node类型的nodeName/nodeValue/nodeType
</div>
<script>
console.log('document', document.readyState);
let startTime = Date.parse(new Date());
console.log('start Time', Date.parse(new Date()));
setTimeout(() => {
if (document.readyState === 'complete') {
const endTime = Date.parse(new Date());
console.log('end Time', Date.parse(new Date()));
console.log('渲染完毕了!', endTime - startTime);
}
}, 0);
let node = document.getElementById('node');
const ulNode = document.getElementById('ul');
let lastElementChild = ulNode.lastElementChild;
let newNode = document.createElement('li');
newNode.innerText = '我是新插入的li节点';
// 添加ul childNodes的最末尾
// ulNode.appendChild(fragment);
// 插入在#text节点之前
// ulNode.insertBefore(newNode, ulNode.firstChild);
// 插入在第一个li之前,且中间无空白文本
// ulNode.insertBefore(newNode, ulNode.firstElementChild);
// ulNode.replaceChild(newNode, ulNode.firstElementChild);
// ulNode.removeChild(ul.firstChild); // 删除的是#text 空白文本节点
// ulNode.removeChild(ul.firstElementChild); // 第一个li节点
// true false
console.log(ulNode.contains(ulNode.firstElementChild), ulNode.contains(newNode));
console.log(lastElementChild.isEqualNode(newNode)); // true
console.log(lastElementChild.isSameNode(newNode)); // false
console.log(ulNode.cloneNode(true));
console.log('ulNodes', ulNode.childNodes);
// let fragment = document.createDocumentFragment();
// for (let i = 0; i < 100000; i++) {
// let newNode = document.createElement('li');
// newNode.innerText = '我是新插入的li节点';
// ulNode.appendChild(newNode)
// // fragment.appendChild(newNode);
// }
// ulNode.appendChild(fragment);
</script>
</body>
</html>
Nodelist
NodeList与HTMLCollection的区别:
- HTMLCollection是 HTML 元素的集合。(仅包含元素)
- NodeList 是一个文档节点的集合。
- HTMLCollection 元素可以通过 name,id 或索引来获取,还有一个
namedItem()。 - NodeList 只能通过索引来获取。
- 只有 NodeList 对象有包含属性节点和文本节点。
NodeList与HTMLCollection的共同点:
- 都是实时的,DOM变化都会反应在两者中
- NodeList 与 HTMLCollection 有很多类似的地方, 共同的item方法。
- NodeList 与 HTMLCollection 都与数组对象有点类似,可以使用索引 (0, 1, 2, 3, 4, ...) 来获取元素。
- NodeList 与 HTMLCollection 都有 length 属性。
DOCUMENT类型
文档子节点
| 子节点 | 子节点名称 |
|---|---|
| document.documentElement | 指向html元素 |
| document.doctype | 标签 |
| document.body | body节点 |
| document.title | title元素的文本 |
| document.URL | 完整的URL |
| document.domain | 页面的域名 |
| document.referrer | 当前页面的来源(上一个页面) |
document.head HTML5 扩展 | 获取文档的head节点 |
document.readyState HTML5 扩展 | 判断浏览器是否加载完毕('loading/complete') |
document.compatMode HTML5 扩展 | 检查页面渲染模式 |
document获取元素的方法
| 方法名 | 作用 |
|---|---|
| document.getElementId | 根据id获取元素节点 |
| document.getElementsByName | 根据name获取元素节点集合NodeList |
document.getElementsByClassName HTML-css扩展 | 根据class获取元素节点集合NodeList |
| document.getElementsByTagName | 根据标签名获取元素节点集合HTMLCollection |
| document.createElement(TagName) | 创建元素节点(不会重绘) |
| document.createText(textContent) | 创建文本节点(不会重绘) |
ELEMENT类型
元素节点的属性
| 子节点 | 子节点名称 |
|---|---|
| id | 元素节点在文档中的唯一标识符 |
| title | 包含元素的额外信息 |
| className | class属性,指定class的类 |
classList HTML扩展 | class的集合类型 |
children HTML扩展 | 元素子节点的集合 |
| childNodes | 子节点的结合 |
| attributes | 每个属性都表示为一个属性节点(返回一个NamedNodeMap) |
classList
操作类名,通过classList可以使用方法更方便的进行删除、添加和替换类名,classList是一个新的集合类型(DOMTokenList ),但在HTML5之前的className是一个字符串。
add(value):向类名列表中添加指定的字符串值 value。如果这个值已经存在,则什么也不做contains(value):返回布尔值,表示给定的 value 是否存在remove(value):类名列表中删除指定的字符串值 valuetoggle(value):类名列表中已经存在指定的 value,则删除;如果不存在,则添加
元素节点的方法
| 子节点 | 子节点名称 |
|---|---|
| element.getAttribute | 获取元素节点的属性(包括自定义属性) |
| element.setAttribute | 标签 |
| element.removeAttribute | title元素的文本 |
| element.getElementByTagName | 完整的URL |
element.getElementByClassName HTML-css扩展 | 页面的域名 |
| element.contains(node) | 判断是否包含某节点 |
其他DOM HTML5扩展
Selectors API
querySelector()
querySelector()方法接收 CSS 选择符参数,返回匹配该模式的第一个后代元素,如果没有匹配项则返回 null
在 Document 上使用 querySelector()方法时,会从文档元素开始搜索;在 Element 上使用querySelector()方法时,则只会从当前元素的后代中查询。
// 取得<body>元素
let body = document.querySelector("body");
// 取得 ID 为"myDiv"的元素
let myDiv = document.querySelector("#myDiv");
// 取得类名为"selected"的第一个元素
let selected = document.querySelector(".selected");
// 取得类名为"button"的图片
let img = document.body.querySelector("img.button");
querySelectorAll()
querySelector()方法接收 CSS 选择符参数,返回所有匹配的节点。返回的一个NodeList的静态实例, 并非"实时查询"
// 取得 ID 为"myDiv"的<div>元素中的所有<em>元素
let ems = document.getElementById("myDiv").querySelectorAll("em");
// 取得所有类名中包含"selected"的元素
let selecteds = document.querySelectorAll(".selected");
// 取得所有是<p>元素子元素的<strong>元素
let strongs = document.querySelectorAll("p strong");
自定义属性
HTML5允许给元素指定非标注的属性,但是呀使用前缀data-以便告诉浏览器,但是这些属性不能包含与渲染有关的信息,也不不能包含元素的语义信息。自定义的data-属性,可以通过元素的dataset属性来访问。
<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>
et div = document.getElementById("myDiv");
// 取得自定义数据属性的值
let appId = div.dataset.appId;
let myName = div.dataset.myname;
// 设置自定义数据属性的值
div.dataset.appId = 23456;
div.dataset.myname = "Michael";
样式
存取元素样式
操作样式表
元素尺寸
- 偏移尺寸
偏移尺寸:包含元素在屏幕上占用的所有视觉空间(可见空间)。元素在页面上的视觉空间由其高度和宽度决定(包括内边距、滚动条、边框(不包含外边距margin))
取得偏移尺寸的只读属性:
| 属性名称 | 属性说明 |
|---|---|
offsetHeight | 元素在垂直方向上占用的像素尺寸,包括它的高度、水平滚动条高度(如果可见)和上、下边框的高度 |
offsetWidth | 元素在水平方向上占用的像素尺寸,包括它的宽度、垂直滚动条宽度(如果可见)和左、右边框的宽度 |
offsetLeft | 元素左边框外侧距离包含元素左边框内侧的像素数 |
offsetRight | 元素上边框外侧距离包含元素上边框内侧的像素数 |
offsetLeft和offsetTop是相对包含元素,包含元素保存在offsetParent属性中,offsetParent不一定是parentNode
- 客户端尺寸
客户端尺寸(client dimensions)包含元素内容及其内边距所占用的空间(不包含border)。
| 属性名称 | 属性说明 |
|---|---|
clientWidth | 内容区宽度加左、右内边距宽度 |
clientHeight | 内容区高度加上、下内边距高度 |
- 滚动尺寸
| 属性名称 | 属性说明 |
|---|---|
scrollHeight | 没有滚动条出现时,元素内容的总高度 |
scrollWidth | 没有滚动条出现时,元素内容的总宽度。 |
scrollLeft | 内容区左侧隐藏的像素数,设置这个属性可以改变元素的滚动位置 |
scrollTop | 内容区顶部隐藏的像素数,设置这个属性可以改变元素的滚动位置 |
- 确定元素尺寸
getBoundingClientRect()方法,返回一个DOMRect对象,包含留个属性: left、top、right、bottom、height和width
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>元素尺寸</title>
<style>
* {
margin: 0px;
padding: 0px;
}
body {
padding: 50px;
}
.parent {
margin: 50px;
padding: 30px;
}
.rect {
width: 500px;
height: 400px;
padding: 20px;
margin: 20px;
border: 10px solid gold;
overflow: auto;
}
</style>
</head>
<body>
<div class="parent">
<div class="rect">
元素尺寸 width: 500 height: 400
</div>
</div>
<script>
let rectNode = document.querySelector('.rect');
console.log('----------浏览器不缩小 start----------');
// 确定元素尺寸 -width/padding/border
console.log('确定元素尺寸', rectNode.getBoundingClientRect());
// 偏移尺寸-width、height包含width/padding/border
console.log('offsetWidth', rectNode.offsetWidth); // 500 + 20*2 + 10* 2 = 560
console.log('offsetHeight', rectNode.offsetHeight); // 400 + 20*2 + 10*2 = 460
console.log('offsetTop', rectNode.offsetTop); // body(50)+parent(50+ 30) +margin(20) = 150
console.log('offsetLeft', rectNode.offsetLeft); // body(50)+parent(50+ 30) +margin(20) = 150
// 客户端尺寸 - width+padding
console.log('clientWidth', rectNode.clientWidth); // width 500 + padding 40 - 滚动条(0) = 540
console.log('clientHeight', rectNode.clientHeight); // height 400 + padding 40 - 滚动条(0) = 440
console.log('----------浏览器不缩小 end----------');
// 滚动尺寸
setTimeout(() => {
console.log('scrollTop', rectNode.scrollTop);
console.log('scrollLeft', rectNode.scrollLeft);
}, 1000);
console.log('scrollWidth', rectNode.scrollWidth);
console.log('scrollHeight', rectNode.scrollHeight);
</script>
</body>
</html>
焦点管理(activeElement)
首先是 document.activeElement,始终包含当前拥有焦点的 DOM 元素。始终包含当前拥有焦点的 DOM 元素。页面加载时,可以通过用户输入(按 Tab 键或代码中使用 focus()方法)让某个元素自动获得焦点。
scrollIntoView
scrollIntoView()方法存在于所有 HTML 元素上,可以滚动浏览器窗口或容器元素以便包含元素进入视口。
参数:
- alignToTop
- true:窗口滚动后元素的顶部与视口顶部对齐。
- false:窗口滚动后元素的底部与视口底部对齐
- scrollIntoViewOptions(可选)
- behavior:定义过渡动画,可取的值为"smooth"和"auto",默认为"auto"。
- block:定义垂直方向的对齐,可取的值为"start"、"center"、"end"和"nearest",默认为 "start"
- inline:定义水平方向的对齐,可取的值为"start"、"center"、"end"和"nearest",默认为 "nearest"。
事件
DOM事件流
DOM2 Events 规范规定事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。
事件处理程序
为响应事件而调用的函数被称为
事件处理程序或( **事件监听器**c)。事件处理程序的名字以"on"开头
HTML事件处理程序(HTML属性)
特定元素支持的每个事件都可以使用事件处理程序的名字以HTML属性的形式来指定。
function showMessage(event) {
console.log(this.type); // 'button'
console.log("Hello world!", event);
}
</script>
<input type="button" value="Click Me" onclick="showMessage()"/>
事件处理函数都有一个event对象,执行函数中的 this相当于事件的目标元素(DOM元素)
DOM0事件处理程序(js)
在JavaScript中指定事件处理程序的传统方式: 把一个函数赋值给(DOM元素)一个事件处理程序属性。 事件处理函数中的this 为目标元素
let btn = document.getElementById("myBtn");
btn.onclick = function() {
console.log("Clicked");
};
// 移除事件
btn.onclick = null;
先从文档中取得按钮,然后给它的 onclick 事件处理程序赋值一个函数。注意,前面的代码在运行之后才会给事件处理程序赋值。因此如果在页面中上面的代码出现在按钮之后,则有可能出现用户点击按钮没有反应的情况
这样使用 DOM0 方式为事件处理程序赋值时,所赋函数被视为元素的方法。以这种方式添加事件处理程序是注册在事件流的冒泡阶段的。
DOM2事件处理程序(addEventListener)
DOM2 Events为事件处理程序的赋值和移除定义了两个方法: addEventListener和removeEventListener。这两个方法也暴露在所有的DOM节点上。事件处理程序中的this为目标元素
参数:
- eventName: 字符串,指定事件名.
不需要加on - eventFn: 指定要事件触发时执行的函数
- useCapture: 指定事件是否在捕获或冒泡阶段执行, true表示捕获阶段,false表示冒泡阶段
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
console.log(this.id);
}, false);
let handler = function () {
console.log('hello world!');
}
btn.addEventListener("click", handler, false);
// 移除事件监听
// 无法移除,两个function不是为同一个
btn.removeEventListener('click', () => {
console.log(this.id);
}, false);
// 可以删除,addEventListener赋值与removeEventListener移除的为同一个
btn.removeEventListener('click', handler, false)
如果给一个节点添加了两个事件处理程序,多个事件处理程序会按照顺序来触发。如果需要移除已赋值的事件处理程序,需要在addEventListener与removeEventListener使用同一个function(function为引用类型,必须定义为同一个 不能为两个长相一致的函数)
IE事件处理程序(attachEvent-无意义)
IE实现了与DOM类似的方法: attachEvent()与detachEvent()。事件处理程序中的this为window。
参数:
- eventName: 字符串,指定事件名.
需要加on - function: 指定要事件触发时执行的函数
let btn = document.getElementById("myBtn");
btn.attachEvent("onclick", () => {
console.log(this.id);
});
btn.attachEvent("onclick", () => {
console.log("Hello world!");
});
// 移除事件监听
// 无法移除,两个function不是为同一个
btn.detachEvent('onclick', () => {
console.log(this.id);
});
// 可以删除,attachEvent赋值与detachEvent移除的为同一个
btn.detachEvent('onclick', handler)
IE事件处理程序的处理与DOM2事件处理程序的处理一致。多个事件按照顺序执行,赋值与删除的函数必须为同一个。
跨浏览器事件处理程序(兼容)
根据需要分别使用 DOM0 方式、DOM2 方式或 IE 方式来添加事件处理程序。
const EventUtil = {
addHandler: function(element, type, handler) {
if (window.addEventListener) {
// DOM2事件处理程序
element.addEventListener(type, handler, false);
} else if (window.attachEvent) {
// IE事件处理程序
element.attachEvent(`on${type}`, handler);
} else {
// DOM0事件处理程序
element[`on${type}`] = handler;
}
},
removeHandler: function(element, type, handler) {
if (window.removeEventListener) {
// DOM2事件处理程序
element.removeEventListener(type, handler, false);
} else if (window.detachEvent) {
// IE事件处理程序
element.detachEvent(`on${type}`, handler);
} else {
// DOM0事件处理程序
element[`on${type}`] = null;
}
}
}
事件对象
在 DOM 中发生事件时,所有相关信息都会被收集并存储在一个名为 event 的对象中。这个对象包含了一些基本信息,比如导致事件的元素、发生的事件类型,以及可能与特定事件相关的任何其他数据
event 对象只在事件处理程序执行期间存在,一旦执行完毕,就会被销毁。
DOM事件对象
在 DOM 合规的浏览器中,event 对象是传给事件处理程序的唯一参数。不管以哪种方式(DOM0或 DOM2)指定事件处理程序,都会传入这个 event 对象。不同的事件会存在不同的属性和方法,但所有事件对象都包含一些公共的方法和属性
| 名称 | 类型 | 注释 |
|---|---|---|
| bubbles | boolean | 表示事件是否冒泡 |
| currentTarget | Element | 当前事件处理程序所在的元素 |
| target | Element | 事件目标 |
| detail | object | 事件相关的其他信息 |
| type | string | 被触发的事件类型(不加on) |
| preventDefault | 函数 | 取消事件的默认行为 |
| stopPropagation | 函数 | 用于取消后续事件捕获或事件冒泡 |
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log(event.currentTarget === this); // true
console.log(event.target === this); // true
};
在事件处理程序内部,this 对象始终等于 currentTarget 的值,而 target 只包含事件的实际目标。如果事件处理程序直接添加在了意图的目标,则 this、currentTarget 和 target 的值是一样的
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log("Clicked");
event.stopPropagation();
};
// 当前的冒泡事件不会被执行,目标事件响应后阻止了后续的事件流(DOM0事件处理程序是在冒泡阶段触发)
document.body.onclick = function(event) {
console.log("Body clicked");
};
IE事件对象
DOM 事件对象不同, IE 事件对象可以基于事件处理程序被指定的方式以不同方式来访问。
- DOM0 方式指定事件处理程序,则
event 对象只是 window 对象的一个属性 - HTML 属性方式指定的事件处理程序,则
event 对象同样可以通过变量 event 访问 - 事件处理程序是使用 attachEvent()指定的,则
event对象会作为唯一的参数传给处理函数
// DOM0处理方式 event只是window的一个属性
var btn = document.getElementById("myBtn");
btn.onclick = function() {
let event = window.event;
console.log(event.type); // "click"
};
// HTML处理方式: event对象作为唯一的参数传给处理函数
<input type="button" value="Click Me" onclick="console.log(event.type)">
// attachEvent处理方式: event对象会作为唯一的参数传给处理函数
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(event) {
console.log(event.type); // "click"
});
在IE中,虽然不同的处理方式会存在不同的event对象。但与 DOM 事件对象一样,基于触发的事件类型不同,event 对象中包含的属性和方法也不一样。不过,所有 IE 事件对象都会包含下表所列的公共属性和方法
| 名称 | 类型 | 注释 |
|---|---|---|
cancelBubble(stopPropagation) | boolean | 默认为false,设置为true会取消冒泡 |
returnValue(preventDeafult) | boolean | 默认true, 设置为false会取消默认事件 |
srcElement(target) | element | 事件目标 |
| type | string | 触发事件类型 |
跨浏览器事件对象
const EventUtil = {
addHandler: () => {},
removeHandler: () => {},
getEvent: (event) {
return event || window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function(event) {
if(event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropation();
} else {
event.cancelBubble();
}
}
}
事件类型
用户界面事件
<object>标签用于在网页里嵌入对象,比如图像、音频、视频、Java小程序、ActiveX、PDF、Flash等等
| 名称 | 注释 |
|---|---|
| load | window上当前页面加载完成后触发,可在img,frame等<object>元素上挂载 一般挂载在window上 |
| unload | 在window上当前页面完全卸载后触发,在<object>元素上响应对象卸载完成后触发 |
| resize | 在window或者iframe、frame被缩放时触发 |
| scroll | 当用户滚动包含滚动条元素时在元素上触发。<body>元素包含已加载页面的滚动条 |
键盘与输入事件
| 名称 | 注释 |
|---|---|
| keydown | 用户按下键盘上某个键时触发,而且持续按住会重复触发 |
keypress(DOM3 废弃) | 用户按下键盘上某个键并产生字符时触发,而且持续按住会重复触发 |
| keyup | 用户释放键盘上某个键时触发 |
HTML5事件
| 名称 | 注释 |
|---|---|
contextmenu | 以专门用于表示何时该显示上下文菜单,从而允许开发者取消默认的上下文菜单并提供自定义菜单 |
beforeunload | beforeunload 事件会在 window 上触发,用意是给开发者提供阻止页面被卸载的机会 |
hashchange | onhashchange 事件处理程序必须添加给 window,每次 URL 散列值发生变化时会调用它 |
EventLoop
宏任务(外部队列MacroTask)
宏任务: JavaScript外部事件的队列,浏览器中主要的外部事件源:
- DOM操作
- 用户交互(鼠标、键盘)
- 网络请求(ajax)
- History API操作
- 定时器(setTimout等)
- js脚本执行
微任务(内部队列MicroTask)
微任务,即JavaScript语言内部的事件队列,一般有以下几种
- Promise的then与catch
- MutationObserver
浏览器EventLoop
console.log('script start 1');
setTimeout(function() {
console.log('setTimout 2');
}, 0);
Promise.resolve().then(function() {
console.log('promise1 3');
}).then(function() {
console.log('promise2 4');
});
console.log('script end 5');
EventLoop分析:
- 首先执行所有外部: 1、当前的JavaScript脚本 - 执行(console.log(1) console.log(5))
- 将宏任务放入宏任务队列[setTimout],微任务放到微任务队列中[Promise放入微任务队列]
- 检查微任务队列,清空当前微任务队列 promise - 一次执行执行then: console.log(3) console.log(4)
- DOM渲染
第二轮:
- 执行宏任务 setTimout, callback - console.log(2)
- 检查微任务队列,无微任务
- DOM渲染
全部执行结果
'script start 1'
'script start 5'
'promise1 3'
'promise2 4'
'setTimout 2'
问答题:
console.log('1 script start');
setTimeout(function() {
console.log('2 setTimeout');
Promise.resolve().then(function(){
console.log('3 promise1');
});
}, 0);
Promise.resolve().then(function(){
console.log('4 promise1');
}).then(function() {
console.log('5 promise2');
});
console.log('6 script end');
/**
答案
1 script start
6 script end
4 promise1
5 promise2
2 setTimeout
3 promise1
**/
Node
Node EventLoop顺序
- 第一阶段: 执行
定时器回调的阶段。检查定时器setTimout、setInterval 统称为timers(减少时间相关系统调用) - 第二阶段:轮询(英文叫
poll)阶段, 通过connections,data等来进行通知,达到poll阶段 - 第三阶段: check阶段 即执行
setImmdiate回调
完善:
-
idle,prepare对应的是 libuv 中的两个叫做 idle和 prepare 的句柄。由于 I/O 的poll过程可能阻塞住事件循环,所以这两个句柄主要是⽤来触发 poll (阻塞)之前需要触发的回调 -
由于 poll 可能 block 住事件循环,所以应当有⼀个外部队列专门用于执行 I/O 的 callback ,并且优先级在
poll 以及 prepare to poll之前。另外我们知道网络 IO 可能有⾮常多的请求同时进来,如果该阶段如果⽆限制的执⾏这些callback,可能导致 Node.js 的进程卡死该阶段,其他外部队列的代码都没法执行了。所以当前外部队列在执行到一定数量的callback之后会截断。由于截断的这个特性,这个专门执行I/O callbacks的外部队列也叫pending callback
所有顺序结合:
- timer 阶段
- I/O 异常回调阶段
- 空闲、预备状态(第2阶段结束,poll 未触发之前)
- poll 阶段
- check 阶段
- 关闭事件的回调阶段
Node版本的区别
在node版本11之前的EventLoop与 >= 11的版本有些区别。
这是11版本之前的事件循环, 宏任务队列是批量执行完毕之后再执行微任务队列。Node版本>=11 之后的事件处理与浏览器一致
setTimeout(() => {
console.log('timer1');
Promise.resolve().then(function () {
console.log('promise1');
});
});
setTimeout(() => {
console.log('timer2');
Promise.resolve().then(function () {
console.log('promise2');
});
});
浏览器的执行顺序:
- 宏任务: 执行JavaScript脚本,将第一个setTimout和第二个setTimout放入宏任务队列
- 无微任务,渲染DOM
- 检查宏任务队列, 拿出第一个setTimout宏任务 执⾏,将promise 加⼊微任务队列
- 检查微任务队列:执⾏第⼀个 promise
- 检查宏任务队列, 拿出第二个setTimout宏任务 执⾏,将promise 加⼊微任务队列
- 检查微任务队列:执⾏第二个 promise
<11 版本Node执行顺序:
- JavaScript 脚本执行,将两个 setTimeout加入宏任务队列
- 无微任务
- 检查宏任务队列:批量执行宏任务队列 执行俩个setTimout,分别将其内部的promise加入微任务队列
- 检查微任务队列:执行两个promise任务