UPDATE:经过长时间的jslint会话后,问题神秘地消失了,所以我不得不得出结论,这是我代码中的某个愚蠢错误,通过删除一些看似无害的拼写错误来修复。
初始问题
我有一个页面,它通过jQuery.post
调用加载大量(随机可变)AJAX对象。
所以我会打很多电话,比如
/entry/get/orange
/entry/get/red
/entry/get/yellow
/entry/get/red <--- duplicate!
/entry/get/red <--- again!
/entry/get/yellow
/entry/get/red <--- dude, what's wrong with you?
...
当以这种方式运行时,除了效率非常低和不必要地访问服务器之外,一切都正常工作。
解决方案尝试
所以我通过Promise切换到缓存,就像这样:
// I have a global colorCache variable
function retrieve(color, callback) {
console.log("retrieving " + color);
var found = -1;
// Each entry in colorCache is an object with some other values
for (var i in colorCache) {
if (colorCache[i].color === color) {
found = i;
break;
}
}
if (found < 0) {
console.log("effecting call for " + color);
var url = "blah/blah/blah/" + color;
var ppar = {
foo: "bar",
answer: 42
};
/* push returns the new length of colorCache */
found = colorCache.push({
color : color,
promise : $.post(url, ppar),
ts : new Date().getTime()
}) - 1;
} else {
console.log("cache hit for " + color);
}
// now found is valid. Return the Promise too, for appending.
return colorCache[found].success(callback);
}
这样,如果相关的.post
已经发出,则使用相同的数据再次调用回调,无论调用本身是否已经终止或仍在进行中。
再说一遍,一切都正常显然。
现在我有另一个问题
有了这个新版本,
- 页面加载
- 所有对象都已加载(比以前快得多,正如预期的那样)
- 页面停止,一切静止。。。没有动画,没有时钟,什么都没有
- 。。。但Process Explorer现在显示Firefox不断消耗26-30%的CPU。它将继续这样做,直到并且除非我关闭加载页面的选项卡。此外,Firefox的内存消耗稳步上升。我看到它在不到一个小时的时间里从800M上升到1.7GB,这不是完全有规律的,但有明显的上升趋势
这里可能有什么问题?
重要提示:Stack Overflow上有几个"浏览器泄漏"问答,但它们都涉及重复的jQuery调用,这些调用以某种方式分配对象或迭代。在这里,一旦我运行了大约50个jQuery调用,就没有其他事情发生了
我(到目前为止)尝试了什么
- 我已经检查了Firebug:没有控制台警告或错误,没有指示日志(我调用的所有函数都有一个),没有网络活动(从Apache日志服务器端进行了双重检查)
- 我在记忆中看过Firefox的进程;主线程正在运行,但我不擅长解释Process Explorer的线程信息——它显示了一些关于"forInIterator"的东西,告诉我的很少
- 除了CPU之外,似乎没有任何活动正在进行(网络、Windows注册表读取/写入)
- 我在谷歌上搜索了很多,发现了一些可能相关的东西(但可能不是)我还没有尝试过这个建议,但即使我看不出它有什么帮助,我也会很快尝试,而且无论如何,即使类似的可能已经在jQuery中出现,我也不会使用
when.js
,因为:一个潜在的解决方法是确保你的promise回调和errbacks返回了一些东西(而不是递延的美元) - 按照Jaromanda X的建议,用Promise.resolve封装.post调用,并在函数内移动缓存对象,以避免污染全局范围(这始终是一种很好的做法)
- 直接返回Promise对象,并通过附加一个external.then调用回调
- 经过铬测试。相同的结果(在某些方面令人放心),只是CPU负载恒定在17%
- 关闭选项卡会使CPU负载消失
TL;DR问题根本与缓存无关。缓存,或者更确切地说:缓存允许的更高的调用周转率,似乎会在jQuery/bootstrapSwitch关系中的其他地方引发误解。也有可能是我使用bootstrapSwitch不正确。
调试和发现
由于没有运行XHR请求,CPU问题一定是由代码显式循环或隐式循环(例如setInterval)引起的。
检测浏览器定时器功能
window.timers = {};
originalSetTimeout = window.setTimeout;
originalSetInterval = window.setInterval;
window.setTimeout = function(fu, t) {
var id = originalSetTimeout(function() {
console.log(id+" has timed out");
delete window.timers[id]; // do not track popped timers
fu();
}, t);
window.timers[id] = {id:id, type: 'timeout', setAt: new Date(), timeout: t };
console.log(id+" has been set to pop in "+t+"ms");
}
window.setInterval = function(fu, t) {
var id = originalSetInterval(function() {
window.timers[id].fired++;
fu();
}, t);
window.timers[id] = {id:id, type: 'interval', setAt: new Date(), timeout: t, fired: 0 };
console.log(id+" has been set to pop every "+t+"ms");
}
显示有几个计时器连续运行。
调查计时器
代码是"严格的",但一旦知道了特定的计时器,我就可以在setInterval中抛出一个Exception,并能够检查调用堆栈。
因此,罪魁祸首是这个代码(删除它会使问题消失):
.bootstrapSwitch({
onSwitchChange: function(e, state) {
...
}
})
结论
问题的原因是我在生成的页面中有很多复选框:它们用bootstrapSwitch进行了美化,但它们一开始是隐藏的。在这种情况下,bootstrapSwitch启动一个计时器,等待复选框在丰富其DOM之前被呈现。显然,这是(其中一个?)这样做的功能:
return initInterval = window.setInterval((function(_this) {
return function() {
if (_this.$wrapper.is(":visible")) {
init();
return window.clearInterval(initInterval);
}
};
})(this), 50);
通常情况下,这些功能不会显著地加载CPU。但由于不清楚的原因,短时间内大量(40+)的AJAX请求,其中一些请求调用bootstrapSwitch,使上述定时器产生了更高的负载。
强制复选框开关一个接一个地显示,然后立即隐藏它们,也有一定的效果:保留了两个计时器,CPU负载低于5%,但所有其他计时器都正确终止。不过,复选框闪烁很尴尬。
最后,我编写了一个定时器函数,它将延迟bootstrapSwitch,直到相关的复选框确实可见为止。一旦它们被调用,bootstrapSwitch就会被调用,但由于它们是可见的,因此不会创建额外的计时器。这样,无论定时器内部发生什么,代码都不会被触发。