• Web APIs


    0x01 概述

    • API 是一些预先定义的函数,提供应用程序与开发人员基于某软件或硬件访问一组例程的能力
    • Web APIs 是 W3C 组织的标准,是 JS 独有的部分
    • Web API 是浏览器提供的一套操作浏览器功能和页面元素的 API,即 DOM 与 BOM

    0x02 DOM

    (1)简介

    • 文档对象模型(Document Object Model)是 W3C 组织推荐的处理可扩展标记语言的标准编程接口

      • 可以改变网页内容、结构和样式
    • DOM 树:DOM 对象

      根元素
      < html >
      元素
      < head >
      元素
      < body >
      元素
      < title >
      元素
      < a >
      元素
      < h1 >
      文本
      文档标题
      属性
      href
      文本
      链接文本
      文本
      一级标题
      文档
      • 文档:即页面,DOM 中使用 document 表示
      • 元素:页面中的任何标签,DOM 中使用 element 表示
      • 节点:页面中的任何内容,DOM 中使用 node 表示

    (2)获取元素

    a. 根据 id 属性

    • getElementById()

    • 返回带有指定 id元素对象

    <p id="content">This is a paragraph.p>
    <script>
      const pElement = document.getElementById("content");
      console.dir(pElement);
    script>
    

    b. 根据标签名

    • getElementByTagName()

    • 返回带有指定标签名的元素对象集合

    <ul>
      <li>Item 1li>
      <li>Item 2li>
      <li>Item 3li>
    ul>
    <script>
      const liElements = document.getElementsByTagName("li");
      console.log(liElements);
    script>
    
    • 也可以用于获取父元素内所有指定标签名的子元素

      • 父元素必须为单个对象
      <ul>
        <li>Item 1li>
        <li>Item 2li>
        <li>Item 3li>
      ul>
      <script>
        const ulElement = document.getElementsByTagName("ul");
        const liElements = ulElement[0].getElementsByTagName("li");
        console.log(liElements);
      script>
      

    c. 通过 HTML5 新方法

    • 根据类名返回元素对象集合

      • getElementByClassName()
      <div class="item">Item 1div>
      <div class="item">Item 2div>
      <div class="item">Item 3div>
      <script>
        const divElements = document.getElementsByClassName("item");
        console.log(divElements);
      script>
      
    • 根据指定选择器返回第一个元素对象

      • querySelector()
      <div class="item">Item 1div>
      <div class="item">Item 2div>
      <div class="item">Item 3div>
      <script>
        const divElement = document.querySelector(".item");
        console.log(divElement);
      script>
      
    • 根据指定选择器返回元素对象集合

      • querySelectorAll()
      <div class="item">Item 1div>
      <div class="item">Item 2div>
      <div class="item">Item 3div>
      <script>
        const divElements = document.querySelectorAll(".item");
        console.log(divElements);
      script>
      

    d. 特殊元素

    • html 元素

      html>
      <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <title>Documenttitle>
        head>
        <body>
          This is a simple page.
          <script>
            const htmlElement = document.documentElement;
            console.dir(htmlElement);
          script>
        body>
      html>
      
    • body 元素

      <body>
        This is a simple page.
        <script>
          const bodyElement = document.body;
          console.dir(bodyElement);
        script>
      body>
      

    (3)事件基础

    • JavaScript 可用于创建动态页面,事件是可以被 JavaScript 侦测到的行为

    • 页面中每个元素都可以产生某些可以触发 JavaScript 的事件

      <p>This is a paragraph.p>
      <script>
        const pElement = document.querySelector("p");
        pElement.onclick = function (e) {
          console.log(e);
        };
      script>
      
    • 事件三要素包括:

      1. 事件源:事件被触发的对象
      2. 事件类型:触发事件的方式
      3. 事件处理:通过函数处理事件
    • 执行事件的步骤:

      1. 获取事件源
      2. 注册(或绑定)事件
      3. 添加事件处理程序
        • 采用函数赋值形式
    • 常见鼠标事件

      事件 条件
      onclick 左键点击后触发
      onmouseover 经过时触发
      onmouseout 离开时触发
      onfocus 获得焦点触发
      onblur 失去焦点触发
      onmousemove 移动触发
      onmouseup 弹起触发
      onmousedown 按下触发

    (4)操作元素

    • 使用 DOM 操作可以改变网页内容、结构和样式

    a. 内容

    • innerHTML:起始位置到终止位置的全部内容,包括 HTML 标签、空格、换行等
    • innerText:类似 innerHTML不包括 HTML 标签、空格、换行等
    <p>
      This is a paragraph.<br />
      <span>Text in spanspan>
    p>
    <script>
      const pElement = document.querySelector("p");
      console.log(pElement.innerHTML);
      console.log(pElement.innerText);
    script>
    

    b. 属性

    • srchrefidvalue

    • 举例 1:图片切换至下一张

      <button id="next">下一张图片button>
      <img src="./1.png" alt="图片1" />
      <script>
        const next = document.getElementById("next");
        const imgElement = document.querySelector("img");
        next.onclick = () => {
          imgElement.src = "./2.png";
          imgElement.alt = "图片2";
          next.disabled = true;
        };
      script>
      
    • 举例 2:显隐密码

      <input type="password" name="password" autofocus />
      <label>显示密码label>
      <script>
        const label = document.querySelector("label");
        const input = document.querySelector("input");
        let flag = 1;
        label.onclick = () => {
          if (flag) {
            label.innerText = "隐藏密码";
            input.type = "text";
            flag = 0;
          } else {
            label.innerText = "显示密码";
            input.type = "password";
            flag = 1;
          }
        };
      script>
      

    c. 样式

    • 行内样式操作

      <div style="width: 300px; height: 300px; background-color: red">div>
      <script>
        const div = document.querySelector("div");
        div.onclick = () => {
          div.style.backgroundColor = "blue";
        };
      script>
      
      • JavaScript 中,样式采取驼峰命名法,如 backgroundColor
      • JavaScript 修改样式操作产生的是行内样式,CSS 的权重更高
    • 类名样式操作

      <div style="width: 300px; height: 300px; background-color: red">div>
      <script>
        const div = document.querySelector("div");
        div.onclick = () => {
          div.className = "clicked";
        };
      script>
      
      • className 会更改(覆盖)元素的类名
    • 举例 1:密码框格式错误提示信息

      <div>
        <input type="password" />
        <p>输入 3~10 个字符p>
      div>
      <script>
        const input = document.querySelector("input")
        const p = document.querySelector("p");
        input.onblur = function() {
          if (this.value.length < 3 || this.value.length > 10) {
            p.className = 'error'
            p.innerText = "字符数错误"
          } else {
            p.className = 'right'
            p.innerText = "字符数正确"
          }
        }
      script>
      
    • 举例 2:(排他思想)

      <button>按钮 1button>
      <button>按钮 2button>
      <button>按钮 3button>
      <script>
        const buttons = document.querySelectorAll("button");
        for (let i = 0; i < buttons.length; i++) {
          buttons[i].onclick = function () {
            for (let j = 0; j < buttons.length; j++) {
              buttons[j].style.backgroundColor = "";
            }
            this.style.backgroundColor = "red";
          };
        }
      script>
      
    • 举例 3:表格行在鼠标悬浮时换色

      <table border="1">
        <thead>
          <tr>
            <th>Nameth>
            <th>Ageth>
            <th>Genderth>
          tr>
        thead>
        <tbody>
          <tr>
            <td>Johntd>
            <td>20td>
            <td>Maletd>
          tr>
          <tr>
            <td>Janetd>
            <td>21td>
            <td>Femaletd>
          tr>
          <tr>
            <td>Jimtd>
            <td>22td>
            <td>Maletd>
          tr>
        tbody>
      table>
      <script>
        const trs = document.querySelector("tbody").querySelectorAll("tr");
        for (let i = 0; i < trs.length; i++) {
          trs[i].onmouseover = function () {
            this.style.backgroundColor = "red";
          };
          trs[i].onmouseout = function () {
            this.style.backgroundColor = "";
          };
        }
      script>
      
    • 举例 4:表单全选的选中与取消

      <table border="1">
        <thead>
          <tr>
            <th><input type="checkbox" id="selectAll" />th>
            <th>Nameth>
            <th>Ageth>
          tr>
        thead>
        <tbody>
          <tr>
            <td><input type="checkbox" />td>
            <td>Johntd>
            <td>20td>
          tr>
          <tr>
            <td><input type="checkbox" />td>
            <td>Janetd>
            <td>21td>
          tr>
          <tr>
            <td><input type="checkbox" />td>
            <td>Jimtd>
            <td>22td>
          tr>
        tbody>
      table>
      <script>
        const selectAll = document.getElementById("selectAll");
        const checkboxes = document.querySelector("tbody").querySelectorAll("input");
        selectAll.onchange = function () {
          checkboxes.forEach(function (checkbox) {
            checkbox.checked = selectAll.checked;
          });
        };
        for (let i = 0; i < checkboxes.length; i++) {
          checkboxes[i].onchange = function () {
            if (!this.checked) {
              selectAll.checked = false;
            } else {
              let allChecked = true;
              checkboxes.forEach(function (checkbox) {
                if (!checkbox.checked) {
                  allChecked = false;
                }
              });
              selectAll.checked = allChecked;
            }
          };
        }
      script>
      

    d. 自定义属性

    • element.属性 获取的是内置属性(即元素本身自带的属性)
    • element.getAttribute('属性') 获取的是自定义和内置的属性
    <div>div>
    <script>
      const div = document.querySelector("div");
      div.setAttribute("data-tabindex", 1); 				 // 设置属性
      const tabindex = div.getAttribute("data-tabindex");    // 获取属性
      console.log(tabindex);
      div.removeAttribute("data-tabindex"); 				 // 移除属性
    script>
    
    • 自定义属性的目的:保存并使用数据

      • 对于可以简单且可以明文展示的数据可以保存在页面中,省去使用数据库
    • HTML5 规定,自定义属性使用 data- 前缀命名并赋值

    • HTML5 新增以下方法获取属性值:

      <div data-class-name="div">div>
      <script>
        const div = document.querySelector("div");
        console.log(div.dataset.className); 	 // 方法一
        console.log(div.dataset["className"]); // 方法二
      script>
      

    (5)节点操作

    a. 简介

    • 节点操作主要是利用节点层级关系获取元素

      • 利用父子兄节点关系获取元素
      • 逻辑性强
      • 兼容性差
    • 节点是页面中的任何内容,DOM 中使用 node 表示,均可使用 JavaScript 访问

    • 一般地,节点至少拥有以下基本属性:

      • 节点类型:nodeType

        节点类型 nodeType
        HTML 元素 1
        属性 2
        文本 3
      • 节点名称:nodeName

      • 节点值:nodeValue

    b. 父节点

    <div id="parent">
      <div id="child">div>
    div>
    <script>
      const parent = document.getElementById("child").parentNode;
      console.log(parent);
    script>
    

    c. 子节点

    • 获取子节点集合,包括元素节点、文本节点等

      <div id="parent">
        <div id="child1">div>
        <div id="child2">div>
        <div id="child3">div>
      div>
      <script>
        const children = document.getElementById("parent").childNodes;
        console.log(children);
      script>
      
    • 只获取子节点集合中的元素节点

      const children = document.getElementById("parent").childNodes;
      for (let i = 0; i < children.length; i++) {
        if (children[i].nodeType === 1) {
          console.log(children[i]);
        }
      }
      

      const children = document.getElementById("parent").children;
      console.log(children);
      
    • 第一个子节点

      const first = document.getElementById("parent").firstChild;
      console.log(first);
      

      第一个元素子节点

      • 推荐方法

        const first = document.getElementById("parent").children[0];
        console.log(first);
        
      • 存在兼容性问题,需要 IE9+

        const first = document.getElementById("parent").firstElementChild;
        console.log(first);
        
    • 最后一个子节点

      const last = document.getElementById("parent").lastChild;
      console.log(last);
      

      最后一个元素子节点

      • 推荐方法

        const parent = document.getElementById("parent");
        const last = parent.children[parent.children.length - 1];
        console.log(last);
        
      • 存在兼容性问题,需要 IE9+

        const last = document.getElementById("parent").lastElementChild;
        console.log(last);
        
    • 举例:导航栏及其下拉菜单

      <ul id="nav">
        <li>
          Item 1
          <ul style="display: none">
            <li>Subitem 1li>
            <li>Subitem 2li>
          ul>
        li>
        <li>
          Item 2
          <ul style="display: none">
            <li>Subitem 1li>
            <li>Subitem 2li>
          ul>
        li>
      ul>
      <script>
        const nav = document.body.children[0];
        const items = nav.children;
        for (let i = 0; i < items.length; i++) {
          items[i].onmouseover = function () {
            this.children[0].style.display = "block";
          };
          items[i].onmouseout = function () {
            this.children[0].style.display = "none";
          };
        }
      script>
      

    d. 兄弟节点

    • 获取当前元素的下一个兄弟节点

      <h1>Titleh1>
      <p>This is a paragraph.p>
      <script>
        const p = document.querySelector("h1").nextSibling;
        console.log(p);
      script>
      
      • 元素节点

        const p = document.querySelector("h1").nextElementSibling;
        console.log(p);
        
    • 获取当前元素的上一个兄弟节点

      <h1>Titleh1>
      <p>This is a paragraph.p>
      <script>
        const h1 = document.querySelector("p").previousSibling;
        console.log(h1);
      script>
      
      • 元素节点

        const h1 = document.querySelector("p").previousElementSibling;
        console.log(h1);
        
    • 上述获取兄弟元素节点的方法,均存在兼容性问题,需要 IE9+,为解决此问题,可以封装以下方法:

      function getNextElementSibling(element) {
        let next = element.nextSibling;
        while (next && next.nodeType !== 1) {
          next = next.nextSibling;
        }
        return next;
      }
      
      function getPreviousElementSibling(element) {
        let prev = element.previousSibling;
        while (prev && prev.nodeType !== 1) {
          prev = prev.previousSibling;
        }
        return prev;
      }
      

    e. 创建与添加节点

    • 动态创建元素节点:createElement()

      const p = document.createElement("p");
      
    • 添加节点至指定父节点的子节点的末尾:appendChild()

      p.innerText = "This is a paragraph"; // 设置标签内容
      document.body.appendChild(p);		 // 添加节点
      
    • 在指定元素前面插入元素:insertBefore()

      const h1 = document.createElement("h1");
      h1.innerText = "Title";
      document.body.insertBefore(h1, p);
      
    • 举例:发布留言

      <textarea>textarea>
      <button>发布button>
      <ul>ul>
      <script>
        const btn = document.querySelector("button");
        btn.onclick = function () {
          const text = document.querySelector("textarea");
          if (text) {
            const ul = document.querySelector("ul");
            const li = document.createElement("li");
            li.innerHTML = text.value;
            // ul.appendChild(li);
            ul.insertBefore(li, ul.firstChild)
          } else {
            alert("发布内容不能为空");
          }
        };
      script>
      
    • 直接将内容写入页面的文档流:write()

      <script>
        document.write("");
        const btn = document.querySelector("button");
        btn.onclick = () => document.write("

      This is a paragraph.

      "
      );
      script>
    • write()innerHTML()createElement() 三种方法区别:

      • write() 在当文档流执行完成后,会导致页面重绘
      • innerHTML() 是将内容写入某个 DOM 节点,适合创建多个元素,结构稍复杂
      • createElement() 结构清晰,效率较低

    f. 删除节点

    • 删除一个节点:removeChild()

      <p>This is a paragraph.p>
      <button>删除节点button>
      <script>
        document.querySelector("button").onclick = () =>
          document.body.removeChild(document.querySelector("p"));
      script>
      
    • 举例 1:删除留言(在“发布留言”案例的基础上修改)

      const btn = document.querySelector("button");
      btn.onclick = function () {
        const text = document.querySelector("textarea");
        if (text) {
          const ul = document.querySelector("ul");
          const li = document.createElement("li");
          li.innerHTML = text.value + "删除";
          ul.insertBefore(li, ul.firstChild);
          const as = document.querySelectorAll("a");
          for (let i = 0; i < as.length; i++) {
            as[i].onclick = function () {
              // this.parentNode.remove();
              ul.removeChild(this.parentNode);
            };
          }
        } else {
          alert("发布内容不能为空");
        }
      };
      
    • 举例 2:动态表格

      <table border="1">
        <thead>
          <tr>
            <th>姓名th>
            <th>年龄th>
            <th>操作th>
          tr>
        thead>
        <tbody>tbody>
      table>
      <script>
        let data = [
          { name: "张三", age: 24 },
          { name: "李四", age: 22 },
          { name: "王五", age: 26 },
          { name: "赵六", age: 21 },
        ];
        const tbody = document.querySelector("tbody");
        for (let i = 0; i < data.length; i++) {
          const tr = document.createElement("tr");
          for (const key in data[i]) {
            const td = document.createElement("td");
            td.innerText = data[i][key];
            tr.appendChild(td);
          }
          const td = document.createElement("td");
          td.innerHTML = "删除";
          tr.appendChild(td);
          tbody.appendChild(tr);
        }
        const as = document.querySelectorAll("a");
        for (let i = 0; i < data.length; i++) {
          as[i].onclick = function () {
            tbody.removeChild(as[i].parentNode.parentNode);
          };
        }
      script>
      

    g. 复制节点

    • 克隆一个节点:cloneNode()

      <p>This is a paragraph.p>
      <script>
        const p = document.querySelector("p").cloneNode(true);
        document.body.appendChild(p);
      script>
      
    • 其中,cloneNode() 的参数默认为 false,即浅拷贝,不会拷贝子节点

    0x03 事件高级

    (1)注册事件

    a. 简介

    • 注册事件又称绑定事件,给元素添加事件
    • 注册事件方式包括:
      • 传统方式
        • on 为前缀的事件,如 onclickonchange
        • 注册事件的唯一性,即同一元素同一事件只能设置一个处理函数,最后注册的处理函数会覆盖前面注册的
      • 方法监听注册方式
        • 采用 W3C 标准
        • 使用 addEventListener() 监听
        • IE9 之前可以使用 attachEvent() 代替
        • 同一元素同一事件可以注册多个监听器,并按注册顺序执行

    b. addEventListener

    • 将指定的监视器注册到目标对象上,当该对象触发指定事件时,就会执行事件处理函数

    • 语法:addEventListener(type, listener[, useCapture])

      • type:事件类型字符串,如 clickchange

      • listener:事件处理函数,即监听函数

      • useCapture:(可选)是否在捕获阶段触发,是布尔值,默认 false

        “事件捕获”在本章节第(3)节说明

      <button>点击button>
      <script>
        document.querySelector("button").addEventListener("click", () => {
          alert("触发点击事件");
        });
      script>
      

    c. attachEvent

    • 将指定的监视器注册到目标对象上,当该对象触发指定事件时,就会执行指定的回调函数
    • 语法:attachEvent(eventNameWiteOn, callback)
      • eventNameWithOn:事件类型字符串,如 onclickonchange
      • callback:回调函数,用于事件处理

    d. 兼容性解决方案

    兼容性处理原则:首先照顾大多数浏览器,再处理特殊浏览器

    function addEventListener(element, eventName, callback) {
      if (element.addEventListener) {
        element.addEventListener(eventName, eventName);
      } else if (element.attachEvent) {
        element.attachEvent(eventName, callback);
      } else {
        element["on" + eventName] = callback;
      }
    }
    

    (2)删除事件

    • 删除事件又称解绑事件

    • 删除事件方式包括:

      • 传统方式

        <button>点击button>
        <script>
          document.querySelector("button").onclick = null;
        script>
        
      • 方法监听删除方式

        • 使用 removeEventListener() 删除

          <button>点击button>
          <script>
            const btn = document.querySelector("button");
            function clickEvent() {
              alert("触发点击事件");
              btn.removeEventListener("click", clickEvent);
            }
            btn.addEventListener("click", clickEvent);
          script>
          
        • IE9 之前可以使用 detachEvent() 代替

    • 兼容性解决方案

      function removeEventListener(element, eventName, callback) {
        if (element.removeEventListener) {
          element.removeEventListener(eventName, callback);
        } else if (element.attachEvent) {
          element.detachEvent("on" + eventName, callback);
        } else {
          element["on" + eventName] = null;
        }
      }
      

    (3)DOM 事件流

    • 事件流描述的是从页面中接收事件的顺序

    • DOM 事件流:事件发生时会在元素节点之间按照特定顺序的传播过程

    • DOM 事件流分三个阶段:

      1. 捕获阶段
        • 事件捕获:由 DOM 最顶层节点开始,逐级向下传播到最具体的元素接收的过程
      2. 当前目标阶段
      3. 冒泡阶段
        • 事件冒泡:事件开始时,由最具体的元素接收,之后逐级向上传播到 DOM 最顶层节点的过程
    • 举例:注册事件采取捕获阶段触发,先父元素节点,后子元素节点

      <div id="parent" style="width: 200px; height: 200px; background: red">
        <div id="child" style="width: 100px; height: 100px; background: green">div>
      div>
      <script>
        document.getElementById("parent").addEventListener(
          "click",
          () => {
            alert("parent");
          },
          true
        );
        document.getElementById("child").addEventListener(
          "click",
          () => {
            alert("child");
          },
          true
        );
      script>
      
    • 部分事件没有冒泡,如 onblur

    (4)事件对象

    • 事件对象代表事件的状态,是事件一系列相关数据的集合,包括鼠标坐标等数据

    • 在以下代码中,形参 event 就是事件对象

      <button>点击button>
      <script>
        document.querySelector("button").onclick = function (event) {
          console.log(event);
        };
      script>
      
    • IE6~8 使用 window.event 写法,兼容性写法为:

      e = event || window.event;
      
    • 常见属性和方法

      属性方法 说明
      target 返回触发事件的对象
      srcElement 返回触发事件的对象(在 IE6~8 中使用)
      type 返回事件类型,如 click 等
      stopPropagation() 阻止冒泡
      cancelBubble 阻止冒泡(在 IE6~8 中使用)
      preventDefault() 阻止默认事件
      returnValue 阻止默认事件(在 IE6~8 中使用)
    • 阻止事件冒泡的兼容性写法

      if (e & e.stopPropagation) {
        e.stopPropagation();
      } else {
        window.event.cancelBubble = true;
      }
      

    (5)事件委托

    • 事件委托又称事件代理,在 jQuery 中称为事件委派

    • 原理:将每个子节点的事件监听器统一设置在其父节点上,利用冒泡原理影响每个子节点

    • 作用:提高性能

    • 举例:

      <ul>
        <li>Item 1li>
        <li>Item 2li>
        <li>Item 3li>
        <li>Item 4li>
        <li>Item 5li>
        <li>Item 6li>
      ul>
      <script>
        document.querySelector("ul").addEventListener("mouseover", function (e) {
          e.target.style.backgroundColor = "red";
        });
      script>
      

    (6)常用鼠标事件

    • 禁用右键菜单

      document.addEventListener("contextmenu", function (e) {
        e.preventDefault();
      });
      
    • 禁止鼠标选中

      document.addEventListener("selectstart", function (e) {
        e.preventDefault();
      });
      
    • 举例:跟随鼠标移动的方框

      <div
        style="
          width: 100px;
          height: 100px;
          background-color: red;
          position: absolute;
          transform: translate(-50%, -50%);
        "
      >div>
      <script>
        const div = document.querySelector("div");
        document.addEventListener("mousemove", (e) => {
          div.style.left = e.pageX + "px";
          div.style.top = e.pageY + "px";
        });
      script>
      
    • mouseentermouseover 的区别在于:mouseenter 不会冒泡

      • mouseenter 只在鼠标经过盒子自身时触发,而 mouseover 经过其子盒子也会触发
      <div
        style="
          width: 200px;
          height: 200px;
          background-color: red;
          position: relative
        "
      >
        <div
          style="
            width: 100px;
            height: 100px;
            background-color: green;
            position: absolute;
            top: 50px;
            left: 50px;
          "
        >div>
      div>
      <script>
        const parent = document.querySelector("div");
        const child = document.querySelector("div div");
        parent.addEventListener("mouseenter", function () {
          console.log("mouseenter");
        });
        parent.addEventListener("mouseover", function () {
          console.log("mouseover");
        });
      script>
      

    (7)常用键盘事件

    • 常用键盘事件

      事件 说明
      keyup 按键松开时触发
      keydown 按键按下时触发
      keypress 按键按下时触发(区别大小写,不识别功能键,如 Ctrl 等)
      keyCode 返回按键的 ASCII 值(区分大小写)
    • 按键执行顺序:

      keydown
      keypress
      keyup
    • 举例:按 / 键选中输入框

      <input />
      <script>
        const input = document.querySelector("input");
        document.addEventListener("keyup", function (e) {
          if (e.keyCode === 191) {
            input.focus();
          }
        });
      script>
      

    0x04 BOM

    (1)简介

    • 浏览器对象模型(Browser Object Model)提供了独立于内容而与浏览器窗口进行交互的对象

    • BOM 由一系列相关对象构成

    • BOM 缺乏标准,最初是 Netscape 浏览器标准的一部分

    • 与 DOM 对比:

      DOM BOM
      名称 文档对象模型 浏览器对象模型
      对象 文档 浏览器
      顶级对象 document window
      功能 操作页面元素 浏览器窗口交互
      标准 W3C 根据浏览器厂商
    • BOM 构成:

      window
      document
      location
      navigation
      screen
      history
      • document:BOM 中包含 DOM
      • window:是浏览器的顶级对象,既是 JS 访问浏览器窗口的接口,也是全局对象
        • 调用 window 对象方法时可以省略,如 alert(),除了 window.name

    (2)window 对象常见事件

    • 窗口加载完成事件

      window.onload = function () {};
      // 或
      window.addEventListener("load", function () {});
      
      • DOM 加载完成事件(需要 IE9+)

        window.addEventListener("DOMContentLoaded", function () {});
        
    • 窗口大小调整事件

      window.onresize = function () {};
      // 或
      window.addEventListener("resize", function () {});
      

    (3)定时器

    a. timeout

    • 设置计时器

      • 语法:setTimeout(callback[, timeMS])

      • 作用:经过指定毫秒后执行回调函数

    • 清除定时器

      • 语法:clearTimeout(timer)
      • 作用:清除 setTimeout() 设置的定时器
    • 举例:

      let timer = setTimeout(function() {}, 1000);
      clearTimeout(timer);
      

    b. interval

    • 设置计时器

      • 语法:setInterval(callback[, timeMS])

      • 作用:间隔指定毫秒后重复执行回调函数

      • 举例:倒计时器

        <div>
          <span class="hour">00span>
          <span class="minute">00span>
          <span class="second">00span>
        div>
        <script>
          const hour = document.querySelector(".hour");
          const minute = document.querySelector(".minute");
          const second = document.querySelector(".second");
          function countDown() {
            const now = new Date();
            console.log(now);
            const target = new Date(2024, 1, 1, 0, 0, 0);
            const diff = target - now;
            hour.innerHTML = Math.floor(diff / 1000 / 60 / 60);
            minute.innerHTML = Math.floor((diff / 1000 / 60) % 60);
            second.innerHTML = Math.floor((diff / 1000) % 60);
          }
          setInterval(countDown, 500);
        script>
        
    • 清除定时器

      • 语法:clearInterval(timer)
      • 作用:清除 setInterval() 设置的定时器
    • 举例:发送短信间隔

      手机号: <input type="number" /><button>获取验证码button>
      <script>
        document.querySelector("button").addEventListener("click", callback);
        function callback() {
          let phone = document.querySelector("input").value;
          if (!/^1[3-9]\d{9}$/.test(phone)) {
            alert("手机号格式不正确");
            return;
          }
          this.disabled = true;
          let time = 10;
          let timer = setInterval(() => {
            this.innerHTML = `重新发送(${time})`;
            time--;
            if (time === 0) {
              clearInterval(timer);
              this.innerHTML = "获取验证码";
              this.disabled = false;
              time = 10;
            }
          }, 1000);
        }
      script>
      

    (4)执行队列

    a. 执行过程

    • JavaScript 的特点是单线程,即同一时间只做一件事

      • 缺点:当 JS 执行时间过长,会导致页面渲染不连贯,产生阻塞的感受
    • HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,即同步异步

      • 同步:顺序依次执行
      • 异步:多任务同时执行
    • 同步任务都在主线程上执行,形成执行栈

    • 异步任务通过回调函数实现,分为以下类型:

      1. 普通事件:如 click
      2. 资源加载:如 load
      3. 定时器

      异步任务相关回调函数会被添加到任务队列(又称消息队列)中

    b. 执行机制

    • JavaScript 执行机制如下:
      1. 先执行执行栈中的同步任务
      2. 将异步任务放入任务队列
      3. 完成执行栈后,按次序读取任务队列,将异步任务放入执行栈并执行
    • 由于主线程不断地重复获取任务、执行任务,因此该机制称为事件循环

    (5)location 对象

    • 用于获取或设置窗体的 URL,并且可以用于解析 URL

    • 属性:

      属性 说明
      href 获取或设置 URL
      host 返回主机(域名)
      port 返回端口号
      pathname 返回路径
      search 返回参数
      hash 返回片段
      • 举例:获取 URL 中携带的参数

        <div>div>
        <script>
          const params = location.search.substr(1);
          const array = params.split("=");
          document.querySelector("div").innerHTML = array[1];
        script>
        
    • 常用方法:

      方法 说明
      assign() 重定向页面,与属性 href 类似,可以跳转页面
      replace() 替换当前页面,替换后不能后退
      reload() 重新加载页面,相当于刷新或 F5
      参数为 true 表示强制刷新或 Ctrl+F5

    (6)navigator 对象

    • 包含有关浏览器的信息

    • 举例:根据终端(浏览器)的不同跳转相应的页面

      if((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|iPad|ios|Android|BlackBerry|IEMobile|Opera Mini|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
        window.location.href = "https://m.example.com/";   // Mobile
      } else {
        window.location.href = "https://www.example.com/"; // PC
      }
      

    (7)history 对象

    • 用于与浏览器历史记录进行交互,包含用户(在浏览器窗口中)访问过的 URL

    • 方法:

      方法 说明
      back() 后退
      forward() 前进
      go(arg) 参数为正数:前进 arg 个页面
      参数为负数:后退 arg 个页面
    • 常见于 OA 办公系统

    0x05 PC 端网页特效

    (1)元素偏移量 offset

    a. 简介

    • 动态获取元素的位置(偏移)、大小等

      • 获取元素距离带有定位父元素的位置
      • 获取元素自身的大小(宽高)
      • 获取的数值不携带单位
    • 常用属性:

      属性 说明
      offsetParent 返回作为该元素带有定位的父级元素
      offsetTop 返回元素相对带有定位父元素上方的偏移
      offsetLeft 返回元素相对带有定位父元素左边框的偏移
      offsetWidth 返回自身包括内边距、边框、内容的宽度
      offsetHeight 返回自身包括内边距、边框、内容的高度
    • 相比之下,offset 更适合获取元素的大小与位置,而 style 更适合修改元素

    b. 获取鼠标在盒子中的坐标

    <div style="width: 200px; height: 200px; background-color: red">div>
    <script>
      document.querySelector("div").addEventListener("mousemove", function (e) {
        let x = e.pageX - this.offsetLeft;
        let y = e.pageY - this.offsetTop;
        this.innerText = `x: ${x}, y: ${y}`;
      });
    script>
    

    c. 拖动模态框

    • HTML

      <button id="open">点击登录button>
      <div id="mask">div>
      <div id="card">
        <h3 id="header">登录框标题h3>
        <button id="close">关闭button>
      div>
      
    • CSS

      #mask {
        width: 100%;
        height: 100%;
        position: fixed;
        top: 0;
        left: 0;
        background-color: rgba(0, 0, 0, 0.3);
        display: none;
      }
      #card {
        width: 400px;
        height: 300px;
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        z-index: 10;
        background-color: white;
        border-radius: 30px;
        box-shadow: 0 0 10px rgb(0, 0, 0);
        text-align: center;
        line-height: 40px;
        display: none;
      }
      #header {
        width: 100%;
        margin-top: 0;
        border-bottom: 2px solid rgba(0, 0, 0, 0.3);
        cursor: move;
      }
      
    • JavaScript

      const mask = document.querySelector("#mask");
      const card = document.querySelector("#card");
      const header = document.querySelector("#header");
      document.querySelector("#open").addEventListener("click", () => {
        mask.style.display = "block";
        card.style.display = "block";
      });
      document.querySelector("#close").addEventListener("click", () => {
        mask.style.display = "none";
        card.style.display = "none";
      });
      header.addEventListener("mousedown", function (e) {
        e.preventDefault();
        let x = e.clientX - card.offsetLeft;
        let y = e.clientY - card.offsetTop;
        function move(e) {
          card.style.left = e.pageX - x + "px";
          card.style.top = e.pageY - y + "px";
        }
        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", function (e) {
          document.removeEventListener("mousemove", move);
        });
      });
      

    d. 图片放大镜

    • HTML

      <div id="image">
        <div id="mask">div>
        <div id="preview">
          <img src="./image.jpg" alt="" id="previewImg" />
        div>
      div>
      
    • CSS

      #image {
        position: relative;
        width: 200px;
        height: 200px;
        background-image: url("./image.jpg");
        background-size: cover;
        border: 1px solid black;
      }
      #mask {
        position: absolute;
        top: 0;
        left: 0;
        width: 100px;
        height: 100px;
        background-color: red;
        opacity: 0.5;
        cursor: move;
        display: none;
      }
      #preview {
        position: absolute;
        top: 0;
        left: 210px;
        width: 300px;
        height: 300px;
        overflow: hidden;
        z-index: 10;
        border: 1px solid black;
        display: none;
      }
      #previewImg {
        position: absolute;
        top: 0;
        left: 0;
      }
      
    • JavaScript

      const image = document.querySelector("#image");
      const mask = document.querySelector("#mask");
      image.addEventListener("mouseover", () => {
        mask.style.display = "block";
        preview.style.display = "block";
      });
      image.addEventListener("mouseout", () => {
        mask.style.display = "none";
        preview.style.display = "none";
      });
      image.addEventListener("mousemove", function (e) {
        let x = e.pageX - this.offsetLeft;
        let y = e.pageY - this.offsetTop;
        let maskX = x - mask.offsetWidth / 2;
        let maskY = y - mask.offsetHeight / 2;
        let maskMaxX = this.offsetWidth - mask.offsetWidth;
        let maskMaxY = this.offsetHeight - mask.offsetHeight;
        if (maskX < 0) {
          maskX = 0;
        } else if (maskX > maskMaxX) {
          maskX = this.offsetWidth - mask.offsetWidth;
        }
        if (maskY < 0) {
          maskY = 0;
        } else if (maskY > maskMaxY) {
          maskY = this.offsetHeight - mask.offsetHeight;
        }
        mask.style.left = maskX + "px";
        mask.style.top = maskY + "px";
      
        const preview = document.querySelector("#preview");
        const previewImg = document.querySelector("#previewImg");
        let previewMaxX = previewImg.offsetWidth - preview.offsetWidth;
        let previewMaxY = previewImg.offsetHeight - preview.offsetHeight;
        previewImg.style.left = -((maskX * previewMaxX) / maskMaxX) + "px";
        previewImg.style.top = -((maskY * previewMaxY) / maskMaxY) + "px";
      });
      

    (2)元素可视区 client

    • 获取元素可视区相关信息,如边框大小、元素大小等

    • 常见属性:

      属性 说明
      clientTop 返回元素上边框的大小
      clientLeft 返回元素左边框的大小
      clientWidth 返回自身包括内边距、内容的宽度
      clientHeight 返回自身包括内边距、内容的高度

    立即执行函数:(function() {})()

    主要作用:创建一个独立的作用域

    (3)元素滚动 scroll

    • 获取元素的大小、滚动距离等

    • 如果浏览器的高度(宽度)不足以显示全部内容,则会自动出现滚动条

      • 当滚动条向下(向右)滚动后,未显示在当前窗口的页面称为被卷去的页面
      • 滚动条在滚动时会触发 onscroll 事件
    • 常见属性:

      属性 说明
      scrollTop 返回被卷去的上侧距离
      scrollLeft 返回被卷去的左侧距离
      scrollWidth 返回自身内容的宽度
      scrollHeight 返回自身内容的高度
    • 被卷去的头部兼容性写法

      function getScroll() {
        return {
          left:
            window.pageXOffset ||
            document.documentElement.scrollLeft ||
            document.body.scrollLeft ||
            0,
          top:
            window.pageYOffset ||
            document.documentElement.scrollTop ||
            document.body.scrollTop ||
            0,
        };
      }
      
    • 举例:侧边栏

      • HTML

        <header>header>
        <div id="content">div>
        <div id="bar">
          <button>回到顶部button>
        div>
        
      • CSS

        header {
          width: 90%;
          height: 200px;
          background-color: #ccc;
          margin: 10px auto;
        }
        #content {
          width: 90%;
          height: 1000px;
          background-color: #eee;
          margin: 10px auto;
        }
        #bar {
          position: absolute;
          top: 300px;
          right: 0;
          width: 30px;
          height: 150px;
          background-color: #ddd;
        }
        button {
          display: none;
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
        }
        
      • JavaScript

        const bar = document.querySelector("#bar");
        const btn = document.querySelector("button");
        let ctnTop = document.querySelector("#content").offsetTop;
        let barTop = bar.offsetTop - ctnTop;
        document.addEventListener("scroll", () => {
          if (window.scrollY > ctnTop) {
            bar.style.position = "fixed";
            bar.style.top = barTop + "px";
            btn.style.display = "block";
          } else {
            bar.style.position = "absolute";
            bar.style.top = "300px";
            btn.style.display = "none";
          }
        });
        btn.addEventListener("click", () => {
          window.scrollTo({
            top: 0,
            behavior: "smooth",
          });
        });
        

    (4)动画函数封装

    • 原理:通过定时器 setInterval() 不断移动盒子的位置

      • 盒子需要定位才能使用动画
      <button>开始button>
      <div
        style="
          width: 200px;
          height: 200px;
          background-color: red;
          position: absolute;
          left: 0;
        "
      >div>
      <script>
        document.querySelector("button").addEventListener("click", () => {
          const div = document.querySelector("div");
          let timer = setInterval(() => {
            if (div.offsetLeft >= 400) {
              clearInterval(timer);
            }
            div.style.left = div.offsetLeft + 1 + "px";
          }, 25);
        });
      script>
      
    • 动画函数的简单封装需要传递两个参数:动画对象、移动距离

      function animate(obj, target) {
        clearInterval(obj.timer);
        obj.timer = setInterval(() => {
          if (obj.offsetLeft >= target) {
            clearInterval(obj.timer);
          }
          obj.style.left = obj.offsetLeft + 1 + "px";
        }, 25);
      }
      document.querySelector("button").addEventListener("click", () => {
        animate(document.querySelector("div"), 500);
      });
      
    • 缓动动画是让元素的运动速度发生变化(即,将移动距离递减)

      • 步长需要取整
      • 正步长向上取整,负步长向下取整
      function animate(obj, target) {
        clearInterval(obj.timer);
        obj.timer = setInterval(() => {
          if (obj.offsetLeft === target) {
            clearInterval(obj.timer);
          }
          let step = (target - obj.offsetLeft) / 50;
          step = step > 0 ? Math.ceil(step) : Math.floor(step);
          obj.style.left = obj.offsetLeft + step + "px";
        }, 25);
      }
      
    • 在动画函数中引入回调函数

      function animate(obj, target, callback) {
        clearInterval(obj.timer);
        obj.timer = setInterval(() => {
          if (obj.offsetLeft === target) {
            clearInterval(obj.timer);
            if (callback) callback();
          }
          let step = (target - obj.offsetLeft) / 50;
          step = step > 0 ? Math.ceil(step) : Math.floor(step);
          obj.style.left = obj.offsetLeft + step + "px";
        }, 25);
      }
      document.querySelector("button").addEventListener("click", () => {
        animate(document.querySelector("div"), 500, () => {
          alert("移动结束");
        });
      });
      
    • 将动画函数封装到单独的 JavaScript 文件中

      1. 创建 animate.js

        /**
         * 实现元素的平滑移动动画。
         * @param {Object} obj - 需要进行动画的DOM对象。
         * @param {number} target - 目标位置,即元素移动到的左偏移量。
         * @param {Function} callback - 动画完成后的回调函数。
         */
        function animate(obj, target, callback) {
          // 停止当前正在进行的动画
          clearInterval(obj.timer);
        
          // 设置定时器,每25毫秒执行一次动画逻辑
          obj.timer = setInterval(() => {
            // 当元素移动到目标位置时,停止动画
            if (obj.offsetLeft === target) {
              clearInterval(obj.timer);
              // 如果设置了回调函数,则动画完成后执行回调
              if (callback) callback();
            }
        
            // 计算每次移动的距离,平滑移动
            let step = (target - obj.offsetLeft) / 50;
            // 确保移动方向正确,且移动步长为整数
            step = step > 0 ? Math.ceil(step) : Math.floor(step);
        
            // 更新元素的左偏移量
            obj.style.left = obj.offsetLeft + step + "px";
          }, 25);
        }
        
      2. 在页面中使用

        <script src="./animate.js">script>
        <script>
          document.querySelector("button").addEventListener("click", () => {
            animate(document.querySelector("div"), 500, () => {
              alert("移动结束");
            });
          });
        script>
        

    0x06 本地存储

    • 本地存储特性:
      • 数据存储在用户浏览器中
      • 存储与读写方便
      • 容量大(sessionStorage - 5M,localStorage - 20M)
      • 只能存储字符串,可以将对象使用 JSON.stringify() 编码后存储

    (1)sessionStorage

    • 当浏览器窗口关闭后,sessionStorage 生命周期终止

    • 在同一窗口(页面)下,数据可以共享

    • 键值对的形式存储和使用

    • 举例:

      <input type="text" />
      <button id="save">存储button>
      <button id="read">读取button>
      <button id="delete">删除button>
      <button id="clear">清空button>
      <script>
        let input = document.querySelector("input");
        document.querySelector("#save").addEventListener("click", () => {
          sessionStorage.setItem("value", input.value);
          alert("存储成功");
        });
        document.querySelector("#read").addEventListener("click", () => {
          let value = sessionStorage.getItem("value");
          alert(`读取内容: ${value}`);
        });
        document.querySelector("#delete").addEventListener("click", () => {
          sessionStorage.removeItem("value");
          alert("删除成功");
        });
        document.querySelector("#clear").addEventListener("click", () => {
          sessionStorage.clear();
          alert("清空成功");
        });
      script>
      

    (2)localStorage

    • 生命周期永久有效,只能手动删除

    • 在同一浏览器下,数据可以共享

    • 键值对的形式存储和使用

    • 举例:

      <input type="text" />
      <button id="save">存储button>
      <button id="read">读取button>
      <button id="delete">删除button>
      <button id="clear">清空button>
      <script>
        let input = document.querySelector("input");
        document.querySelector("#save").addEventListener("click", () => {
          localStorage.setItem("value", input.value);
          alert("存储成功");
        });
        document.querySelector("#read").addEventListener("click", () => {
          value = localStorage.getItem("value");
          alert(`读取内容: ${value}`);
        });
        document.querySelector("#delete").addEventListener("click", () => {
          localStorage.removeItem("value");
          alert("删除成功");
        });
        document.querySelector("#clear").addEventListener("click", () => {
          localStorage.clear();
          alert("清空成功");
        });
      script>
      

    (3)记住用户名

    <input type="text" id="username" />
    <input type="checkbox" id="rememberMe" />
    <label for="rememberMe">记住我label>
    <script>
      const username = document.querySelector("#username");
      const rememberMe = document.querySelector("#rememberMe");
      if (localStorage.getItem("username")) {
        username.value = localStorage.getItem("username");
        rememberMe.checked = true;
      }
      rememberMe.addEventListener("change", function () {
        if (this.checked) {
          localStorage.setItem("username", username.value);
        } else {
          localStorage.removeItem("username");
        }
      });
    script>
    
  • 相关阅读:
    学会用命令行创建uni-app项目并用vscode开放项目
    vue3+ts插槽的使用
    智能合约漏洞案例,Palmswap 90 万美元漏洞分析
    c语言练习93:环形链表的约瑟夫问题
    linux 打开相机工具cheese/guvcview
    Flutter高仿微信-第32篇-单聊-语音
    java计算机毕业设计音乐网站MyBatis+系统+LW文档+源码+调试部署
    二本Java渣渣9面字节遭虐,苦修数月深造这份宝典,终进阿里
    Apache Ant
    华为机考入门python3--(17)牛客17- 坐标移动
  • 原文地址:https://www.cnblogs.com/SRIGT/p/18134525