JavaScript基础七

十. DOM和BOM

1. HelloWorld

  • 要使用DOM来操作网页, 我们需要浏览器至少得先给我一个对象才能去完成各种操作

  • 所以浏览器已经为我们提供了一个document对象, 它是一个全局变量可以直接使用

  • Document代表的是整个网页

  
    let btnEl = document.getElementById("btn")
    // 修改btn中的文字
    btnEl.onclick = function () {
      btnEl.textContent = "Hello World"
    }

    // btnEl.innerText = "Click Me"

2. 文档节点

  • Document
    • document对象表示的是整个网页

    • document对象的原型链

      • HTMLDocument – Document – Node – EventTarget – Object.propotype – null
    • 凡是在原型链上存在的对象的属性和方法都可以通过Document调用

    • 部分属性

      • document.document.Element – html根元素
      • document.head – head元素
      • document.title – title元素
      • document.body – body元素
      • document.links – 获取页面中所有的超链接

3. 获取元素节点

   <button id="btn01">点我一下</button>
  <span class="s1">我是span</span>
  <span class="s1">我是span</span>
  <span class="s1">我是span</span>
  <span class="s1">我是span</span>
  <span class="s1">我是span</span>

  <div>我是div</div>
  <div>我是div</div>
  <div>我是div</div>
  <div>我是div</div>
  <div>我是div</div>

  <form>
    <input type="text" name="username">
    <input type="radio" name="gender" value="male"><input type="radio" name="gender" value="female"></form>
  • 元素节点对象(Element)
    • 在网页中, 每一个标签元素都是元素节点
    • 如何获取元素节点对象?
      1. 通过document对象来获取节点
      2. 通过document对象来创建元素节点
    • 通过document来获取已有的元素节点
      • document.getElementById()

        • 根据id获取一个元素节点对象
      • document.getElementsClassName()

        • 根据元素的class属性来获取一组元素节点对象
        • 返回的是类数组对象
        • 该方法返回的结果是一个实时更新的集合
        • 当网页当中新添加元素时, 集合也会事实的刷新
      • document.getElementsByTagName()

        • 根据标签名获取一组元素节点对象
        • 返回的结果是可以实时更新的集合
        • document.getElementsByTagName(“*”) 获取页面所有的元素
      • document.getElementsByName()

        • 根据name属性获取一组元素节点对象
        • 返回一个实时更新的集合
        • 主要用于表单项
      • document.querySelectorAll()

        • 根据选择器去页面中查询
        • 它会返回一个类数组不会实时刷新
        • 有一个没有都会返回伪数组
      • document.quserySelector()

        • 根据选择器去页面中查询第一个符合条件的元素
        • 它会返回一个类数组不会实时刷新
      • 创建一个元素节点

        • document.createElement()
          • 根据标签名创建一个元素选择器
  const btnEl = document.getElementById("btn01")

    const s1El = document.getElementsByClassName("s1")

    const divEl = document.getElementsByTagName("div")

    const genderInput = document.getElementsByName("gender")

    const divEls = document.querySelectorAll("div")
    
    const div = document.querySelector("div")

    const h2 = document.createElement("h2")

    // for (let i = 0; i < s1El.length; i++) {
      // alert(s1El[i])
      // s1El[i].innerText = "你在干嘛"
    // }
    // console.log(document.getElementsByClassName("s1"))
    // console.log(s1El)
    // console.log(btnEl)

4. 元素属性和方法

  • div元素的原型链 span就是HMLSpanElement

    • HTMLDivElement – HTMLElement – Element – Node – EventTarget – Object.propotype – null
  • 通过元素节点对象获取其他节点的方法 带**的更重要

    • element.childNodes 获取当前元素的子节点 (会包括空白的子节点)

    • element.children 获取当前元素的子元素 **

    • element.firstChild 获取第一个子节点

    • element.firstElementChild 获取第一个子元素 **

    • element.lastChild 获取最后一个子节点

    • element.lastElementChild 获取最后一个子元素 **

    • element.nextSibling 获取后兄弟节点

    • element.nextElementSibling 获取后兄弟元素 **

    • element.preiousSibling 获取前兄弟节点

    • element.previousElementSibling 获取前兄弟元素 **

    • elenent.parentNode 获取父节点

    • element.parentElement 获取父元素

    • element.tagName 获取当前元素标签名

  const box1 = document.querySelector("#box1")
    
    // 只找box1里面的span 
    // const spans = box1.getElementsByTagName("span")
    const spans = box1.getElementsByClassName("s1")

    const cns = box1.childNodes

    const children = box1.children

    const last = box1.lastChild

    console.log(last)

5. 文本节点

  • 在DOM中, 网页中所有的文本内容都是文本节点对象
    • 可以通过元素来获取其中的文本节点对象, 但是我们通常不会这么做

    • 我们可以直接通过元素去修改其中的文本

      • 修改文本的三个属性
        • element.textContent 获取或修改元素中的文本内容
        • 获取的是标签中的内容, 不会考虑css样式
        • 当字符串中有特殊字符时, 会自动对标签进行转义
<li> &lt;li&gt;
  • element.innerText 获取或修改元素中的文本内容

    • innerText会考虑css样式
    • 通过innerText去读取css样式, 会触发网页的重排(计算css样式)
  • element.innerHTML 获取或修改元素中的HTML代码

    • 可以直接向元素中添加html代码
    • innerHTML在插入内容时, 有被xss注入的风险
  const box1 = document.getElementById("box1")

      // const text = box1.firstChild  不用这样
      // console.log(text)

      // box1.innerText = "xxx"
      // box1.textContent = "你好易语言"
      // box1.innerText = "你好text" 

6. 属性节点

  • 属性节点(Attr)
    • 在DOM也是一个对象, 通常不需要获取对象而是直接通过元素即可完成对齐的各种操作
    • 如何操作属性节点:
      • 方式一:
        • 读取: 元素.属性名 (注意class属性需要用className改)
        • 读取一个布尔值时, 会返回一个true或者false
        • 修改: 元素.属性名 = 属性值
      • 方式二:
        • 读取: 元素.getAttribute(属性名)
        • 修改: 元素.setAttribute(属性名, 属性值)
        • 删除: 元素.removeAttribute(属性名)
  // const input = document.getElementsByName("username")[0]
    const input = document.querySelector("[name=username]")
    
    // 方式一
    // 修改
    // input.value = "熏悟空"
    // input.disabled = true
    // input.disabled = false

    // 读取class
    // console.log(input.className)

    // 方式二
    // 修改
    input.setAttribute("value", "猪八戒")
    input.setAttribute("disabled", "disabled")

    // 读取
    console.log(input.getAttribute("type"))
    console.log(input.getAttribute("name"))

7. 事件

  • onclick 单击
  • ondblclick 双击
  • onmouseenter 鼠标移入
  • 事件(Event)
    • 事件就是用户和页面之间发生的交互行为
    • 比如: 点击按钮, 鼠标移动, 双击按钮, 敲击键盘, 松开按键
    • 可以通过为事件绑定响应函数(回调函数), 来完成和用户之间的交互
    • 绑定相应函数的方式有多种
      1. 可以直接在元素的属性中设置
      2. 可以通过为元素的指定属性设置回调函数的形式来绑定事件
        • 一个事件只能绑定一个响应函数
      3. 可以通过元素的addEventListener()方法来绑定事件
      • 一个事件可以绑定多个响应函数
  // 1. 获取到按钮对象
    const btnEl = document.getElementById("btn")
    // 2. 为按钮对象的事件属性设置响应函数
    // btnEl.onclick = function() {
    //   console.log("点我干嘛")
    // }

      btnEl.addEventListener("click", function() {
        console.log("你点我干嘛")
      })

      btnEl.addEventListener("click", function() {
        console.log("你在惦记我")
      })

8. 文档加载事件

  • 网页是自上而下加载的, 如果将js代码编写在网页上边
    • js代码在执行时, 网页还没有加载完毕, 这时会出现无法获取到DOM对象的情况

    • window.onload 事件会在窗口内容加载完毕之后才触发

    • DOMContentLoaded事件会在当前文档加载完毕之后触发

    • 如何解决这个问题

      1. 将script标签编写到body的最后 (*****)
      2. 将代码编写到onload回调函数中
      3. 可以当代码编写到document对象的DOMContentLoaded (执行时机更早)
      4. 将代码编写代外部js文件中, 然后以defer的进行引入 (执行时机更早, 早于DOMContentLoaded) (*****)
   window.onload = function () {
      const btn = document.getElementById("btn")
      console.log(btn)
    }

    document.addEventListener("DOMContentLoaded", function() {
      const btn = document.querySelector("#btn")
      console.log(btn)
    })

    const btn = document.getElementById("btn")

    console.log(btn)

9. 练习

   .outer {
      width: 604px;
      margin: 50px auto;
      /* 文本居中 按钮居中 */
      text-align: center;
    }
  <div class="outer">
    <p id="info">
      总共n张图片, 当前是第n张
    </p>
    <div class="img-wrapper">
      <img src="./images/banner_01.jpeg" alt="">
    </div>

    <div class="btn-wrapper">
      <button id="prev">上一张</button>
      <button id="next">下一张</button>
    </div>

  </div>
   // 获取到图片
    const img = document.getElementsByTagName("img")[0]
    // 获取到按钮
    const prev = document.getElementById("prev")
    const next = document.getElementById("next")

    // 获取info文字
    let info = document.getElementById("info")


    // 创建一个数组存储图片的路径
    const imgArr = [
      "./images/banner_01.jpeg",
      "./images/banner_02.jpeg",
      "./images/banner_03.jpeg",
      "./images/banner_04.jpeg",
      "./images/banner_05.jpeg"
    ]

    // 创建一个变量来记录当前图片的索引
    let current = 0

    info.textContent = `总共 ${imgArr.length} 张图片, 当前是第${current + 1}`

    // 点击prev按钮后, 上一张
    prev.onclick = function () {
      current--
      // 检查current是否合法
      if (current < 0) {
        current = imgArr.length - 1
      }

      img.src = imgArr[current]
      info.textContent = `总共 ${imgArr.length} 张图片, 当前是第${current + 1}`
    }

    // 点击next按钮后, 切换图片
    next.onclick = function () {
      // 切换图片
      current++
      // 判断是否合法
      if (current > imgArr.length - 1) {
        current = 0
      }

      img.src = imgArr[current]
      info.textContent = `总共 ${imgArr.length} 张图片, 当前是第${current + 1}`
    }

10. 练习

  <div>
    <form action="#">
      <div>
        请选择你的爱好
        <input type="checkbox" id="check-all"> <span class="qx">全选</span>
      </div>
      <div>
        <input type="checkbox" name="hobby" value="乒乓球"> 乒乓球
        <input type="checkbox" name="hobby" value="羽毛球"> 羽毛球
        <input type="checkbox" name="hobby" value="篮球"> 篮球
        <input type="checkbox" name="hobby" value="足球"> 足球
      </div>
      <div>
        <button type="button" id="all">全选</button>
        <button type="button" id="no">取消</button>
        <button type="button" id="reverse">反选</button>
        <button type="button" id="send">提交</button>
      </div>
    </form>
  </div>
   /* 
      全选功能
      取消
      反选
      提交
      让四个小checkbox和大的checkbox同步
    */

    /* 
      全选
        - 点击按钮, 使四个多选框都变成选中的状态
    */

    // 获取all全选按钮
    let allBtn = document.getElementById("all")
    // 获取四个多选框
    let hobbys = document.getElementsByName("hobby")
    // 获取取消按钮
    let noBtn = document.getElementById("no")

    let qx = document.querySelector(".qx")
    let checkAll = document.getElementById("check-all")
    // 为全选按钮绑定响应函数
    allBtn.onclick = function () {
      // 将多选框设置为选中状态
      for (let i = 0; i < hobbys.length; i++) {
        hobbys[i].checked = true
        qx.textContent = "取消"
      }
      checkAll.checked = true
    }

    // 取消功能, 点击取消, 取消所有的选择
    noBtn.onclick = function () {
      for (let i = 0; i < hobbys.length; i++) {
        hobbys[i].checked = false
        qx.textContent = "全选"
      }
      checkAll.checked = false
    }

    // 反选功能
    let reverse = document.getElementById("reverse")
    reverse.onclick = function () {
      for (let i = 0; i < hobbys.length; i++) {
        hobbys[i].checked = !hobbys[i].checked
        if (hobbys[i].checked === true) {
          qx.textContent = "取消"
          checkAll.checked = true
        }
        if (hobbys[i].checked === false) {
          qx.textContent = "全选"
          checkAll.checked = false
        }
      }

    }

    // 提交按钮
    // 点击按钮后, 将选中的按钮显示出来
    let send = document.getElementById("send")
    send.onclick = function () {
      for (let i = 0; i < hobbys.length; i++) {
        if (hobbys[i].checked) {
          alert(hobbys[i].value)
          hobbys[i].checked = false
          checkAll.checked = false
        }
      }
    }

    // 全选按钮


    // change 发生变化

    checkAll.onclick = function () {

      // 当事件的响应函数中, 响应函数绑定给谁this就是谁 (箭头函数除外)
      // console.log(this)

      for (let i = 0; i < hobbys.length; i++) {
        // hobbys[i].checked = !hobbys[i].checked
        hobbys[i].checked = this.checked
        if (hobbys[i].checked === true) {
          qx.textContent = "取消"
        }
        if (hobbys[i].checked === false) {
          qx.textContent = "全选"
        }
      }
    }

    /* 
      使全选的checkAll和四个checked进行同步
        如果四个全选, 则全选的checkAll也选中
        如果四个没全选, 则全选的checkAll不选中
    */
    for (let i = 0; i < hobbys.length; i++) {
      hobbys[i].onchange = function() {
        // 判断hbs是否为全选装填
        // 获取所有选中的checkAll
        // 我要找这堆找到选中的
        let checkedBox = document.querySelectorAll("[name=hobby]:checked")
        if(checkedBox.length === hobbys.length) {
          checkAll.checked = true
          qx.textContent = "取消"
        }
        if(checkedBox.length !== hobbys.length) {
          checkAll.checked = false
          qx.textContent = "全选"
        }
      }
    }

10. DOM修改

  <button id="btn01">按钮1</button>
  <button id="btn02">按钮2</button>

  <hr>

  <ul id="list">
    <li id="swk">孙悟空</li>
    <li id="zbj">猪八戒</li>
    <li id="shs">沙和尚</li>
  </ul>
  • 添加子节点 appendChild用于给一个节点添加一个子节点
    • list.appendChild(li)

    • insertAdjacentElement() 可以向元素任意的位置添加元素

    • 两个参数, 1. 要添加的位置 2. 添加的元素

      • beforeend 标签的最后 (当前元素子元素)
      • afterbegin 标签的开始 (当前元素子元素)
      • beforegin 在元素的前面插入元素(当前元素兄弟元素)
      • afterend 在元素的后面插入元素(当前元素兄弟元素)
    • list.insertAdjacentElement(“afterbegin”, li)

    • insertAdjacentHTML 直接向网页中添加代码

   // 获取ul
    const list = document.getElementById("list")


    // 获取按钮
    const btn01 = document.getElementById("btn01")
    btn01.onclick = function () {
      // 创建一个唐僧

      // 创建一个li
      const li = document.createElement("li")
      // 向li中添加一个文本
      li.textContent = "唐僧"
      // 给li添加id属性
      li.id = "ts"
       list.insertAdjacentHTML("beforeend", "<li id='bgj'>白骨精</li>")
    }

    const btn02 = document.getElementById("btn02")
    btn02.onclick = function() {
      // 创建蜘蛛精, 替换孙悟空
      const li = document.createElement("li")
      li.textContent = "蜘蛛精"
      li.id = "zhj"

      // 获取孙悟空
      const swk = document.getElementById("swk")
      

      // replaceWith() 使用一个元素替换当前元素
      // 用li替换swk  
      // swk.replaceWith(li)

      // remove() 用来删除当前元素
      // swk.remove()
    }

12. 练习

  <div class="outer">
    <table>
      <tbody>
        <tr>
          <th>姓名</th>
          <th>邮件</th>
          <th>薪资</th>
          <th>操作</th>
        </tr>
        <tr>
          <td>孙悟空</td>
          <td>swk@hgs.com</td>
          <td>10000</td>
          <td><a href="javascript:;">删除</a></td>
        </tr>
        <tr>
          <td>猪八戒</td>
          <td>zbj@glz.com</td>
          <td>8000</td>
          <td><a href="javascript:;">删除</a></td>
        </tr>
        <tr>
          <td>沙和尚</td>
          <td>shs@lsh.com</td>
          <td>6000</td>
          <td><a href="javascript:;">删除</a></td>
        </tr>
      </tbody>
    </table>

    <form action="#">
      <div>
        <label for="name">姓名</label>
        <input type="text" id="name" />
      </div>
      <div>
        <label for="email">邮件</label>
        <input type="email" id="email" />
      </div>
      <div>
        <label for="salary">薪资</label>
        <input type="number" id="salary" />
      </div>
      <button>添加</button>
    </form>
  </div>
   /* 
        点击删除超链接后, 删除当前的员工信息
    */

    function delEmpHandler() {
      // 本练习中的超链接我们不希望跳转, 但是跳转行为是超链接默认行为, 
      // 只要点击超链接就会触发页面中的跳转, 事件中可以取消默认行为来阻止超链接默认行为
      // 使用return false来取消默认行为, 只有xxx.xxx = function() {}这种形式绑定的事件中才执行
      // return false


      // 删除当前员工信息, 删除当前元素所在的tr
      // console.log(this)

      // thi表示当前点击的超链接
      const tr = this.parentNode.parentNode

      // 获取要删除的员工姓名
      // const empName = tr.getElementsByTagName("td")[0].textContent
      const empName = tr.firstElementChild.textContent

      // 弹出一个友好的提示
      // confirm 确认取消删除
      let flag = confirm("你确认要删除【" + empName + "】吗")
      if (flag) {

        tr.remove()
      }
    }

    // 获取所有的超链接
    const links = document.links
    // 为它们绑定单击响应
    for (let i = 0; i < links.length; i++) {
      links[i].onclick = delEmpHandler
    }

13. 练习更新

   function delEmpHandler() {
      const tr = this.parentNode.parentNode
      const empName = tr.firstElementChild.textContent
      let flag = confirm("你确认要删除【" + empName + "】吗")
      if (flag) {
        tr.remove()
      }
    }

    const links = document.links
    for (let i = 0; i < links.length; i++) {
      links[i].onclick = delEmpHandler
    }

    /* 
      点击按钮后, 将用户信息插入到表格中
    */
    // 获取tbody
    const tbody = document.querySelector("tbody")

    const btn = document.getElementById("btn")
    btn.onclick = function () {
      // 获取用户输入的数据
      const name = document.getElementById("name").value
      const email = document.getElementById("email").value
      const salary = document.getElementById("salary").value

      // 将获取到的数据设置为dom对象

      // 这种写法容易被攻击, 有风险
      tbody.insertAdjacentHTML("beforeend", `
      <tr>
          <td>${name}</td>
          <td>${email}</td>
          <td>${salary}</td>
          <td><a href="javascript:;">删除</a></td>
        </tr>
        `)

        // 由于上面的超链接是新添加的, 所以它的上面并没有绑定单击响应函数, 所以新添加的员工无法删除
        // 解决方式, 为新添加的员工绑定响应函数
        links[links.length - 1].onclick = delEmpHandler
    }

14. 练习更新

  function delEmpHandler() {
      const tr = this.parentNode.parentNode
      const empName = tr.firstElementChild.textContent
      let flag = confirm("你确认要删除【" + empName + "】吗")
      if (flag) {
        trNaNpxove()
      }
    }

    const links = document.links
    for (let i = 0; i < links.length; i++) {
      links[i].onclick = delEmpHandler
    }

    /* 
      点击按钮后, 将用户信息插入到表格中
    */
    // 获取tbody
    const tbody = document.querySelector("tbody")

    const btn = document.getElementById("btn")
    btn.onclick = function () {
      // 获取用户输入的数据
      const name = document.getElementById("name").value
      const email = document.getElementById("email").value
      const salary = document.getElementById("salary").value

      // 将获取到的数据设置为dom对象

      // 创建元素
      const tr = document.createElement("tr")

      // 创建td
      const nameTd = document.createElement("td")
      const emailTd = document.createElement("td")
      const salaryTd = document.createElement("td")

      // 添加文本
      nameTd.textContent = name
      emailTd.textContent = email
      salaryTd.textContent = salary

      // 将三个td添加到tr中
      tr.appendChild(nameTd)
      tr.appendChild(emailTd)
      tr.appendChild(salaryTd)
      tr.insertAdjacentHTML("beforeend", `<td><a href="javascript:;">删除</a></td>` )
      
      tbody.appendChild(tr)

        // 由于上面的超链接是新添加的, 所以它的上面并没有绑定单击响应函数, 所以新添加的员工无法删除
        // 解决方式, 为新添加的员工绑定响应函数
        links[links.length - 1].onclick = delEmpHandler
    }

15. 节点复制

  <button id="btn01">点我一下</button>

  <ul id="list1">
    <li id="l1">孙悟空</li>
    <li id="l2">猪八戒</li>
    <li id="l3">沙和尚</li>
  </ul>

  <ul id="list2">
    <li>蜘蛛精</li>
  </ul>
  const list2 = document.getElementById("list2")
    const l1 = document.getElementById("l1")
    const btn01 = document.getElementById("btn01")
    btn01.onclick = function() {

      // cloneNode里面传入true和false ,
      // true代表一起克隆. 不写或者false代表不克隆
      const newl1 = l1.cloneNode(true) // 用来对节点进行复制的
      /* 
        使用cloneNode节点进行复制的时候, 它会复制节点所有特点, 包括所有属性
          这个方法默认只会复制当前节点. 不会复制节点的子节点
          可以传递一个true作为参数, 这样该方法也会将元素的子节点一起克隆
      */

      newl1.id = "newl1"

      list2.appendChild(newl1)
    }

16. 修改css样式

 <button id="btn">点我一下</button>
<div class="box1"></div>

  const btn = document.getElementById("btn")
  const box1 = document.querySelector(".box1")
  btn.onclick = function () {
    btn.style.marginLeft = "500px"
    // 修改box1的样式
    // 修改样式的方法:  元素.style.样式名 = "样式值"
    // 注意: 如果样式名中含有-, 则需要将样式名修改为驼峰命名法  background-color -- backgroundColor
    box1.style.width = "400px"
    box1.style.backgroundColor = "blue"
  }

17. 读取css样式

  • getComputedStyle()
    • 它会返回一个对象, 这个对象中包含了当前元素所有生效的样式
  • 参数
    1. 要获取样式对象
    2. 要获取的伪元素
  • 返回值
    • 返回的是一个对象, 对象中存储了当前元素样式
  • 注意:
    • 样式对象中返回的样式值不一定拿来直接计算
    • 所以使用时, 一定要确保是可以计算的才去计算
  const btn = document.querySelector("#btn")
    const box = document.querySelector(".box")

    btn.onclick = function() {
      // 点击按钮读取css元素样式
    
      const styleObj = getComputedStyle(box)

      // console.log(styleObj.color) // 获取不到伪元素
      // console.log(parseInt(styleObj.width) + 200 + "px")
      // console.log(styleObj.backgroundColor)
      console.log(styleObj.width)


      const beforeStyle = getComputedStyle(box, "::before")
      console.log(beforeStyle.color)
    }

18. 通过属性读取样式

  • 元素.clientHeight
    -元素.clientWidth

    • 获取元素内部的宽度和高度(包括内容区和内边距)
  • 元素.offsetHeight

  • 元素.offsetWidth

    • 获取元素可见框的大小(包括内容区, 内边距, 边框)
  • 元素.scrollHeight

  • 元素.scrollWidth

    • 获取元素滚动区域的大小
  • 元素.offsetParent

    • 获取元素的定位父元素
    • 离当前元素最近的开启了定位元素的父元素, 如果所有的元素都没有开启定位则返回body
  • 元素.offsetTop

  • 元素.offsetleft

    • 获取元素相对于其定位元素的偏移量
  • 元素.scrollTop

  • 元素.scrollLeft

    • 获取或设置元素滚动条的偏移量

19. 操作class

  • 元素.classList.add() 向元素中添加一个或多个class
  • 元素.calssList.remove() 移除元素中的一个或多个class
  • 元素.classList.toggle() 切换元素中的一个class
  • 元素.classList.contains() 检查元素是否包含某个属性
      // 添加
      box1.classList.add("box2, box3, box4")
      box1.classList.add("box2")
      
      // 删除
      box1.classList.remove("box2")
      
      // 切换  
      box1.classList.toggle("box2")


      // 检查是否包含
      let result = box1.classList.contains("box2")
       console.log(result )

  • 除了直接修改样式外, 也可以通过修改class属性来间接的影响

    • 通过class修改样式的好处
      1. 可以一次性修改多个样式
      2. 对js和css进行解耦
        • box1.className += " box2"
  • 元素.classList 是一个对象, 对象中提供了对当前元素的类的各种操作

20. 事件对象

  • Event 事件
    • 事件对象
      • 事件对象是浏览器在事件触发时所创建的对象
      • 这个对象中封装了事件相关的各种信息
      • 通过事件对象可以获取到事件的详细信息
        • 比如: 鼠标的坐标, 键盘的按键
        • 浏览器在创建事件对象后, 会将事件对象作为响应函数的参数传递
        • 所以我们可以在事件的回调函数中定义一个形参来接收事件对象
  • mousemove 鼠标移动
   box1.addEventListener("mousemove", function(event) {
      // console.log(event.clientX, event.clientY)
      box1.textContent = event.clientX + ',' +event.clientY
    })

21. 事件对象

  • 在DOM中存在多种不同类事件对象

    • 多种事件对象有一个共同的祖先 Event

      • event.target 表示触发的事件
      • event.currentTarget 绑定事件对象 (同this)
      • event.stopPropagation() 停止事件的传导
      • event.preventDefault() 取消默认行为 推荐使用这个, 不要使用return fasle
    • 事件的冒泡(bubble)

      • 事件的冒泡就是指事件的向上传递
      • 当元素上的某个事件被触发后, 其祖先元素上的相同事件也会同时触发
      • 冒泡的存在大大的简化了代码的编写, 但是在一些场景下我们并不希望冒泡的存在
      • 不希望事件冒泡时, 可以通过事件对象来取消冒泡
  • event.target 表示的是触发事件的对象

   const box1 = document.getElementById("box1")
    const box2 = document.getElementById("box2")
    const box3 = document.getElementById("box3")
    const chao = document.getElementById("pop")

    chao.addEventListener("click", function(event) {
      event.preventDefault()  // 取消默认行为
      console.log("被点击了")
    })

    box1.addEventListener("click", function (event) {

      /* 
        在事件的响应函数中
          event.target 表示的是触发事件的对象
          this 绑定事件的对象
      */

      // console.log(event.target)


      // event.currentTarget  当前的对象
      console.log(event.currentTarget)
    })

    // box2.addEventListener("click", function(event) {
    //   event.stopPropagation()  // 取消事件的传导
    //   console.log("我是box2")
    // })

    // box3.addEventListener("click", function(event) {
    //   event.stopPropagation()  // 取消事件的传导
    //   console.log("我是box3")
    // })

22. 事件冒泡

   const box1 = document.getElementById("box1")
    const box2 = document.getElementById("box2")
    document.addEventListener("mousemove", function(event) {
      box1.style.left = event.clientX + "px"
      box1.style.top = event.clientY + "px"
    })

    box2.addEventListener("mousemove", function(event) {
      event.stopPropagation()
    })

23. 事件委派

  • 思路:
    • 可以将事件统一绑定个document, 这样点击超链接时由于事件冒泡
    • 会导致document上的点击事件被触发, 这样只绑定一次, 所有的超链接都会被触发
      const links = document.querySelectorAll("ul a")
    const list = document.getElementById("list")
    const btn = document.getElementById("btn")

    // for (let i = 0; i < links.length; i++) {
    //   links[i].addEventListener("click", function(event) {
    //     console.log(event.target.textContent)
    //   })
    // }

    // // 点击按钮后, 在ul里添加一个新的li
    // btn.addEventListener("click", function(event) {
    //   list.insertAdjacentHTML("beforeend", "<li><a href='Javascript:;'>新超链接</a></li>")
    // })

    document.addEventListener("click", function(event) {
      console.log(event.target.textContent)
    })
      
    btn.addEventListener("click", function(event) {
      list.insertAdjacentHTML("beforeend", "<li><a href='Javascript:;'>新超链接</a></li>")
    })

24. 事件委派

  • 事件委派
    • 将本该绑定给多个元素的事件, 统一绑定给document, 这样可以降低代码的复杂度方便维护
  const list = document.getElementById("list")
    const btn = document.getElementById("btn")

    // 获取list中的所有链接
    const links = list.getElementsByTagName("a")

    document.addEventListener("click", function (event) {
      // 在执行代码之前, 先判断一下事件是由谁触发的
      // 检查event.terget是否在links中存在
      // console.log(Array.from(links))
      if ([...links].includes(event.target)) {
        console.log(event.target.textContent)
      }
    })

    // 点击按钮后, 在ul中添加一个新的li
    btn.addEventListener("click", function (event) {
      list.insertAdjacentHTML("beforeend", "<li><a href='Javascript:;'>新超链接</a></li>")
    })

25. 事件捕获

  • 事件的传播机制(事件捕获)
    • 在DOM中, 事件的传播可以分为三个阶段:

      • 事件捕获 (由祖先元素向目标元素进行事件的捕获)(默认阶段, 事件不会在捕获阶段触发)
      • 目标阶段 (触发事件的对象)
      • 冒泡阶段 (由目标元素向祖先元素冒泡的阶段)
    • 事件的捕获, 指事件从外向内的传导

      • 当前元素上的触发事件以后, 会先从当前元素最大的祖先元素开始向当前元素开始进行事件捕获

      • 如果希望在捕获阶段触发事件, 可以在addEventListener的第三个参数设置为true

        • 一般情况下我们不希望在捕获阶段触发, 所以通常都不需要设置第三个参数
   const box1 = document.getElementById("box1")
    const box2 = document.getElementById("box2")
    const box3 = document.getElementById("box3")

    box1.addEventListener("click", function (event) {
      // event.stopPropagation()  // 停止事件的传导, 如果是冒泡就把冒泡停了, 如果是捕获就把捕获停了
      // 这样 2 3 都不会显示了
      alert(event.eventPhase) // eventPhase 表示事件触发的时机
      // 返回1表示捕获阶段, 2目标节点
    }, )

    box2.addEventListener("click", function (event) {
      alert(event.eventPhase)
    }, )

    box3.addEventListener("click", function (event) {

      alert(event.eventPhase)
    }, )

26. BOM

  • BOM

    • 浏览器对象模型
    • BOM为我们提供了一组对象, 通过这组对象可以完成对浏览器的各种操作
  • BOM对象:

    • Window - 代表浏览器窗口 (全局对象)
    • Navigator - 浏览器的对象 (可以用来识别浏览器)
    • Location - 浏览器地址栏信息
    • History - 浏览器历史记录 (控制浏览器的前进后退)
    • Screen - 屏幕的信息 (用的比较少)
  • BOM对象都是作为window对象的属性保存的, 所以可以直接在JS中访问这些对象

27. navigator

  • Navigator
    • userAgent 返回一个用来描述浏览器信息的字符串
  console.log(navigator.userAgent)

28. location

  • Location 表示浏览器地址栏的信息
    • 可以直接将location的值修改为一个新的地址, 这样会使的网页发生跳转
    • location.href 可以获取当前的地址
    • location.assign() 跳转到新的页面
    • location.replace() 使用新网址替换当前网址, (不会产生历史记录, 无法通过回退按钮回退)
    • location.reload() 刷新页面, 可以传递一个true来强制缓存的刷新
const btn = document.getElementById("btn")
    btn.addEventListener("click", function(event) {

      // console.log(location)
      // location = "https://www.lilichao.com"
      // console.log(location.href)

      // location.assign("https://www.lilichao.com")

      // location.replace("https://www.lilichao.com")

      location.reload(true)

    })

29. Histort

  • History
    • history.back()
      • 回退
    • history.forward()
      • 前进
    • history.go()
      • 可以向前跳转(正数向前), 也可以向后跳转(负数向后), 还可以指定数量
   const btn = document.getElementById("btn")

     btn.addEventListener("click", function(event) {
        // console.log(History.length)

        // history.back()

        // history.forward()

        // history.go(-1)
     })

30. 定时器

  • 通过定时器, 可以使代码在指定时间后执行
    • 设置定时器的方式有两种:
      • setTimeout()

        • 参数
          1. 回调函数 (要执行的代码)
          2. 间隔的时间(毫秒)
      • setTimeout()只会执行一次

        • 关闭定时器

          • clearTimeout() 里面传定时器的标识
        • setInterval()

          • 参数
            3. 回调函数
            4. 间隔的时间
        • setinterval()会重复执行

        • 关闭定时器

          • clearInterval() 里面传定时器的标识
   // let time = setTimeout(function() {
    //   console.log("我是定时器中的代码")
    // }, 3000)

    // const btn = document.querySelector("button")
    // btn.onclick = function() {
    //   clearTimeout(time)
    // }


    // let interval = setInterval(function() {
    //   console.log("哈哈哈哈")
    // }, 2000)

    // const btn = document.querySelector("button")
    // btn.onclick = function() {
    //   clearInterval(interval)
    // }

    let numH1 = document.getElementById("num")
    num = 0

    let inter = setInterval(() => {
      num++
      numH1.textContent = num
      if (num === 10) {
        clearInterval(inter)
      }
    }, 1000)

31. 事件循环

  • 事件循环 (event loop)

    • 函数在每次执行时, 都会产生执行环境
    • 执行环境负责存储函数执行时产生的一切数据
    • 问题: 函数的执行环境要存储到哪里呢?
      • 函数的执行环境存储到了一个叫调用栈的地方
      • 栈, 是一种数据结构, 特点: 后进先出
  • 调用栈 (call stack)

    • 调用栈负责存储函数的执行环境
    • 当一个函数被调用时, 它的执行环境会作为一个栈帧
    • 插入到调用栈的栈顶, 函数执行完毕其栈帧会自动从栈中弹出

32. 事件循环

  • 事件循环 (event loop)

    • 函数在每次执行时, 都会产生执行环境
    • 执行环境负责存储函数执行时产生的一切数据
    • 问题: 函数的执行环境要存储到哪里呢?
      • 函数的执行环境存储到了一个叫调用栈的地方
      • 栈, 是一种数据结构, 特点: 后进先出
      • 队列, 是一种数据结构, 特点: 先进先出
  • 调用栈 (call stack)

    • 调用栈负责存储函数的执行环境
    • 当一个函数被调用时, 它的执行环境会作为一个栈帧
      • 插入到调用栈的栈顶, 函数执行完毕其栈帧会自动从栈中弹出
  • 消息队列

    • 消息队列负责存储将要执行的函数
    • 当我们触发了一个事件时, 其响应函数并不是直接就添加到调用栈中的
      • 因为调用栈中可能会存在一些还没有执行完毕的代码
      • 事件触发后, JS引擎是将事件响应函数插入到消息队列排队
function fn() {
      let a = 10
      let b = 20

      function fn2() {
        console.log("fn2")
      }
      fn2()
    }
    fn()

    console.log(1111)


    const btn = document.getElementById("btn")

    btn.onclick = function () {
      console.log("被点击了")
    }

33. 综合练习-贪吃蛇

* {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    #main {
      height: 460px;
      width: 360px;
      border: 10px #000 solid;
      background-color: #b7d4a8;
      border-radius: 20px;
      margin: 50px auto;
    }

    #stage {
      width: 304px;
      height: 304px;
      border: 2px solid #000;
      margin: 20px auto;
      /* 相对定位 */
      position: relative;
    }

    #snake>div {
      width: 10px;
      height: 10px;
      background-color: #000;
      position: absolute;
      border: 1px solid #b7d4a8;
    }

    #food {
      width: 10px;
      height: 10px;
      /* 绝对定位 */
      position: absolute;
      top: 100px;
      left: 120px;

      display: flex;
      flex-flow: wrap;
    }

    #food>div {
      width: 5px;
      height: 5px;
      background-color: #000;
      transform: rotate(40deg);
    }

    #info {
      width: 304px;
      margin: 0 auto;
      display: flex;
      /* 文字左右对齐 */
      justify-content: space-between;
      font-weight: 700;
      font-size: 20px;
      font-family: courier;
    }
   <!-- 外边大框 -->
  <div id="main">
    <!-- 里面的游戏区域 -->
    <div id="stage">

      <!-- 蛇 -->
      <div id="snake">
        <div></div>
      </div>

      <!-- 果实 -->
      <div id="food">
        <div></div>
        <div></div>
        <div></div>
        <div></div>
      </div>
    </div>

    <!-- 分数 -->
    <div id="info">
      <div>SCORE: <span id="score">0</span></div>
      <div>LEVEL: <span id="level">1</span></div>
    </div>
  </div>

    // 获取蛇的容器
    const snake = document.getElementById("snake")
    //获取蛇的各个部分
    const snakes = snake.getElementsByTagName("div")

    // 获取食物
    const food = document.getElementById("food")

    // 获取计分板
    const scoreSpan = document.getElementById("score")
    const levelSpan = document.getElementById("level")

    // 创建变量存储分数登记
    let score = 0
    let level = 0

    /* 
      食物的取值坐标应该在0 = 290之间
    */

    function changeFood() {
      // 生成 0 - 29之间的随机数 然后再乘以10
      const x = Math.floor(Math.random() * 30) * 10
      const y = Math.floor(Math.random() * 30) * 10

      // 设置食物的坐标
      food.style.left = x + "px"
      food.style.top = y + "px"
    }

    // 定义一个变量用来存储蛇的移动方向
    let dir

    // 创建一个变量来记录按键的状态
    let keyActive = true

    /*
      绑定时间keydown按下 keyup松开
        - 键盘事件只能绑定给可以获取焦点的元素或者是document
    */

    const keyArr = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight",]

    // 创建一个对象
    const reObj = {
      ArrowUp: "ArrowDown",
      ArrowDown: "ArrowUp",
      ArrowLeft: "ArrowRight",
      ArrowRight: "ArrowLeft",
    }

    /* 
      游戏进制掉头, 
        构成的要件
          1. 身体超过2包括2
          2. 方向不能是相反的方向
        处理:
            保持原来的方向不变, 不修改dir的值
    */

    document.addEventListener("keydown", function (event) {
      if (keyActive && keyArr.includes(event.key)) {
        // 判断蛇是否掉头
        if (snakes.length < 2 || reObj[dir] !== event.key) {
          // 设置方向
          dir = event.key
          keyActive = false
        }

      }

    })

    /* 
      要使得身体和头一起移动, 只需要在蛇移动时, 变化蛇尾巴的位置
    */

    // 废弃了
    // let isGameOver = false

    setTimeout(function move() {

      // 蛇头
      const head = snakes[0]

      // 获取蛇头坐标
      let x = head.offsetLeft
      let y = head.offsetTop

      switch (dir) {
        case "ArrowUp":
          // 向上移动
          y = y - 10
          break
        case "ArrowDown":
          // 向下移动
          y += 10
          break
        case "ArrowLeft":
          // 向左移动
          x = x - 10
          break
        case "ArrowRight":
          // 向右移动蛇
          x += 10
          break
      }

      // 检查蛇是否吃到食物
      if (head.offsetTop === food.offsetTop && head.offsetLeft === food.offsetLeft) {
        // console.log("吃到食物了")
        // 1. 改变食物的位置
        changeFood()

        // 2. 增加蛇的身体
        snake.insertAdjacentHTML("beforeend", "<div/>")
        score++
        scoreSpan.textContent = score

        // 检查等级
        if(score % 1 === 0 && level < 14) {
          level++
          levelSpan.textContent = level + 1 
        }
      }

      /* 
        判断蛇是否结束:
          1. 撞墙
          2. 撞自己
      */

      // 判断撞墙
      if (x < 0 || x > 290 || y < 0 || y > 290) {
        alert("撞墙了, 游戏结束!")
        // 游戏结束
        return
      }

        // 判断是否撞到自己
        for (let i = 0; i < snakes.length - 1; i++) {
          if (snakes[i].offsetLeft === x && snakes[i].offsetTop === y) {
            alert("撞到自己了")
            return
          }
        }



      // 获取尾巴
      const tail = snakes[snakes.length - 1]
      // 移动蛇的位置
      tail.style.left = x + "px"
      tail.style.top = y + "px"
      // 将尾巴移动到蛇头的位置

      snake.insertAdjacentElement("afterbegin", tail)

      keyActive = true
      setTimeout(move, 300 - level * 20)
    }, 300)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coderyhh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值