比较
1 uni.$emit是同步执行的,当有大量监听器时可能阻塞主线程。其他方案都是异步执行(setTimeout或Promise),性能更好。
2 uni.$emit的内存管理最方便(页面卸载时自动清理),其他方案都需要手动清理(需手动取消注册/订阅/监听),但提供了更精细的控制。
3 uni.$emit耦合度最高,其他方案都能更好地实现解耦。
4 uni.$emit使用最简单,其他方案需要额外实现但提供了更多优势。
综合建议
1 简单项目/快速开发:优先使用 uni.$emit,虽然有些缺点但开发效率最高。
2 中型项目/长期维护:推荐使用事件总线方案,提供了良好的平衡。
3 大型复杂项目:使用观察者模式,可以获得最好的架构和扩展性。
4 需要精细控制事件:避免使用 uni.$emit,选择回调注册或观察者模式。
5 性能敏感场景:避免 uni.$emit的同步特性,选择异步方案。
1 方案 2 优点 3 缺点 4 适用场景
回调函数注册(setTimeout)|简单直接,无需额外依赖|需要手动管理回调函数|简单项目,事件类型较少
观察者模式(setTimeout)|解耦更好,可扩展性强|需要实现观察者类|中大型项目,需要良好架构
事件总线(Promise)|功能全面,类似Vue事件系统|需要实现事件总线类|需要类似Vue事件系统的项目
使用回调函数注册机制
// 在socket.js中
export default {
// ...其他代码...
eventListeners: {}, // 存储事件监听器
// 注册事件监听器
on(eventName, callback) {
if (!this.eventListeners[eventName]) {
this.eventListeners[eventName] = [];
}
this.eventListeners[eventName].push(callback);
},
// 取消事件监听
off(eventName, callback) {
if (this.eventListeners[eventName]) {
const index = this.eventListeners[eventName].indexOf(callback);
if (index > -1) {
this.eventListeners[eventName].splice(index, 1);
}
}
},
// 修改_setUpListeners方法
_setUpListeners() {
if (!this.socketTask) return;
this.socketTask.onMessage((message) => {
let res;
try {
res = socketStrToJson(message.data);
} catch (e) {
console.error('消息解析失败:', e);
return;
}
// 分发事件
this.dispatchEvent(res.event, res);
});
},
// 事件分发方法
dispatchEvent(eventName, data) {
if (this.eventListeners[eventName]) {
// 使用setTimeout确保异步执行,避免阻塞
setTimeout(() => {
this.eventListeners[eventName].forEach(callback => {
try {
callback(data);
} catch (e) {
console.error(`事件${eventName}处理出错:`, e);
}
});
}, 0);
}
}
};
调用:
// 在页面中
import socket from '@/socket.js';
export default {
onLoad() {
// 注册事件监听
socket.on('eventName', this.handleEvent);
},
onUnload() {
// 取消事件监听
socket.off('eventName', this.handleEvent);
},
methods: {
handleEvent(data) {
// 处理事件
}
}
}
方法三 使用观察者模式-订阅 Observer
// observer.js
export default class Observer {
constructor() {
this.observers = {};
}
subscribe(event, callback) {
if (!this.observers[event]) {
this.observers[event] = [];
}
this.observers[event].push(callback);
}
unsubscribe(event, callback) {
if (this.observers[event]) {
this.observers[event] = this.observers[event].filter(
observer => observer !== callback
);
}
}
notify(event, data) {
if (this.observers[event]) {
// 异步执行避免阻塞
setTimeout(() => {
this.observers[event].forEach(observer => {
try {
observer(data);
} catch (e) {
console.error(`Observer for ${event} error:`, e);
}
});
}, 0);
}
}
}
// socket.js
import Observer from './observer.js';
export default {
observer: new Observer(),
// ...其他代码...
_setUpListeners() {
if (!this.socketTask) return;
this.socketTask.onMessage((message) => {
let res;
try {
res = socketStrToJson(message.data);
} catch (e) {
console.error('消息解析失败:', e);
return;
}
// 通知观察者
this.observer.notify(res.event, res);
});
}
};
// 在页面中
import socket from '@/socket.js';
export default {
onLoad() {
// 订阅事件
socket.observer.subscribe('eventName', this.handleEvent);
},
onUnload() {
// 取消订阅
socket.observer.unsubscribe('eventName', this.handleEvent);
},
methods: {
handleEvent(data) {
// 处理事件
}
}
}
方法二 使用事件总线(Event Bus)event-bus.js
// 使用事件总线(Event Bus)event-bus.js
export default class EventBus {
constructor() {
this.events = {};
}
$on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
$off(event, callback) {
if (this.events[event]) {
if (callback) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
} else {
delete this.events[event];
}
}
}
$emit(event, ...args) {
if (this.events[event]) {
// 使用微任务确保异步执行
Promise.resolve().then(() => {
this.events[event].forEach(callback => {
try {
callback(...args);
} catch (e) {
console.error(`EventBus ${event} handler error:`, e);
}
});
});
}
}
}
// socket.js
import EventBus from './event-bus.js';
export default {
eventBus: new EventBus(),
// ...其他代码...
_setUpListeners() {
if (!this.socketTask) return;
this.socketTask.onMessage((message) => {
let res;
try {
res = socketStrToJson(message.data);
} catch (e) {
console.error('消息解析失败:', e);
return;
}
// 触发事件
this.eventBus.$emit(res.event, res);
});
}
};
调用:
// 在页面中
import socket from '@/socket.js';
export default {
onLoad() {
// 监听事件
socket.eventBus.$on('eventName', this.handleEvent);
},
onUnload() {
// 取消监听
socket.eventBus.$off('eventName', this.handleEvent);
},
methods: {
handleEvent(data) {
// 处理事件
}
}
}
方法一 uni.$emit
// 在页面中
import socket from '@/socket.js';
export default {
created() {
// 监听
uni.$on(this.incidentName,this.onMessage)
},
beforeDestroy() {
uni.$off(this.incidentName) // 销毁监听,释放内存
},
methods: {
onMessage(data) {
// 处理事件
}
}
}
socket.js
// socket.js
import EventBus from './event-bus.js';
import {
socketFormat,
socketStrToJson
} from '@/utils/util.js'
import config from '../config';
import Vue from 'vue'
import worker from '../minxins/worker';
// #ifdef WEB
import {
MessageBox
} from 'element-ui';
import modal from '@/plugins/web-modal.js'
// #endif
// #ifndef WEB
import _modal from '@/plugins/modal.js'
// #endif
export default {
eventBus: new EventBus(),
socketTask: null, // WebSocket 连接实例
isConnected: false, // 连接状态
heartbeatInterval: null, // 心跳定时器
heartbeatIntervalTime: 30000, // 心跳间隔时间(30 秒)
heartbeatTimeout: 5000, // 心跳响应超时时间(5 秒)
maxRetryCount: 3, // 最大重连次数
retryCount: 0, // 当前重连次数
isSend: false,
messageTask: [],
maxMessageQueueSize: 100, // 消息队列最大长度
requestTime: 0,
responseTime: 0,
reconnectDelay: 3000, // 重连延迟时间(3秒)
// 初始化 WebSocket 连接
init(workerObj) {
const _this = this;
// 清理现有连接
this.cleanup();
if (!config.baseUrl) {
console.error('服务器地址未配置');
return;
}
this.socketTask = uni.connectSocket({
url: config.baseUrl,
success: () => {
console.log('connectSocket success');
},
fail: (err) => {
console.error('WebSocket 连接失败:', err);
this.handleConnectionFailure(err);
}
});
this.socketTask.onOpen(() => {
console.log('WebSocket 连接成功', this.socketTask);
this.handleConnectionSuccess();
});
this.socketTask.onClose(() => {
console.log('WebSocket 已关闭');
this.handleConnectionClose();
});
this.socketTask.onError((err) => {
console.error('WebSocket 错误:', err);
this.handleConnectionFailure(err);
});
},
// 清理现有连接和状态
cleanup() {
if (this.socketTask) {
this.socketTask.close();
this.socketTask = null;
}
this.stopHeartbeat();
this.isConnected = false;
this.messageTask = [];
this.isSend = false;
},
// 处理连接成功
handleConnectionSuccess() {
this.isConnected = true;
this.retryCount = 0;
this._setUpListeners();
this.startHeartbeat();
// 连接成功后处理积压的消息
if (this.messageTask.length > 0) {
this.processMessage();
}
},
// 处理连接关闭
handleConnectionClose() {
this.isConnected = false;
this.socketTask = null;
this.stopHeartbeat();
// 非主动关闭时尝试重连
if (this.retryCount < this.maxRetryCount) {
this.retryConnection();
}
},
// 处理连接失败
handleConnectionFailure(err) {
this.isConnected = false;
this.stopHeartbeat();
this.retryConnection();
},
// 设置接收消息的监听器
_setUpListeners() {
if (!this.socketTask) return;
this.socketTask.onMessage((message) => {
console.log(`%c服务器响应数据:`, "background: blue; color: white;", message.data);
let res;
try {
res = socketStrToJson(message.data);
} catch (e) {
console.error('消息解析失败:', e);
return;
}
if (res && res.data && res.data.code !== 200) {
this.showError(res.data.message);
}
console.log('事件名称:', res.event);
console.log('响应结果', res);
// 触发全局事件 方法一
// uni.$emit(res.event + "", res);
// 触发事件 方法二
this.eventBus.$emit(res.event, res);
// 更新最后响应时间
this.responseTime = Date.now();
if (res.data === 'pong') {
console.log('收到心跳响应');
}
});
},
// 显示错误消息
showError(message) {
// #ifdef WEB
modal.msgError(message);
// #endif
// #ifndef WEB
_modal.showToast(message);
// #endif
},
/**
* 发送消息
* @param {Object} msg 消息对象
*/
sendMessage(msg) {
if (!msg) return;
// 检查消息队列长度
if (this.messageTask.length >= this.maxMessageQueueSize) {
console.warn('消息队列已满,丢弃最早的消息');
this.messageTask.shift();
}
this.messageTask.push(msg);
if (!this.isSend && this.isConnected) {
this.processMessage();
}
},
// 处理消息队列
processMessage() {
if (this.messageTask.length === 0) {
this.isSend = false;
return;
}
this.isSend = true;
const msg = this.messageTask[0]; // 先不移除,等发送成功再移除
if (!this.socketTask || this.socketTask.readyState !== 1) {
console.warn('WebSocket未连接,暂停发送消息');
this.isSend = false;
return;
}
this.socketTask.send({
data: msg,
success: () => {
console.log('消息发送成功:', msg);
this.messageTask.shift(); // 发送成功后才移除
setTimeout(() => this.processMessage(), 100);
},
fail: (err) => {
console.error('消息发送失败:', err);
// 发送失败时保持消息在队列中,稍后重试
setTimeout(() => this.processMessage(), 1000);
}
});
},
/**
* 检查 WebSocket 是否已连接
* @param {Function} callback 回调函数
*/
checkSocketReady(callback) {
if (!callback) return;
if (this.socketTask && this.socketTask.readyState === 1) {
callback();
} else {
const listener = () => {
if (this.socketTask && this.socketTask.readyState === 1) {
this.socketTask.offOpen(listener);
callback();
}
};
this.socketTask?.onOpen(listener);
}
},
// 关闭连接
close() {
this.retryCount = this.maxRetryCount; // 阻止自动重连
if (this.socketTask) {
this.socketTask.close({
success: () => {
console.log('WebSocket 连接关闭');
this.cleanup();
},
fail: (err) => {
console.error('WebSocket 关闭失败:', err);
this.cleanup();
}
});
}
},
// 启动心跳检测
startHeartbeat() {
this.stopHeartbeat(); // 先停止现有的心跳
this.heartbeatInterval = setInterval(() => {
if (!this.socketTask || this.socketTask.readyState !== 1) {
console.warn('WebSocket 未连接,停止心跳检测');
this.stopHeartbeat();
return;
}
// 检查上次心跳响应是否超时
const now = Date.now();
if (now - this.responseTime > this.heartbeatTimeout) {
console.warn('心跳响应超时,尝试重连');
this.handleConnectionFailure(new Error('心跳响应超时'));
return;
}
console.log('发送心跳: ping');
this.requestTime = now;
const msg = socketFormat('Ping', 'pong', {});
this.sendMessage(msg);
}, this.heartbeatIntervalTime);
},
// 停止心跳检测
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
console.log('心跳检测已停止');
}
},
// 重连逻辑
retryConnection() {
if (this.retryCount >= this.maxRetryCount) {
console.error('已达到最大重连次数,停止重连');
this.showReconnectDialog();
return;
}
this.retryCount++;
console.log(`尝试重连,第 ${this.retryCount} 次`);
// 使用指数退避算法增加重连间隔
const delay = Math.min(
this.reconnectDelay * Math.pow(2, this.retryCount - 1),
30000 // 最大30秒
);
setTimeout(() => {
this.init();
}, delay);
},
// 显示重连对话框
showReconnectDialog() {
const _this = this;
// #ifdef WEB
MessageBox.alert("WebSocket 连接失败,请点击确认进行重连", "系统提示", {
type: 'error',
showCancelButton: true,
callback: function(action) {
if (action === 'confirm') {
_this.retryCount = 0; // 重置重连计数
_this.init();
}
}
});
// #endif
// #ifndef WEB
_modal.confirm('连接中断,请点击确认进行重连', '系统提示').then(action => {
if (action) {
_this.retryCount = 0; // 重置重连计数
_this.init();
}
});
// #endif
}
};