音视频的前端知识

发布于:2025-06-14 ⋅ 阅读:(21) ⋅ 点赞:(0)

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 对象的本质:浏览器自动生成的内置对象

  1. 创建时机
    当事件(如 clickkeydownmousemove)触发时,浏览器会立即生成 event 对象,并作为参数传入事件处理函数。例如:

    button.addEventListener('click', (event) => {
      // 这里的 event 就是浏览器自动创建的事件对象
      console.log(event); // 输出事件对象的所有属性和方法
    });
    
  2. 内置特性
    event 对象是浏览器内置的 Event 类的实例,包含事件类型、目标元素、交互数据等核心信息,无需手动创建,直接使用即可。

二、event 对象的核心用途:封装事件相关的所有信息

  1. 标识事件源(目标元素)
  • 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(绑定事件的父元素)
});
  1. 获取交互数据
  • 鼠标事件event.clientX(鼠标水平坐标)、event.offsetY(相对于目标元素的垂直坐标)。
  • 键盘事件event.key(按下的键名,如 'Enter')、event.keyCode(键的编码)。
  • 表单事件event.target.value(输入框的值)、event.target.checked(复选框的勾选状态)。
  1. 控制事件行为
  • 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(视频总时长) 视频播放时更新进度条

四、在音视频开发中的典型应用

  1. 视频进度条拖拽
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;
});
  1. 键盘控制视频播放
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. 关键用法(极简流程)

  1. 配置与创建连接

    const pc = new RTCPeerConnection({
      iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
    });
    
  2. 添加本地媒体流

    const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    stream.getTracks().forEach(track => pc.addTrack(track, stream));
    
  3. 媒体协商(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);
    
  4. 交换 ICE 候选者

    // 发送本地候选者
    pc.onicecandidate = (event) => {
      if (event.candidate) sendToServer(event.candidate);
    };
    
    // 接收远程候选者
    await pc.addIceCandidate(remoteCandidate);
    
  5. 接收远程媒体流

    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();
      
      // 呼叫方创建 Offer
      createOffer();
      
      // 更新按钮状态
      callButton.disabled = true;
      hangupButton.disabled = false;
      sendButton.disabled = false;
    });
    
    // 3. 创建 RTCPeerConnection
    function 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. 创建 Offer
    async function createOffer() {
      try {
        log('创建 Offer...');
        const offer = await peerConnection.createOffer();
        await peerConnection.setLocalDescription(offer);
        log('已设置本地 Offer');
        
        // 通过信令服务器发送 Offer
        sendToSignalingServer({
          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');
          // 如果是接收方,创建 RTCPeerConnection
          if (!peerConnection) {
            createPeerConnection();
            // 更新按钮状态
            callButton.disabled = true;
            hangupButton.disabled = false;
          }
          
          // 设置远程 Offer
          peerConnection.setRemoteDescription(new RTCSessionDescription(message))
            .then(() => {
              log('已设置远程 Offer');
              // 创建 Answer
              return 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');
          // 设置远程 Answer
          peerConnection.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();
      }
      
      // 关闭 RTCPeerConnection
      if (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. 消息构建过程

  1. 创建对象

    { type, data }
    

    这是 ES6 的简写语法,等同于:

    { type: type, data: data }
    
  2. 序列化为 JSON 字符串

    JSON.stringify({ type, data })
    

    例如,当调用 sendJsonMessage('chat', 'Hello') 时,生成的字符串是:

    {"type":"chat","data":"Hello"}
    
  3. 通过 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秒
  }

  // 连接 WebSocket
  connect() {
    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();