Web APIs

0x01 概述

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

0x02 DOM

(1)简介

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

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

    graph TB 文档-->0[根元素<br/>< html >]-->1[元素<br/>< head >] & 2[元素<br/>< body >] 1-->11[元素<br/>< title >]-->111[文本<br/>文档标题] 2-->21[元素<br/>< a >] & 22[元素<br/>< h1 >] 21-->211[属性<br/>href] & 212[文本<br/>链接文本] 22-->221[文本<br/>一级标题]
    • 文档:即页面,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 1</li>
 <li>Item 2</li>
 <li>Item 3</li>
</ul>
<script>
 const liElements = document.getElementsByTagName("li");
 console.log(liElements);
</script>
  • 也可以用于获取父元素内所有指定标签名的子元素

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

c. 通过 HTML5 新方法

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

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

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

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

d. 特殊元素

  • html 元素

    <!DOCTYPE html>
    <html lang="en">
     <head>
     <meta charset="UTF-8" />
     <title>Document</title>
     </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 span</span>
</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>按钮 1</button>
    <button>按钮 2</button>
    <button>按钮 3</button>
    <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>Name</th>
     <th>Age</th>
     <th>Gender</th>
     </tr>
     </thead>
     <tbody>
     <tr>
     <td>John</td>
     <td>20</td>
     <td>Male</td>
     </tr>
     <tr>
     <td>Jane</td>
     <td>21</td>
     <td>Female</td>
     </tr>
     <tr>
     <td>Jim</td>
     <td>22</td>
     <td>Male</td>
     </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>Name</th>
     <th>Age</th>
     </tr>
     </thead>
     <tbody>
     <tr>
     <td><input type="checkbox" /></td>
     <td>John</td>
     <td>20</td>
     </tr>
     <tr>
     <td><input type="checkbox" /></td>
     <td>Jane</td>
     <td>21</td>
     </tr>
     <tr>
     <td><input type="checkbox" /></td>
     <td>Jim</td>
     <td>22</td>
     </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 1</li>
     <li>Subitem 2</li>
     </ul>
     </li>
     <li>
     Item 2
     <ul style="display: none">
     <li>Subitem 1</li>
     <li>Subitem 2</li>
     </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>Title</h1>
    <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>Title</h1>
    <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("<button>点击</button>");
     const btn = document.querySelector("button");
     btn.onclick = () => document.write("<p>This is a paragraph.</p>");
    </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 + "<a href='javascript:;'>删除</a>";
     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 = "<a href='javascript:;'>删除</a>";
     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 1</li>
     <li>Item 2</li>
     <li>Item 3</li>
     <li>Item 4</li>
     <li>Item 5</li>
     <li>Item 6</li>
    </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 值(区分大小写)
  • 按键执行顺序:

    graph LR 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 对比:

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

    graph TB 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">00</span>
       <span class="minute">00</span>
       <span class="second">00</span>
      </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>
作者:SRIGT原文地址:https://www.cnblogs.com/SRIGT/p/18134525

%s 个评论

要回复文章请先登录注册