在 Qt 与 JavaScript 结合开发时,尤其是通过 QWebChannel 桥接大量方法时,经常会遇到一个棘手的问题:单个方法调用正常,但并发或连续调用多个方法后,控制台报错 Uncaught TypeError: channel.execCallbacks[message.id] is not a function。这通常伴随着关于 Synchronous XMLHttpRequest 的警告。
问题根源分析
经过排查,核心原因在于 QWebChannel 实例的管理方式。如果在每个业务方法中都重新实例化 new QWebChannel(...),会导致底层的通道对象被反复创建和销毁。当新的通道实例初始化时,它可能会重置内部的回调映射表(execCallbacks),而之前异步请求挂起的句柄还没来得及处理就被'干掉'了,从而引发类型错误。
这就好比每次打电话都重新注册一次电话局,之前的通话记录自然也就丢了。因此,解决思路应当借鉴数据库连接池的模式:全局单例,统一初始化。
解决方案:全局桥接对象
我们需要将 QWebChannel 和桥接对象声明为全局变量,确保整个页面生命周期内只存在一个通道实例。所有业务逻辑复用这个实例。
1. 全局变量定义
首先,在全局作用域预留两个变量,分别用于存储通道和桥接对象。
var __jsqtBridge = null; // 全局变量 jsqtBridge
var __QWebChannel = null; // 全局变量 QWebChannel
2. 页面加载时初始化
利用 jQuery 的 ready 事件,在 DOM 就绪后立即建立连接。这样后续的所有方法都能直接获取到已初始化的对象。
$(document).ready(function() {
$.init__jsqtBridge();
});
3. 业务方法中的保护机制
在具体调用业务方法前,先检查全局变量是否有效。如果为空,则触发初始化流程;如果已存在,则直接使用。
if (__jsqtBridge == null || __jsqtBridge == undefined || __jsqtBridge == "") {
$.init__jsqtBridge();
return false;
}
完整代码示例
下面是一个整合后的实现方案,包含了初始化逻辑、测试入口以及典型的数据加载方法。注意观察 $.extend 内部的结构,确保所有方法都指向同一个 __jsqtBridge。
/* 说明:调用 qwebchannel.js 的应用 js qwebchanneltest.js */
var __jsqtBridge = null; // 全局变量 jsqtBridge
__QWebChannel = ;
$.({
: () {
{
__QWebChannel = (qt., () {
__jsqtBridge = channel..;
. = __jsqtBridge;
.();
});
} (e) {
.(, e);
}
},
: () {
();
(__jsqtBridge != && __jsqtBridge != && __jsqtBridge != ) {
();
..();
} {
();
__QWebChannel = (qt., () {
__jsqtBridge = channel..;
. = __jsqtBridge;
();
__jsqtBridge.();
});
}
},
: () {
(__jsqtBridge == || __jsqtBridge == || __jsqtBridge == ) {
$.();
;
}
__jsqtBridge.(() {
.(content);
(content);
});
},
: () {
(__jsqtBridge == || __jsqtBridge == || __jsqtBridge == ) {
$.();
;
}
__jsqtBridge.(() {
.(content);
(content);
});
},
: () {
(__jsqtBridge == || __jsqtBridge == || __jsqtBridge == ) {
$.();
;
}
__jsqtBridge.(() {
.(content);
(content);
});
}
});
$().(() {
$.();
});

