# 认识 DOM 和架构

  • 浏览器是用来展示网页的,而网页中最重要的就是里面各种的标签元素,JavaScript 很多时候是需要操作这些元素的。
    • JavaScript 如何操作元素呢?通过 Document Object Model(DOM,文档对象模型)。
    • DOM 给我们提供了一系列的模型和对象,让我们可以方便的来操作 Web 页面。

DOM

# EventTarget

  • 因为继承自 EventTarget,所以也可以使用 EventTarget 的方法:
document.addEventListener("click", () => console.log("document被点击"))
const divEl = document.querySelector("div")
const spanEl = document.querySelector("span")
divEl.addEventListener("click", () => console.log("div元素被点击"))
spanEl.addEventListener("click", () => console.log("span元素被点击"))

# Node 节点

  • 所有的 DOM 节点类型都继承自 Node 接口。
    • https://developer.mozilla.org/zh-CN/docs/Web/API/Node
  • Node 有几个非常重要的属性:
    • nodeName:node 节点的名称。
    • nodeType:可以区分节点的类型。
    • nodeValue:node 节点的值;
    • childNodes:所有的子节点;
console.log(divEl.nodeName, spanEl.nodeName ) // DIV SPAN
 
console.log(divEl.nodeType, spanEl.nodeType) // 1 1
  • 注意 document 虽然是继承 Node , 是有 appendChild 方法的,但浏览器进行了限制,不能进行添加
const span = document.createElement('span')
document.appendChild(span)
  • 会报下面这种错误 Uncaught DOMException: Failed to execute 'appendChild' on 'Node': Only one element on document allowed.
  • 但从打印可以看出是有这个方法的
console.log(document.appendChild) // ƒ appendChild() { [native code] }
  • 所以我们一般通过 body 进行添加
const span = document.createElement('span')
document.body.appendChild(span)

# Document 常见的属性

  • document.body

  • document.head

  • document.title

console.log(document.body) // <body>…</body>
console.log(document.head) //  <head>…</head>
document.title = "Hello World"
  • window.location
  • window.locationdocument.location 他们是指向同一个对象
console.log(window.location) // Location {ancestorOrigins: DOMStringList, href: ..., origin: 'http://127.0.0.1:5500', protocol: 'http:', host: '127.0.0.1:5500', …}
console.log(document.location) // Location {ancestorOrigins: DOMStringList, href: ..., origin: 'http://127.0.0.1:5500', protocol: 'http:', host: '127.0.0.1:5500', …}
console.log(window.location === document.location) // true

# Document 常见的方法

# createElement

  • 语法: const element = document.createElement(tagName[, options])
const image = document.createElement("img")
console.log(image) // <img>

# getElementById

  • ** element ** 是一个 Element 对象。如果当前文档中拥有特定 ID 的元素不存在则返回 null.
  • ** id ** 是大小写敏感的字符串,代表了所要查找的元素的唯一 ID.
const element = document.getElementById(id)
  • div 元素有个 idbox
<div id='box'></div>
  • 如果 id为box则返回该元素对象 ,否则为 null
const divEl1 = document.getElementById("box")
console.log(divEl1) // <div id='box'></div>
const divEl1 = document.getElementById("abc")
console.log(divEl1) // null

# getElementsByTagName

  • elements 是一个由发现的元素出现在树中的顺序构成的动态的 HTML 集合 HTMLCollection (但是看下面的提示) 。
  • name 是一个代表元素的名称的字符串。特殊字符 "*" 代表了所有元素。
const elements = document.getElementsByTagName(name);
  • 有两个 div 元素
<div></div>
<div></div>
  • 会给你一个数组,通过下标拿到元素
const divEl2 = document.getElementsByTagName("div")
console.log(divEl2) // HTMLCollection(2) [div, div]
console.log(divEl2[0]) // <div></div>
console.log(divEl2[1]) // <div></div>

# getElementsByName

elements = document.getElementsByName(name)
  • elements 是一个实时更新的 NodeList 集合。当文档中有同一个 name 属性的元素被添加或移除时,这个集合会自动更新

  • name 是元素的 name 属性的值

  • 有两个元素, name 都是 title

<div name="title"></div>
<h2 name="title"></h2>
  • 也是会给你一个数组,通过下标拿到元素
const divEl3 = document.getElementsByName("title")
console.log(divEl3) // NodeList(2) [div, h2]
console.log(divEl3[0]) // <div name="title"></div>
console.log(divEl3[1]) // <h2 name="title"></h2>

# querySelector

element = document.querySelector(selectors)
  • selectors 包含一个或多个要匹配的选择器的 DOM 字符串 DOMString 。 该字符串必须是有效的 CSS 选择器字符串;如果不是,则引发 SYNTAX_ERR 异常。请参阅使用选择器定位 DOM 元素以获取有关选择器以及如何管理它们的更多信息。
  • 比如有三个元素
<h2 id="content"></h2>
<div class="content"></div>
<span name="content"></span>
  • 通过选择器直接拿到元素
const divEl4 = document.querySelector("#content")
const divEl5 = document.querySelector(".content")
const divEl6 = document.querySelector("[name=content]")
console.log(divEl4) // <h2 id="content"></h2>
console.log(divEl5) // <div class="content"></div>
console.log(divEl6) // <span name="content"></span>

# querySelectorAll

  • 返回与指定的选择器组匹配的文档中的元素列表 (使用深度优先的先序遍历文档的节点)。返回的对象是 NodeList
  • selectors 一个 DOMString 包含一个或多个匹配的选择器。这个字符串必须是一个合法的 CSS selector 如果不是,会抛出一个 SyntaxError 错误。有关使用选择器标识元素的更多信息,请参阅 Locating DOM elements using selectors 可以通过使用逗号分隔多个选择器来指定它们。
elementList = parentNode.querySelectorAll(selectors)
  • 比如有三个类都是 contentdiv
<div class="content"></div>
<div class="content"></div>
<div class="content"></div>
  • 匹配到返回一个数组,如下面这样然后可以通过下标获取元素,没找到返回 undefined
const divEl7 = document.querySelectorAll(".content")
console.log(divEl7) // NodeList(3) [div.content, div.content, div.content]
console.log(divEl7[0]) // <div class="content"></div>
console.log(divEl7[1]) // <div class="content"></div>
console.log(divEl7[2]) // <div class="content"></div>
console.log(divEl7[3]) // undefined

# Element 常见的属性

  • 需要了解下面属性的更多详情可以去 MDN 查看,链接我都放在后面啦~

  • id https://developer.mozilla.org/zh-CN/docs/Web/API/Element/id

  • tagName https://developer.mozilla.org/zh-CN/docs/Web/API/Element/tagName

  • children https://developer.mozilla.org/zh-CN/docs/Web/API/Element/children

  • className https://developer.mozilla.org/zh-CN/docs/Web/API/Element/className

  • classList https://developer.mozilla.org/zh-CN/docs/Web/API/Element/classList

  • clientWidth https://developer.mozilla.org/zh-CN/docs/Web/API/Element/clientWidth

  • clientHeight https://developer.mozilla.org/zh-CN/docs/Web/API/Element/clientHeight

  • offsetLeft https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetLeft

  • offsetTop https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetTop

  • 然后通过下面案例简单测试下

<style>
 * {
  margin: 0;
  padding: 0;
 }
  #box {
    width: 200px;
    height: 200px;
    background-color: red;
  }
  .content {
    width: 100px;
    height: 100px;
    display: inline-block;
    background-color: blue;
  }
</style>
<div id="box" class="abc def" age="18">
  <span name="nekoaimer" class="content">span元素</span>
  <strong></strong>
  <a href="#"></a>
</div>
  • 通过上面属性打印看看
const divEl = document.querySelector("#box")
console.log(divEl.id) // box
console.log(divEl.tagName) // DIV
console.log(divEl.children) // HTMLCollection(3) [span.content, strong, a, nekoaimer: span.content]
console.log(divEl.className) // abc def
console.log(divEl.classList) // DOMTokenList(2) ['abc', 'def', value: 'abc def']
console.log(divEl.clientWidth) // 200
console.log(divEl.clientHeight) // 200
console.log(divEl.offsetLeft) // 0
console.log(divEl.offsetTop) // 0

# Element 常见的方法

  • 因为继承自 Node ,那么很多方法都是有的,比如 appendChild、removeChild ......
  • 下面说两个特有的方法,以下面案例举栗
<div id="box" class="abc def" age="16"></div>
  • getAttribute 获取属性值 https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getAttribute
  • setAttribute 设置属性与值 https://developer.mozilla.org/zh-CN/docs/Web/API/Element/setAttribute
const divEl = document.querySelector("#box")
const value = divEl.getAttribute("age")
console.log(value) // 16
divEl.setAttribute('friend', 'saber') 
console.log(divEl) // <div id="box" class="abc def" age="16" friend=“saber”></div>

# 认识事件监听

  • 前面我们讲到了 JavaScript 脚本和浏览器之间交互时,浏览器给我们提供的 BOM、DOM 等一些对象模型。
    • 事实上还有一种需要和浏览器经常交互的事情就是事件监听:
    • 浏览器在某个时刻可能会发生一些事件,比如鼠标点击、移动、滚动、获取、失去焦点、输入内容等等一系列的事件;
  • 我们需要以某种方式(代码)来对其进行响应,进行一些事件的处理;
    • 在 Web 当中,事件在浏览器窗口中被触发,并且通过绑定到某些元素上或者浏览器窗口本身,那么我们就可以给这些元素或者 window 窗口来绑定事件的处理程序,来对事件进行监听。
  • 如何进行事件监听呢?
    • 事件监听方式一:在 script 中直接监听;
    • 事件监听方式二:通过元素的 on 来监听事件;
    • 事件监听方式三:通过 EventTarget 中的 addEventListener 来监听;
  • 举几个栗子,先来个样式方便点击
<style>
  .box {
    width: 100px;
    height: 100px;
    background-color: pink;
  }
</style>

# 在 script 中直接监听

  • 这种是在 div 中直接写 onclick
<div class="box" onclick="console.log('div元素被点击')"></div>
  • 或者是写上一个函数,直接调用
<div class="box" onclick="divClick()"></div>
function divClick() {
  console.log("div元素被点击2")
}

# 通过元素的 on 来监听事件

  • 这是 HTML 元素
<div class="box"></div>
  • 通过 onClick 来监听,但缺点是只能监听一个,也就是说最后一个事件会覆盖上面两个事件
const divEl = document.querySelector(".box")
divEl.onclick = function() {
  console.log("div元素被点击1")
}
divEl.onclick = function() {
  console.log("div元素被点击2")
}
divEl.onclick = function() {
  console.log("div元素被点击3")
}
  • 所以这里只会打印第二个输出: div元素被点击3

# 通过 EventTarget 中的 addEventListener 来监听

  • 这是 HTML 元素
<div class="box"></div>
  • onClick 区别是可以监听多个事件
const divEl = document.querySelector(".box")
divEl.addEventListener("click", () => console.log("div元素被点击1"))
divEl.addEventListener("click", () => console.log("div元素被点击2"))
divEl.addEventListener("click", () => console.log("div元素被点击3"))
  • 上面三条信息都会在控制台输出

# 认识事件流的由来

  • 事实上对于事件有一个概念叫做事件流,为什么会产生事件流呢?
    • 我们可以想到一个问题:当我们在浏览器上对着一个元素点击时,你点击的不仅仅是这个元素本身
    • 这是因为我们的 HTML 元素是存在父子元素叠加层级的
    • 比如一个 span 元素是放在 div 元素上的,div 元素是放在 body 元素上的,body 元素是放在 html 元素上的

# 事件冒泡和事件捕获

  • 我们会发现默认情况下事件是从最内层的 span 向外依次传递的顺序,这个顺序我们称之为事件冒泡(EventBubble)。

    • 事实上,还有另外一种监听事件流的方式就是从外层到内层(body -> span),这种称之为事件捕获(EventCapture);
    • 为什么会产生两种不同的处理流呢?
      • 这是因为早期浏览器开发时,不管是 IE 还是 Netscape 公司都发现了这个问题,但是他们采用了完全相反的事件流来对事件进行了传递;
      • IE 采用了事件冒泡的方式,Netscape 采用了事件捕获的方式;
  • 那么我们如何去监听事件捕获的过程呢?

  • 通过下面栗子举栗

<style>
  body {
    color: white;
    background-color: yellow;
  }
  .container {
    width: 200px;
    height: 200px;
    background-color: pink;
  }
  .span {
    width: 100px;
    height: 100px;
    display: inline-block;
    background-color: rgb(228, 79, 10);
  }

# 事件冒泡

  • 下面就是事件冒泡由里往外传
const spanEl = document.querySelector(".span")
const divEl = document.querySelector(".container")
spanEl.addEventListener("click", () => console.log("事件冒泡: span元素被点击了"))
divEl.addEventListener("click", () => console.log("事件冒泡: div元素被点击了"))
document.body.addEventListener("click", () => console.log("事件冒泡: body元素被点击了"))
  • 当点击了 span 元素时打印顺序 span -> div -> body
// 事件冒泡: span 元素被点击了
// 事件冒泡: div 元素被点击了
// 事件冒泡: body 元素被点击了
  • 当点击了 div 元素时打印顺序 div -> body
// 事件冒泡: div 元素被点击了
// 事件冒泡: body 元素被点击了
  • 这种事件流称之为事件冒泡

# 事件捕获

  • addEventListener 里传入第三个参数为 true ,就是事件捕获由外往里传的
const spanEl = document.querySelector(".span")
const divEl = document.querySelector(".container")
spanEl.addEventListener("click", (event) => console.log("事件捕获: span元素被点击了"), true)
divEl.addEventListener("click", () => console.log("事件捕获: div元素被点击了"), true)
document.body.addEventListener("click", (event) => console.log("事件捕获: body元素被点击了"), true)
  • 当点击了 span 元素时打印顺序 body -> div -> span
// 事件捕获: body 元素被点击了
// 事件捕获: div 元素被点击了
// 事件捕获: span 元素被点击了
  • 当点击了 div 元素时打印顺序 body -> div
// 事件捕获: div 元素被点击了
// 事件捕获: body 元素被点击了

# 事件对象 event

  • 当一个事件发生时,就会有和这个事件相关的很多信息:
    • 比如事件的类型是什么,你点击的是哪一个元素,点击的位置是哪里等等相关的信息;
    • 那么这些信息会被封装到一个 Event 对象中;
    • 该对象给我们提供了想要的一些属性,以及可以通过该对象进行某些操作;
  • 常见的属性:
    • type:事件的类型;
    • target:当前事件发生的元素;
    • currentTarget:当前处理事件的元素;
    • offsetX、offsetY:点击元素的位置;
  • 常见的方法:
    • preventDefault:取消事件的默认行为;
    • stopPropagation:阻止事件的进一步传递;
  • 更多事件类型详情 MDN:https://developer.mozilla.org/zh-CN/docs/Web/Events

# 常见的属性

  • 举个栗子
<style>
  body {
    color: white;
    background-color: yellow;
  }
  .container {
    width: 200px;
    height: 200px;
    background-color: pink;
  }
  .span {
    width: 100px;
    height: 100px;
    display: inline-block;
    background-color: #e44f0a;
  }
</style>
<div class="container">
  div元素
  <span class="span">span元素</span>
</div>
  • 点击 span 元素
const spanEl = document.querySelector(".span")
spanEl.addEventListener("click", (event) => {
  console.log("span元素被点击:", event) 
  console.log("事件的类型:", event.type) 
  console.log("事件的元素:", event.target, event.currentTarget) 
  console.log("事件发生的位置:", event.offsetX, event.offsetY) 
})
  • 打印下面信息
//span 元素被点击: PointerEvent {isTrusted: true, pointerId: 1, width: 1, height: 1, pressure: 0, …}
// 事件的类型: click
// 事件的元素: <span class="span">span 元素 & lt;/span><span class="span">span 元素 & lt;/span>
// 事件发生的位置: xx yy
  • 点击 div 元素
const divEl = document.querySelector(".container")
divEl.addEventListener("click", (event) => console.log("div元素被点击:", event.target, event.currentTarget))
  • 打印下面信息
//div 元素被点击: <div class="container">…</div><div class="container">…</div>

# 常见的方法

  • preventDefault 阻止默认行为
  • 举个栗子
<a href="https://nekoaimer.com">nekoaimer</a>
  • 点击不会跳转到 https://nekoaimer.com 页面
const aEl = document.querySelector("a")
aEl.addEventListener("click", (event) => event.preventDefault())
  • stopPropagation 阻止事件冒泡
  • 举个栗子
<style>
  body {
    color: white;
    background-color: yellow;
  }
  .container {
    width: 200px;
    height: 200px;
    background-color: pink;
  }
  .span {
    width: 100px;
    height: 100px;
    display: inline-block;
    background-color: #e44f0a;
  }
</style>
<div class="container">
  div元素
  <span class="span">span元素</span>
</div>
  • 加上 event.stopPropagation()
spanEl.addEventListener("click", (event) => {
  event.stopPropagation()
  console.log("事件捕获: span元素被点击了")
})
divEl.addEventListener("click", () => console.log("事件捕获: div元素被点击了"))
document.body.addEventListener("click", (event) => console.log("事件捕获: body元素被点击了"))
  • 点击 span , 回答下只打印了下面一句信息,因为阻止了冒泡
// 事件捕获: span 元素被点击了
  • 但是点击 div 后依旧与之前一样向外进行冒泡 div -> body
// 事件捕获: div 元素被点击了
// 事件捕获: body 元素被点击了
Update on Views times

Give me a cup of [coffee]~( ̄▽ ̄)~*

Nico Niconi WeChat Pay

WeChat Pay

Nico Niconi Alipay

Alipay