音视频的前端知识
1 基本对象
1.1 DOCUMENT
定义:浏览器内置的全局对象(window.document),提供访问和操作 HTML 文档的接口。
核心功能:
- 查找和选择 HTML 元素(如 div、input)。
- 修改元素的内容、属性和样式。
- 创建新元素并添加到文档中。
- 监听用户事件(如点击、滚动)
1. 查找和选择HTML元素
// 通过ID获取元素(返回单个元素)
const elementById = document.getElementById('my-element');// 通过标签名获取元素(返回HTMLCollection)
const allDivs = document.getElementsByTagName('div');
console.log(allDivs[0]); // 访问第一个div// 通过类名获取元素(返回HTMLCollection)
const elementsByClass = document.getElementsByClassName('my-class');// 通过CSS选择器获取元素(返回单个元素)
const firstParagraph = document.querySelector('p');
const specificElement = document.querySelector('#container .item');// 通过CSS选择器获取所有匹配元素(返回NodeList)
const allParagraphs = document.querySelectorAll('p');
allParagraphs.forEach(p => {console.log(p.textContent);
});
一般而言,其实我感觉你只需要了解第一个就行 id(针对后端开发人员)
2.监听用户事件(如点击、滚动)
// 获取按钮元素
const button = document.getElementById('my-button');// 添加点击事件监听
button.addEventListener('click', (event) => {console.log('Button clicked!');console.log('Event target:', event.target); // 触发事件的元素console.log('Current element:', event.currentTarget); // 绑定事件的元素// 修改元素event.target.textContent = 'Clicked!';
});// 监听表单提交
const form = document.getElementById('my-form');
form.addEventListener('submit', (event) => {event.preventDefault(); // 阻止表单默认提交行为const inputValue = form.querySelector('input').value;console.log('Form submitted with value:', inputValue);
});// 监听键盘事件
document.addEventListener('keydown', (event) => {if (event.key === 'Enter') {console.log('Enter key pressed');}
});
脚本这种语言 是动态类型 所有是解释型语言,就是无需定于类型 这段代码涉及到一个内置对象 event 先别管这些
1.2 EVENT
event
是 JavaScript 中的内置事件对象,当浏览器检测到用户操作(如点击、键盘输入、滚动等)时,会自动创建该对象并传递给事件处理函数。它包含了事件的所有相关信息,是处理交互逻辑的核心工具。
一、event 对象的本质:浏览器自动生成的内置对象
-
创建时机
当事件(如click
、keydown
、mousemove
)触发时,浏览器会立即生成event
对象,并作为参数传入事件处理函数。例如:button.addEventListener('click', (event) => {// 这里的 event 就是浏览器自动创建的事件对象console.log(event); // 输出事件对象的所有属性和方法 });
-
内置特性
event
对象是浏览器内置的Event
类的实例,包含事件类型、目标元素、交互数据等核心信息,无需手动创建,直接使用即可。
二、event 对象的核心用途:封装事件相关的所有信息
- 标识事件源(目标元素)
event.target
:触发事件的具体元素(如被点击的按钮)。event.currentTarget
:绑定事件监听器的元素(常用于事件委托)。
// 给父元素绑定点击事件
document.getElementById('parent').addEventListener('click', (event) => {
console.log('event.target:', event.target.id); // 输出:child(实际被点击的按钮)
console.log('event.currentTarget:', event.currentTarget.id); // 输出:parent(绑定事件的父元素)
});
- 获取交互数据
- 鼠标事件:
event.clientX
(鼠标水平坐标)、event.offsetY
(相对于目标元素的垂直坐标)。 - 键盘事件:
event.key
(按下的键名,如'Enter'
)、event.keyCode
(键的编码)。 - 表单事件:
event.target.value
(输入框的值)、event.target.checked
(复选框的勾选状态)。
- 控制事件行为
event.preventDefault()
:阻止事件的默认行为(如阻止链接跳转、表单提交)。event.stopPropagation()
:阻止事件冒泡到父元素(中断事件传播)。
三、不同事件类型的 event 对象差异
event
对象的属性会根据事件类型动态变化,以下是常见场景:
事件类型 | 特有属性 | 示例场景 |
---|---|---|
点击事件(click) | event.clientX , event.clientY | 计算鼠标点击位置 |
键盘事件(keydown) | event.key , event.ctrlKey , event.shiftKey | 判断用户是否按下组合键 |
表单输入(input) | event.target.value | 实时获取输入框内容 |
鼠标移动(mousemove) | event.pageX , event.pageY | 实现拖拽效果、跟随鼠标的元素 |
窗口滚动(scroll) | event.target.scrollTop | 监听页面滚动位置,实现导航栏动态效果 |
音视频事件(play/pause) | event.target.duration (视频总时长) | 视频播放时更新进度条 |
四、在音视频开发中的典型应用
- 视频进度条拖拽
const progressBar = document.getElementById('progress-bar');
const video = document.getElementById('my-video');progressBar.addEventListener('click', (event) => {// 计算点击位置占进度条的比例const rect = progressBar.getBoundingClientRect();const clickX = event.clientX - rect.left;const percentage = (clickX / rect.width) * 100;// 转换为视频播放时间const seekTime = (percentage / 100) * video.duration;video.currentTime = seekTime;
});
- 键盘控制视频播放
document.addEventListener('keydown', (event) => {const video = document.getElementById('my-video');if (event.key === ' ' || event.key === 'Spacebar') {event.preventDefault(); // 阻止空格滚动页面的默认行为video.paused ? video.play() : video.pause();} else if (event.key === 'ArrowRight') {video.currentTime += 10; // 快进10秒} else if (event.key === 'ArrowLeft') {video.currentTime -= 5; // 快退5秒}
});
总结:event 对象的核心地位
作为浏览器内置的事件对象,event
是 JavaScript 交互逻辑的“信息枢纽”:它封装了用户操作的所有细节,让开发者能够精准响应交互行为。无论是简单的按钮点击,还是复杂的音视频手势控制,熟练掌握 event
的属性和方法都是实现高效交互的基础。建议通过实际项目练习(如自定义视频播放器的控制逻辑)来深入理解其应用场景!
1.3 navigator
navigator 是浏览器提供的一个全局对象,用于访问浏览器相关信息和功能。在音视频开发中,它是获取摄像头、麦克风等媒体设备的入口。
1.获取摄像头
// 请求访问摄像头和麦克风
async function accessCameraAndMicrophone() {try {// 配置约束条件(分辨率、帧率等)const constraints = {video: {width: { ideal: 1280 },height: { ideal: 720 },frameRate: { ideal: 30 }},audio: true};// 获取媒体流const stream = await navigator.mediaDevices.getUserMedia(constraints);//等待异步返回结果// 渲染到video元素const videoElement = document.getElementById('my-video');videoElement.srcObject = stream;return stream;} catch (error) {console.error('获取媒体失败:', error);// 处理错误(如权限被拒、设备不可用)}
}
NotFoundError:未找到指定设备。
NotAllowedError:用户拒绝授予权限。
OverconstrainedError:设备不支持请求的约束条件。
1.2 共享屏幕
async function startScreenShare() {try {// 配置约束条件const constraints = {video: true,audio: false // 可选:是否捕获系统音频};// 获取屏幕共享流const stream = await navigator.mediaDevices.getDisplayMedia(constraints);// 渲染到video元素const videoElement = document.getElementById('screen-share');videoElement.srcObject = stream;return stream;} catch (error) {console.error('屏幕共享失败:', error);}
}
2 WEBRTC
2.1 基本概念
WebRTC(Web Real-Time Communication)是现代浏览器提供的实时音视频通信 API,让网页能直接通过浏览器进行视频通话、语音聊天、文件共享等功能,无需安装插件。
获取媒体流
使用 navigator.mediaDevices.getUserMedia()
方法来获取摄像头和麦克风的媒体流。示例代码如下:
const constraints = { video: true, audio: true };
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {console.log('Got MediaStream:', stream);
})
.catch(error => {console.error('Error accessing media devices.', error);
});
constraints
对象用于指定需要获取的媒体类型,如视频和音频。成功获取到媒体流后,可以将其设置到 video
元素的 srcObject
属性上进行展示。
RTCPeerConnection
- 创建连接:通过
new RTCPeerConnection()
来创建一个点对点连接对象。 - 媒体协商:使用
createOffer()
和createAnswer()
方法来生成会话描述(Offer 和 Answer),并通过信令服务器在对等方之间交换这些描述,以协商双方支持的媒体格式、编码等参数。 - 添加媒体流:使用
addTrack()
方法将本地媒体流添加到RTCPeerConnection
对象中,以便将其发送到对等方。 - 监听事件:通过监听
icecandidate
事件来收集本地的 ICE 候选地址,并将其发送给对等方;监听track
事件来接收对等方发送的媒体流,以便在本地进行展示。
RTCSessionDescription
- 生成与设置:在媒体协商过程中,
RTCPeerConnection
对象会生成RTCSessionDescription
对象,包含会话的元数据,如媒体类型、编码格式等。使用setLocalDescription()
方法将本地生成的会话描述设置到RTCPeerConnection
对象中,使用setRemoteDescription()
方法来设置接收到的对等方的会话描述。
RTCDataChannel
- 创建通道:通过
RTCPeerConnection
对象的createDataChannel()
方法来创建一个数据通道,用于传输文本、二进制数据等。 - 发送与接收数据:使用
send()
方法发送数据,通过监听message
事件来接收对等方发送的数据。
2.2 实战
一、RTCPeerConnection:建立音视频连接的核心引擎
1. 核心作用
- 管理点对点连接,处理音视频数据传输
- 协调媒体协商(Offer/Answer)和网络穿透(ICE)
- 绑定媒体流(摄像头、麦克风)和数据通道
2. 关键用法(极简流程)
-
配置与创建连接
const pc = new RTCPeerConnection({iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
-
添加本地媒体流
const stream = await navigator.mediaDevices.getUserMedia({ video: true }); stream.getTracks().forEach(track => pc.addTrack(track, stream));
-
媒体协商(Offer/Answer)
// 发起方生成 Offer const offer = await pc.createOffer(); await pc.setLocalDescription(offer);// 接收方根据 Offer 生成 Answer await pc.setRemoteDescription(offer); const answer = await pc.createAnswer(); await pc.setLocalDescription(answer);
-
交换 ICE 候选者
// 发送本地候选者 pc.onicecandidate = (event) => {if (event.candidate) sendToServer(event.candidate); };// 接收远程候选者 await pc.addIceCandidate(remoteCandidate);
-
接收远程媒体流
pc.ontrack = (event) => {remoteVideo.srcObject = event.streams[0]; };
3. 流程图(简化版)
┌──────────────────────────────────────────────────────────┐
│ RTCPeerConnection │
├──────────────────────────────────────────────────────────┤
│ 1. 配置 STUN/TURN 服务器 │
│ 2. 创建连接实例 │
│ 3. 添加本地媒体流(摄像头/麦克风) │
│ 4. 生成 Offer 并发送给对方 │
│ 5. 接收对方的 Answer 并设置为远程描述 │
│ 6. 收集并交换 ICE 候选者(解决网络穿透) │
│ 7. 监听 ontrack 事件,显示对方视频流 │
└──────────────────────────────────────────────────────────┘
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>WebRTC 核心 API 示例</title>
</head>
<body><h1>WebRTC 核心 API 示例</h1><div><div><h3>本地视频</h3><video id="localVideo" autoplay muted></video></div><div><h3>远程视频</h3><video id="remoteVideo" autoplay></video></div></div><div><button id="startButton">开启摄像头</button><button id="callButton" disabled>发起通话</button><button id="hangupButton" disabled>挂断</button></div><div><h3>实时聊天</h3><div id="chatMessages"></div><div><input type="text" id="chatInput" placeholder="输入消息..."><button id="sendButton" disabled>发送</button></div></div><div><h3>日志</h3><div id="logMessages"></div></div><script>// DOM 元素const localVideo = document.getElementById('localVideo');const remoteVideo = document.getElementById('remoteVideo');const startButton = document.getElementById('startButton');const callButton = document.getElementById('callButton');const hangupButton = document.getElementById('hangupButton');const sendButton = document.getElementById('sendButton');const chatInput = document.getElementById('chatInput');const chatMessages = document.getElementById('chatMessages');const logMessages = document.getElementById('logMessages');// 状态变量let localStream;let peerConnection;let dataChannel;let isCaller = false; // 是否为呼叫方// 日志函数function log(message) {const logEntry = document.createElement('div');logEntry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;logMessages.appendChild(logEntry);logMessages.scrollTop = logMessages.scrollHeight;}// 聊天消息函数function addChatMessage(message, isLocal = false) {const messageDiv = document.createElement('div');messageDiv.textContent = isLocal ? `我: ${message}` : `对方: ${message}`;chatMessages.appendChild(messageDiv);chatMessages.scrollTop = chatMessages.scrollHeight;}// 1. 开启摄像头startButton.addEventListener('click', async () => {try {log('请求摄像头和麦克风权限...');localStream = await navigator.mediaDevices.getUserMedia({video: true,audio: true});// 显示本地视频localVideo.srcObject = localStream;log('摄像头和麦克风已开启');// 更新按钮状态startButton.disabled = true;callButton.disabled = false;} catch (error) {log(`获取媒体流失败: ${error.message}`);alert(`获取媒体流失败: ${error.message}`);}});// 2. 发起通话callButton.addEventListener('click', () => {isCaller = true;log('准备发起通话...');createPeerConnection();// 呼叫方创建数据通道createDataChannel();// 呼叫方创建 OffercreateOffer();// 更新按钮状态callButton.disabled = true;hangupButton.disabled = false;sendButton.disabled = false;});// 3. 创建 RTCPeerConnectionfunction createPeerConnection() {try {// 配置 STUN/TURN 服务器const configuration = {iceServers: [{ urls: 'stun:stun.l.google.com:19302' },{ urls: 'stun:stun1.l.google.com:19302' }]};peerConnection = new RTCPeerConnection(configuration);log('RTCPeerConnection 创建成功');// 添加本地媒体流localStream.getTracks().forEach(track => {peerConnection.addTrack(track, localStream);});log('已添加本地媒体流');// 监听远程媒体流peerConnection.ontrack = (event) => {log('接收到远程媒体流');remoteVideo.srcObject = event.streams[0];};// 监听 ICE 候选者peerConnection.onicecandidate = (event) => {if (event.candidate) {log('发送 ICE 候选者到对方');// 实际项目中通过信令服务器发送sendToSignalingServer({type: 'ice-candidate',candidate: event.candidate});}};// 监听连接状态peerConnection.onconnectionstatechange = () => {log(`连接状态: ${peerConnection.connectionState}`);if (peerConnection.connectionState === 'disconnected' || peerConnection.connectionState === 'failed') {log('连接断开,尝试重新连接...');hangup();}};// 接收方监听数据通道peerConnection.ondatachannel = (event) => {log('接收到数据通道');dataChannel = event.channel;setupDataChannel();};} catch (error) {log(`创建 RTCPeerConnection 失败: ${error.message}`);}}// 4. 创建 Offerasync function createOffer() {try {log('创建 Offer...');const offer = await peerConnection.createOffer();await peerConnection.setLocalDescription(offer);log('已设置本地 Offer');// 通过信令服务器发送 OffersendToSignalingServer({type: 'offer',sdp: offer.sdp});} catch (error) {log(`创建 Offer 失败: ${error.message}`);}}// 5. 创建数据通道function createDataChannel() {try {log('创建数据通道...');dataChannel = peerConnection.createDataChannel('chat', {ordered: true, // 消息按顺序到达maxRetransmits: 3 // 最大重传次数});setupDataChannel();} catch (error) {log(`创建数据通道失败: ${error.message}`);}}// 6. 设置数据通道事件监听function setupDataChannel() {dataChannel.onopen = () => {log('数据通道已打开');sendButton.disabled = false;};dataChannel.onclose = () => {log('数据通道已关闭');sendButton.disabled = true;};dataChannel.onerror = (error) => {log(`数据通道错误: ${error.message}`);};dataChannel.onmessage = (event) => {log(`收到数据: ${event.data}`);addChatMessage(event.data);};}// 7. 处理来自信令服务器的消息(模拟)function handleSignalingMessage(message) {switch (message.type) {case 'offer':log('收到 Offer');// 如果是接收方,创建 RTCPeerConnectionif (!peerConnection) {createPeerConnection();// 更新按钮状态callButton.disabled = true;hangupButton.disabled = false;}// 设置远程 OfferpeerConnection.setRemoteDescription(new RTCSessionDescription(message)).then(() => {log('已设置远程 Offer');// 创建 Answerreturn peerConnection.createAnswer();}).then(answer => {log('创建 Answer...');return peerConnection.setLocalDescription(answer);}).then(() => {log('已设置本地 Answer');// 发送 Answer 到呼叫方sendToSignalingServer({type: 'answer',sdp: peerConnection.localDescription.sdp});}).catch(error => {log(`处理 Offer 失败: ${error.message}`);});break;case 'answer':log('收到 Answer');// 设置远程 AnswerpeerConnection.setRemoteDescription(new RTCSessionDescription(message)).then(() => {log('已设置远程 Answer,连接已建立');}).catch(error => {log(`处理 Answer 失败: ${error.message}`);});break;case 'ice-candidate':log('收到 ICE 候选者');// 添加 ICE 候选者peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate)).catch(error => {log(`添加 ICE 候选者失败: ${error.message}`);});break;}}// 8. 模拟信令服务器通信(实际需替换为WebSocket)function sendToSignalingServer(message) {// 实际项目中,这里应该使用WebSocket连接到信令服务器// 此处仅为演示,模拟消息发送log(`发送到信令服务器: ${message.type}`);// 模拟接收方处理(实际需服务器转发)setTimeout(() => {if (window.receiver && window.receiver.handleSignalingMessage) {window.receiver.handleSignalingMessage(message);} else {log('提示: 实际项目中应通过信令服务器转发消息');}}, 500);}// 9. 发送聊天消息sendButton.addEventListener('click', () => {const message = chatInput.value.trim();if (message && dataChannel && dataChannel.readyState === 'open') {dataChannel.send(message);addChatMessage(message, true);chatInput.value = '';}});// 10. 挂断通话hangupButton.addEventListener('click', hangup);function hangup() {log('正在挂断通话...');// 关闭数据通道if (dataChannel && dataChannel.readyState !== 'closed') {dataChannel.close();}// 关闭 RTCPeerConnectionif (peerConnection) {peerConnection.close();peerConnection = null;}// 停止媒体流if (localStream) {localStream.getTracks().forEach(track => track.stop());localStream = null;localVideo.srcObject = null;remoteVideo.srcObject = null;}// 更新按钮状态callButton.disabled = false;hangupButton.disabled = true;sendButton.disabled = true;chatInput.value = '';log('通话已挂断');}// 11. 监听键盘回车发送消息chatInput.addEventListener('keypress', (e) => {if (e.key === 'Enter') {sendButton.click();}});// 12. 模拟接收方(仅演示,实际需服务器支持)window.receiver = {handleSignalingMessage: handleSignalingMessage};</script>
</body>
</html>
3web socket
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,非常适合实时应用(如聊天、游戏、数据推送等)。下面是 JavaScript 中使用 WebSocket 的基础模板:
1. 基本连接模板
// 创建 WebSocket 连接
const socket = new WebSocket('ws://example.com/socket');// 监听连接事件
socket.onopen = () => {console.log('WebSocket 连接已打开');// 发送消息socket.send('Hello, server!');
};// 监听消息事件
socket.onmessage = (event) => {console.log('收到消息:', event.data);
};// 监听错误事件
socket.onerror = (error) => {console.error('WebSocket 错误:', error);
};// 监听关闭事件
socket.onclose = (event) => {console.log('WebSocket 连接已关闭', event);// 尝试重连(可选)if (event.code !== 1000) { // 非正常关闭reconnect();}
};// 重连函数(可选)
function reconnect() {setTimeout(() => {console.log('尝试重新连接...');// 递归调用重连函数// 实际应用中可能需要指数退避策略}, 3000);
}// 关闭连接
function closeConnection() {socket.close();
}
2. JSON 消息处理模板
const socket = new WebSocket('ws://example.com/json-socket');// 发送 JSON 消息
function sendJsonMessage(type, data) {const message = JSON.stringify({ type, data });socket.send(message);
}socket.onmessage = (event) => {try {const data = JSON.parse(event.data);// 根据消息类型处理switch (data.type) {case 'chat':handleChatMessage(data.content);break;case 'notification':showNotification(data.message);break;default:console.log('未知消息类型:', data);}} catch (error) {console.error('解析 JSON 消息失败:', error);}
};
解析:
WebSocket JSON 消息发送函数解析
1. 函数结构与参数
function sendJsonMessage(type, data) {const message = JSON.stringify({ type, data });socket.send(message);
}
- 参数:
type
:消息类型(如'chat'
、'notification'
)data
:消息内容(可以是任何可序列化的数据)
- 返回值:无(异步发送)
2. 消息构建过程
-
创建对象:
{ type, data }
这是 ES6 的简写语法,等同于:
{ type: type, data: data }
-
序列化为 JSON 字符串:
JSON.stringify({ type, data })
例如,当调用
sendJsonMessage('chat', 'Hello')
时,生成的字符串是:{"type":"chat","data":"Hello"}
-
通过 WebSocket 发送:
socket.send(message);
这里的
socket
是已连接的 WebSocket 实例。
3. 完整使用示例
// 初始化 WebSocket
const socket = new WebSocket('ws://example.com/socket');// 封装的 JSON 消息发送函数
function sendJsonMessage(type, data) {const message = JSON.stringify({ type, data });/*用于将 JavaScript 对象或值转换为 JSON 格式的字符串。这个过程称为 序列化(Serialization)*/socket.send(message);
}// 连接建立后发送消息
socket.onopen = () => {// 发送聊天消息sendJsonMessage('chat', {content: 'Hello, everyone!',userId: 12345});// 发送命令sendJsonMessage('command', {action: 'join_room',roomId: 'general'});
};// 接收并解析消息
socket.onmessage = (event) => {try {const data = JSON.parse(event.data);// 根据 type 处理不同类型的消息switch (data.type) {case 'chat':console.log('收到聊天消息:', data.data.content);break;case 'notification':console.log('收到通知:', data.data.message);break;default:console.log('未知消息类型:', data);}} catch (error) {console.error('解析消息失败:', error);}
};
3. 心跳检测模板
const socket = new WebSocket('ws://example.com/socket');
let heartbeatInterval;
const HEARTBEAT_INTERVAL = 30000; // 30秒// 发送心跳
function sendHeartbeat() {if (socket.readyState === WebSocket.OPEN) {socket.send('ping');}
}// 启动心跳
function startHeartbeat() {heartbeatInterval = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL);
}// 停止心跳
function stopHeartbeat() {clearInterval(heartbeatInterval);
}socket.onopen = () => {console.log('连接已打开,启动心跳');startHeartbeat();
};socket.onclose = () => {console.log('连接已关闭,停止心跳');stopHeartbeat();
};socket.onmessage = (event) => {if (event.data === 'pong') {// 收到服务器响应的心跳return;}// 处理其他消息
};
4. 事件驱动的 WebSocket 封装
class WebSocketClient {constructor(url) {this.url = url;this.socket = null;this.eventHandlers = {};this.reconnectAttempts = 0;this.maxReconnectAttempts = 10;this.reconnectInterval = 3000; // 3秒}// 连接 WebSocketconnect() {this.socket = new WebSocket(this.url);this.socket.onopen = () => {this.reconnectAttempts = 0;this.emit('open');};this.socket.onmessage = (event) => {this.emit('message', event.data);};this.socket.onerror = (error) => {this.emit('error', error);};this.socket.onclose = (event) => {this.emit('close', event);this.reconnect();};}// 重连逻辑reconnect() {if (this.reconnectAttempts < this.maxReconnectAttempts) {this.reconnectAttempts++;setTimeout(() => {console.log(`尝试重新连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);this.connect();}, this.reconnectInterval);} else {this.emit('reconnect_failed');}}// 发送消息send(data) {if (this.socket && this.socket.readyState === WebSocket.OPEN) {this.socket.send(data);} else {this.emit('send_failed', data);}}// 关闭连接close() {if (this.socket) {this.socket.close();}}// 事件监听on(event, handler) {if (!this.eventHandlers[event]) {this.eventHandlers[event] = [];}this.eventHandlers[event].push(handler);}// 事件触发emit(event, ...args) {if (this.eventHandlers[event]) {this.eventHandlers[event].forEach(handler => handler(...args));}}
}// 使用示例
const client = new WebSocketClient('ws://example.com/socket');client.on('open', () => {console.log('连接已打开');client.send('Hello!');
});client.on('message', (data) => {console.log('收到消息:', data);
});client.on('error', (error) => {console.error('WebSocket 错误:', error);
});client.on('close', (event) => {console.log('连接已关闭', event);
});client.connect();