Web API
DOM API
创建元素
document.createElement(tagName):创建指定标签名的元素document.createTextNode(text):创建文本节点
获取元素
document.getElementById(id):通过 id 获取元素,返回单个元素document.getElementsByClassName(className):通过类名获取元素,返回 HTMLCollection(类数组)document.getElementsByTagName(tagName):通过标签名获取元素,返回 HTMLCollection(类数组)document.querySelector(selector):通过 CSS 选择器获取元素,返回单个元素document.querySelectorAll(selector):通过 CSS 选择器获取元素,返回 NodeList(类数组)
插入元素
parentNode.appendChild(childNode):将子节点添加到父节点的最后面parentNode.removeChild(childNode):从父节点移除子节点parentNode.insertBefore(newNode, referenceNode):将新节点插入到参考节点前面parentNode.replaceChild(newNode, oldNode):用新节点替换旧节点element.insertAdjacentHTML(position, htmlString):在指定位置插入 HTML 字符串position可选值:beforebegin:元素前面afterbegin:元素内部的第一个子节点前面beforeend:元素内部的最后一个子节点后面afterend:元素后面
操作元素
element.setAttribute(name, value):设置元素属性element.getAttribute(name):获取元素属性值element.removeAttribute(name):移除元素属性element.classList:操作元素的类名列表element.classList.add(className):添加类名element.classList.remove(className):移除类名element.classList.toggle(className):切换类名element.classList.contains(className):检查是否包含类名
element.style:操作元素的内联样式element.style.propertyName = value:设置样式属性getComputedStyle(element).propertyName:获取计算后的样式属性值
element.innerHTML:获取或设置元素的 HTML 内容element.textContent:获取或设置元素的文本内容element.innerText:获取或设置元素的可见文本内容element.cloneNode(deep):克隆元素,deep为 boolean 值,表示是否深度克隆(包括子节点)
事件处理
element.addEventListener(eventType, listener, options):添加事件监听器options?: boolean | AddEventListenerOptions:capture:boolean 值,表示是否在捕获阶段触发(默认 false,在冒泡阶段执行,可直接传入 boolean 值)once:boolean 值,表示监听器是否只触发一次passive:boolean 值,表示监听器是否永远不会调用preventDefault(),如果监听器调用了preventDefault(),浏览器会忽略它并输出警告信息
element.removeEventListener(eventType, listener, options):移除事件监听器- 事件对象
eventevent.target:触发事件的元素event.currentTarget:当前正在处理事件的元素event.preventDefault():阻止默认行为event.stopPropagation():阻止事件冒泡event.stopImmediatePropagation():阻止事件冒泡并阻止当前元素的其他事件监听器执行
DOM 事件模型
事件传播流程
- 捕获阶段:事件从 document 对象向事件目标元素传播,触发捕获事件监听器
document -> html -> body -> ... -> target
- 目标阶段:事件到达事件目标元素,触发目标事件监听器
- 冒泡阶段:事件从事件目标元素向 document 对象传播,触发冒泡事件监听器
target -> ... -> body -> html -> document
事件委托
- 将事件监听器添加到父元素上,通过事件冒泡机制来处理子元素的事件
- 优点:减少事件监听器的数量,提高性能;动态添加子元素时无需重新绑定事件监听器
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<script>
const list = document.getElementById("list");
list.addEventListener("click", (event) => {
if (event.target.tagName === "LI") {
console.log("绑定事件的元素:", event.currentTarget.tagName); // UL
console.log("触发事件的元素:", event.target.tagName); // LI
console.log(event.target.textContent); // Item 1/2/3
}
});
</script>BOM API
window:表示浏览器窗口的全局对象window.location:表示当前页面的 URL 信息window.history:表示浏览器的历史记录window.navigator:表示浏览器的用户代理信息window.screen:表示用户屏幕的信息window.localStorage:表示浏览器的本地存储window.sessionStorage:表示浏览器的会话存储window.alert(message):显示一个警告框window.confirm(message):显示一个确认框,返回用户的选择window.prompt(message, default):显示一个提示框,返回用户输入的值
navigator
navigator.sendBeacon
navigator.sendBeacon 方法用于在页面卸载时,向服务器异步发送少量数据(ping 请求),常用于发送分析数据或日志信息
- 不会阻塞页面卸载过程
- 可以发送跨域请求
- 只能发送 POST 请求
- 发送的数据量有限制(<= 64KB)
- 不能自定义请求头
- 只能传输
ArrayBuffer,ArrayBufferView,Blob,DOMString,FormData或URLSearchParams类型的数据
window.addEventListener("unload", () => {
const url = "http://localhost:3000/log";
const data = JSON.stringify({
event: "page_unload",
timestamp: Date.now(),
});
const blob = new Blob([data], { type: "application/json" });
navigator.sendBeacon(url, blob);
});SSE/WebSocket
SSE
SSE 是基于 HTTP 的服务器推送技术,允许服务器主动向客户端推送实时数据
- 客户端连接:客户端创建 EventSource 对象,指定服务器的 URL,与服务器建立持久化的 HTTP 长连接(使用 HTTP/HTTPS,不需要升级协议,请求头中包含
Accept: text/event-stream指定事件流格式) - 服务器推送:服务器设置 HTTP 响应头
Content-Type: text/event-stream,向客户端推送事件,每条事件包含event:事件名data:事件数据和id:事件 ID 等,以\n\n分隔多条事件如id: 1\nevent: message\ndata: hello\n\n - 客户端接收:客户端使用 onmessage 或 addEventListener 监听事件,收到事件后,触发对应的事件处理器,处理事件数据
- 连接关闭:客户端关闭 EventSource 对象,关闭与服务器的 HTTP 长连接
readyState:
- CONNECTING 正在建立连接
- OPEN 已建立连接,正在接收服务器推送的数据
- CLOSED 已关闭连接
<!DOCTYPE html>
<html>
<head>
<title>SSE</title>
</head>
<body>
<div id="sse"></div>
<script>
document.addEventListener("keydown", (e) => {
if (e.keyCode === 13) {
const eventSource = new EventSource("/sse");
/* 也可以使用 eventSource.addEventListener('message')
可自定义 默认 message */
eventSource.onmessage = (event) => {
const data = event.data;
document.getElementById("sse").innerHTML += `<p>${data}</p>`;
};
eventSource.onerror = (err) => {
console.error(err);
eventSource.close();
};
}
});
</script>
</body>
</html>import express from "express";
const app = express();
app.get("/sse", (req, res) => {
console.log("Client connected");
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "Keep-Alive");
let counter = 0;
const sendData = () => {
counter++;
const payload = {
time: new Date().toISOString(),
count: counter,
};
res.write(`data: ${JSON.stringify(payload)}\n\n`);
if (counter >= 100) {
clearInterval(timer);
res.end();
}
};
const timer = setInterval(sendData, 1000);
req.on("close", () => {
console.log("Client disconnected");
clearInterval(timer);
});
});
app.listen(3000, () => {
console.log("Listening on port 3000");
});import { defineConfig } from "vite";
/** 启动 vite 服务
* "scripts": {
"dev": "vite sse"
},
*/
export default defineConfig({
server: {
proxy: {
"/sse": {
target: "http://localhost:3000/",
changeOrigin: true,
},
},
},
});WebSocket
WebSocket 前,如果需要在服务器和客户端间双向通信,则需要通过 HTTP 轮询实现,HTTP 轮询分为短轮询和长轮询
短轮询:浏览器使用 JavaScript 启动一个定时器,以固定的间隔向服务器发送请求,询问服务器有没有新消息
缺点:实时性差,频繁的请求会增大服务器的压力
长轮询:浏览器发送请求后,服务器保持连接,等到有新消息时才返回,减少了请求次数,提高了实时性
缺点:
- 多线程服务器的 listener 线程长时间挂起,等待新消息,浪费 CPU 资源
- 一个长时间无数据传输的 HTTP 连接,链路上的任何一个网关都可能关闭该 HTTP 连接,这是不可控的 HTML5 新增 WebSocket 协议,可以在浏览器和服务器间建立不受限制的双向通信的通道
HTTP 通过 header 中是否包含 Connection: Upgrade 和 Upgrade: websocket,以判断是否升级到 WebSocket 协议,其他 header 字段
Sec-WebSocket-Key:浏览器随机生成的安全密钥 Sec-WebSocket-Version:WebSocket 协议版本 Sec-WebSocket-Extensions:协商 WebSocket 连接使用的扩展 Sec-WebSocket-Protocol:协商 WebSocket 连接使用的子协议
WebSocket 特点
- 支持双向通信,实时性高
- 未加密的 WebSocket 协议标识符是
ws://,端口号是 80,对应http://;加密的 WebSocket 协议标识符是wss://,端口号是 443,对https:// - 协议开销小,HTTP 每次通信都需要携带完整的 HTTP 头部,WebSocket 协议的头部较小,减小了数据传输的开销
- 支持扩展:用户可以扩展 WebSocket 协议,也可以自定义子协议(例如可以自定义压缩算法等)
- 没有跨域问题
SSE 和 WebSocket 的区别
- SSE 基于 HTTP,利用 HTTP 的长连接特性,在客户端和服务器间建立持久连接;WebSocket 基于 TCP
- SSE 支持传输 text 文本字符串;WebSocket 支持传输 text 文本字符串和 blob 二进制数据
- SSE 只支持单向数据流,即只支持服务器向客户端推送数据;WebSocket 支持双向数据流,没有消息大小限制
- SSE 不能手动关闭或重新连接;WebSocket 可以手动关闭和重新连接等
import ws from "ws";
const wss = new ws.Server({ port: 8080 }, () => {
console.log("WebSocket started at port 8080");
});
enum State {
HEART = 1,
MESSAGE = 2,
}
wss.on("connection", (socket) => {
console.log("Client connected");
socket.on("message", (e) => {
// socket.send("From Server: " + e.toString());
wss.clients.forEach((client) => {
client.send(JSON.stringify({ type: State.MESSAGE, data: e.toString() }));
});
});
// Heartbeat mechanism
let heartInterval: NodeJS.Timeout;
const heartChecker = () => {
if (socket.readyState === ws.OPEN) {
socket.send(JSON.stringify({ type: State.HEART, data: "ping" }));
} else {
clearInterval(heartInterval);
}
};
heartInterval = setInterval(heartChecker, 3000);
});<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebSocket</title>
</head>
<body>
<div>
<ul id="list"></ul>
<input type="text" id="input" />
<button id="btn">Send</button>
</div>
</body>
</html>
<script>
const ws = new WebSocket("ws://localhost:8080");
const input = document.querySelector("#input");
const btn = document.querySelector("#btn");
const list = document.querySelector("#list");
btn.addEventListener("click", () => {
if (input.value) {
ws.send(input.value);
input.value = "";
}
});
ws.onopen = () => {
console.log("WebSocket connection established");
};
ws.onmessage = (event) => {
console.log("Message from server:", event.data);
let data = JSON.parse(event.data);
if (data.type === 2) {
let li = document.createElement("li");
li.innerText = event.data;
list.appendChild(li);
}
};
ws.onerror = (error) => {
console.error("WebSocket error:", error);
};
/* 调用 ws.close() 可以主动断开 */
ws.onclose = () => {
console.log("WebSocket connection closed");
};
</script>AJAX
AJAX:Asynchronous JavaScript And XML
XMLHttpRequest
- readyState 0:已创建 xhr 实例,未调用 open 方法
- readyState 1:已调用 open 方法,未调用 send 方法
- readyState 2:已调用 send 方法,已收到服务器返回的响应头
- readyState 3:正在接收服务返回的数据
- readyState 4:已收到服务器返回的全部数据
const xhr = new XMLHttpRequest();
/* 请求方式 请求地址 是否异步 默认为 true */
xhr.open("POST", "http://localhost:3000");
xhr.setRequestHeader("Content-Type", "application/json");
// xhr.open("GET", "http://localhost:3000");
/* readyState 改变时,调用的回调函数 */
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
};
/* 监听事件 */
xhr.addEventListener("progress", (event) => {
console.log(event.loaded, event.total);
});
xhr.timeout = 3000;
xhr.addEventListener("timeout", (event) => {
console.log("timeout");
});
/* xhr.abort() 中断请求 可以通过 "abort" 监听 */
xhr.onload = function () {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.log(xhr.status);
}
};
xhr.send(null /* params */);Fetch
text()将响应体解析为文本字符串json()将响应体解析为 JSON 并返回一个 JS 对象blob()将响应体解析为二进制数据,并返回一个 Blob 对象arrayBuffer()将响应体解析为二进制数据,并返回一个 ArrayBuffer 对象formData()将响应体解析为表单数据,并返回一个 FormData 对象
/* 默认 get 请求 */
fetch("http://localhost:3000", {
method: "post",
header: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "rico",
age: 20,
}),
})
.then((res) => res.json())
.then((res) => {
console.log(res);
});
/* 调用 abort.abort() 中断请求 */
const abort = new AbortController();
fetch("http://localhost:3000", {
signal: abort.signal,
})
.then(async (res) => {
const response = res.clone;
const reader = res.body.getReader();
const total = res.headers.get("Content-Length");
let loaded = 0;
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
loaded += value.length;
}
return response.text();
})
.then((res) => {
console.log(res);
});Web Worker
Web Worker 允许在后台线程中运行 JavaScript 代码,避免阻塞主线程,适用于计算密集型任务
特点:
- worker 线程执行的脚本与主线程执行的脚本必须同源
- 为了防止中间人攻击,worker 线程不允许读取本地文件,只允许加载网络文件
- worker 线程不允许操作 DOM,不能使用 window,document,parent(parent === window)对象,可以使用 navigator 和 location 对象
Service Worker
Service worker 充当 Web 应用程序、浏览器与网络之间的代理服务器,适用于 PWA(Progressive Web App)场景
特点:
- Service Worker 完全异步,同步的 XHR 和 Web Storage 不能在 Service Worker 中使用
- 作用域为整个域名(多个页面共享),页面关闭仍可运行
- Service Worker 只能使用 HTTPS(更加严格的 worker)
- Service Worker 是客户端脚本,有下载,安装,激活的生命周期
场景:
- 后台数据同步:启动一个 Service Worker,即使用户没有访问页面,也可以更新缓存
- 响应推送:启动一个 Service Worker,向用户发送消息,通知新的内容可用
- 离线访问:Service Worker 可以缓存资源,使应用在没有网络连接时仍然可用
- 性能增强:预取用户可能需要的资源