【高级程序设计(第四版)】-DOM

443 阅读18分钟

文档对象模型(DOM,Document Object Model)是 HTML 和 XML 文档的编程接口。DOM表示多层节点构成的文档,通过它开发者可以添加、删除和修改页面的各个部分。

节点

Node类型

常量中文
ELEMENT_NODE1元素节点
ATTRIBUTE_NODE2属性节点
TEXT_NODE3文本节点
CDATA_SECTION_NODE4CDATA 区块(xml)
ENTITY_REFERENCE_NODE5实体引用节点(xml)
ENTITY_NODE6实体节点(xml)
PROCESSING_INSTRUCTION_NODE7处理指令节点(xml)
COMMENT_NODE8注释节点
DOCUMENT_NODE9文档节点
DOCUMENT_TYPE_NODE10文档类型节点(doctype)
DOCUMENT_FRAGMENT_NODE11fragment节点
NOTATION_NODE12记号节点(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

NodeListHTMLCollection的区别

  • HTMLCollection是 HTML 元素的集合。(仅包含元素)
  • NodeList 是一个文档节点的集合。
  • HTMLCollection 元素可以通过 name,id 或索引来获取,还有一个namedItem()
  • NodeList 只能通过索引来获取。
  • 只有 NodeList 对象有包含属性节点和文本节点。

NodeListHTMLCollection的共同点:

  • 都是实时的,DOM变化都会反应在两者中
  • NodeList 与 HTMLCollection 有很多类似的地方, 共同的item方法。
  • NodeList 与 HTMLCollection 都与数组对象有点类似,可以使用索引 (0, 1, 2, 3, 4, ...) 来获取元素。
  • NodeList 与 HTMLCollection 都有 length 属性。

DOCUMENT类型

文档子节点

子节点子节点名称
document.documentElement指向html元素
document.doctype标签
document.bodybody节点
document.titletitle元素的文本
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包含元素的额外信息
classNameclass属性,指定class的类
classList HTML扩展class的集合类型
children HTML扩展元素子节点的集合
childNodes子节点的结合
attributes每个属性都表示为一个属性节点(返回一个NamedNodeMap)

classList

  操作类名,通过classList可以使用方法更方便的进行删除、添加和替换类名,classList是一个新的集合类型(DOMTokenList ),但在HTML5之前的className是一个字符串。

  • add(value): 向类名列表中添加指定的字符串值 value。如果这个值已经存在,则什么也不做
  • contains(value): 返回布尔值,表示给定的 value 是否存在
  • remove(value): 类名列表中删除指定的字符串值 value
  • toggle(value): 类名列表中已经存在指定的 value,则删除;如果不存在,则添加

元素节点的方法

子节点子节点名称
element.getAttribute获取元素节点的属性(包括自定义属性)
element.setAttribute标签
element.removeAttributetitle元素的文本
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";

样式

存取元素样式

操作样式表

元素尺寸

  • 偏移尺寸

image.png

  偏移尺寸:包含元素在屏幕上占用的所有视觉空间(可见空间)。元素在页面上的视觉空间由其高度和宽度决定(包括内边距、滚动条、边框(不包含外边距margin)) 取得偏移尺寸的只读属性:

属性名称属性说明
offsetHeight元素在垂直方向上占用的像素尺寸,包括它的高度、水平滚动条高度(如果可见)和上、下边框的高度
offsetWidth元素在水平方向上占用的像素尺寸,包括它的宽度、垂直滚动条宽度(如果可见)和左、右边框的宽度
offsetLeft元素左边框外侧距离包含元素左边框内侧的像素数
offsetRight元素上边框外侧距离包含元素上边框内侧的像素数

offsetLeftoffsetTop是相对包含元素,包含元素保存在offsetParent属性中,offsetParent不一定是parentNode

  • 客户端尺寸

image.png

  客户端尺寸(client dimensions)包含元素内容及其内边距所占用的空间(不包含border)。

属性名称属性说明
clientWidth内容区宽度加左、右内边距宽度
clientHeight内容区高度加上、下内边距高度
  • 滚动尺寸

image.png

属性名称属性说明
scrollHeight没有滚动条出现时,元素内容的总高度
scrollWidth没有滚动条出现时,元素内容的总宽度。
scrollLeft内容区左侧隐藏的像素数,设置这个属性可以改变元素的滚动位置
scrollTop内容区顶部隐藏的像素数,设置这个属性可以改变元素的滚动位置
  • 确定元素尺寸   getBoundingClientRect()方法,返回一个DOMRect对象,包含留个属性: left、top、right、bottom、height和width

image.png

<!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 个阶段:事件捕获到达目标事件冒泡

1638348817(1).png

事件处理程序

为响应事件而调用的函数被称为 事件处理程序 或( **事件监听器**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为事件处理程序的赋值和移除定义了两个方法: addEventListenerremoveEventListener。这两个方法也暴露在所有的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)

  如果给一个节点添加了两个事件处理程序,多个事件处理程序会按照顺序来触发。如果需要移除已赋值的事件处理程序,需要在addEventListenerremoveEventListener使用同一个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 对象。不同的事件会存在不同的属性和方法,但所有事件对象都包含一些公共的方法和属性

名称类型注释
bubblesboolean表示事件是否冒泡
currentTargetElement当前事件处理程序所在的元素
targetElement事件目标
detailobject事件相关的其他信息
typestring被触发的事件类型(不加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事件目标
typestring触发事件类型

跨浏览器事件对象

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等等

名称注释
loadwindow上当前页面加载完成后触发,可在img,frame等<object>元素上挂载 一般挂载在window上
unload在window上当前页面完全卸载后触发,在<object>元素上响应对象卸载完成后触发
resize在window或者iframe、frame被缩放时触发
scroll当用户滚动包含滚动条元素时在元素上触发。<body>元素包含已加载页面的滚动条

键盘与输入事件

名称注释
keydown用户按下键盘上某个键时触发,而且持续按住会重复触发
keypress(DOM3 废弃)用户按下键盘上某个键并产生字符时触发,而且持续按住会重复触发
keyup用户释放键盘上某个键时触发

HTML5事件

名称注释
contextmenu以专门用于表示何时该显示上下文菜单,从而允许开发者取消默认的上下文菜单并提供自定义菜单
beforeunloadbeforeunload 事件会在 window 上触发,用意是给开发者提供阻止页面被卸载的机会
hashchangeonhashchange 事件处理程序必须添加给 window,每次 URL 散列值发生变化时会调用它

EventLoop

宏任务(外部队列MacroTask)

宏任务: JavaScript外部事件的队列,浏览器中主要的外部事件源:

  • DOM操作
  • 用户交互(鼠标、键盘)
  • 网络请求(ajax)
  • History API操作
  • 定时器(setTimout等)
  • js脚本执行

微任务(内部队列MicroTask)

微任务,即JavaScript语言内部的事件队列,一般有以下几种

  • Promise的then与catch
  • MutationObserver

image.png

浏览器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. 首先执行所有外部: 1、当前的JavaScript脚本 - 执行(console.log(1) console.log(5))
  2. 将宏任务放入宏任务队列[setTimout],微任务放到微任务队列中[Promise放入微任务队列]
  3. 检查微任务队列,清空当前微任务队列 promise - 一次执行执行then: console.log(3) console.log(4)
  4. DOM渲染

第二轮:

  1. 执行宏任务 setTimout, callback - console.log(2)
  2. 检查微任务队列,无微任务
  3. 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

1638443311(1).jpg

Node EventLoop顺序

  1. 第一阶段: 执行 定时器回调 的阶段。检查定时器setTimout、setInterval 统称为 timers(减少时间相关系统调用)
  2. 第二阶段:轮询(英文叫poll)阶段, 通过connections, data等来进行通知,达到poll阶段
  3. 第三阶段: check阶段 即执行setImmdiate回调

完善:

  1. idle, prepare 对应的是 libuv 中的两个叫做 idle和 prepare 的句柄。由于 I/O 的poll过程可能阻塞住事件循环,所以这两个句柄主要是⽤来触发 poll (阻塞)之前需要触发的回调

  2. 由于 poll 可能 block 住事件循环,所以应当有⼀个外部队列专门用于执行 I/O 的 callback ,并且优先级在 poll 以及 prepare to poll 之前。另外我们知道网络 IO 可能有⾮常多的请求同时进来,如果该阶段如果⽆限制的执⾏这些callback,可能导致 Node.js 的进程卡死该阶段,其他外部队列的代码都没法执行了。所以当前外部队列在执行到一定数量的callback之后会截断。由于截断的这个特性,这个专门执行I/O callbacks的外部队列也叫 pending callback image.png

所有顺序结合:

  1. timer 阶段
  2. I/O 异常回调阶段
  3. 空闲、预备状态(第2阶段结束,poll 未触发之前)
  4. poll 阶段
  5. check 阶段
  6. 关闭事件的回调阶段

Node版本的区别

在node版本11之前的EventLoop与 >= 11的版本有些区别。

1638443893(1).jpg 这是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');
  });
});

浏览器的执行顺序:

  1. 宏任务: 执行JavaScript脚本,将第一个setTimout和第二个setTimout放入宏任务队列
  2. 无微任务,渲染DOM
  3. 检查宏任务队列, 拿出第一个setTimout宏任务 执⾏,将promise 加⼊微任务队列
  4. 检查微任务队列:执⾏第⼀个 promise
  5. 检查宏任务队列, 拿出第二个setTimout宏任务 执⾏,将promise 加⼊微任务队列
  6. 检查微任务队列:执⾏第二个 promise

<11 版本Node执行顺序:

  1. JavaScript 脚本执行,将两个 setTimeout加入宏任务队列
  2. 无微任务
  3. 检查宏任务队列:批量执行宏任务队列 执行俩个setTimout,分别将其内部的promise加入微任务队列
  4. 检查微任务队列:执行两个promise任务