Javascript 关闭窗口会中断事件循环假设
我遇到了一个小麻烦,后来变成了一个大问题 问题1:在Internet Explorer中,当您关闭一个窗口(通过Javascript 关闭窗口会中断事件循环假设,javascript,internet-explorer,internet-explorer-9,Javascript,Internet Explorer,Internet Explorer 9,我遇到了一个小麻烦,后来变成了一个大问题 问题1:在Internet Explorer中,当您关闭一个窗口(通过窗口打开)时,所有者文档将随之消失 这意味着对DOM的任何调用,如appendChild或createElement,都将失败,并导致SCRIPT70:权限被拒绝或SCRIPT1717:接口未知 我已经研究了其他浏览器(如Chrome)的行为。在Chrome中,所有者文档仍然引用#文档,但是所有者文档。默认视图最终将是未定义的。这对我来说很有意义。对appendChild和create
窗口打开)时,所有者文档将随之消失
这意味着对DOM的任何调用,如appendChild
或createElement
,都将失败,并导致SCRIPT70:权限被拒绝
或SCRIPT1717:接口未知
我已经研究了其他浏览器(如Chrome)的行为。在Chrome中,所有者文档
仍然引用#文档
,但是所有者文档。默认视图
最终将是未定义的
。这对我来说很有意义。对appendChild
和createElement
的调用将通过。我认为只要不直接引用defaultView
,一切都很好
问题2:在Internet Explorer中,当您单击生成窗口的关闭按钮时,它似乎不尊重事件循环。我在生成的窗口中附加了一个unload
事件,它立即启动,而不是在事件循环结束时将其排队。这对我来说没有意义。处理这个相当琐碎的问题变得非常不可能了
如果我们只是遇到了问题1,那么将有一个仍然痛苦但简单的解决方案:检查所有者文档是否存在,如果不存在则跳过。由于它是代码>所有者文件消失在同步的JavaScript代码中间。
预期行为:如果引用了DOM节点,则该节点不应消失-垃圾收集健全性
预期行为2:DOM节点不应在同步代码中消失。(当然,除非您将其删除)
已知的解决方法:将与DOM交互的所有代码移到窗口中,这样当窗口关闭时,JavaScript运行时环境也会随之关闭。这不是一个简单的解决方案,可能需要对您的体系结构进行重大更改
蹩脚的解决方案:将任何与DOM交互的函数包装在一个函数中,如果该函数检测到元素的窗口已关闭,则将消耗错误。这是一个相当具有侵略性的过程,对性能有着重大影响,IE已经非常慢了
有更好的解决办法吗
我想要的,至少是一种忽略由于用户关闭窗口而引发的任何错误的方法。问题1和问题2打破了您对JavaScript代码的基本假设:垃圾收集和事件循环
演示脚本
<script type="text/javascript">
function go() {
var popup = window.open('', 'open', 'width=500,height=300,scrollbars=yes,resizable=yes');
popup.document.open();
popup.document.write('<html><head></head><body></body></html>');
popup.document.close();
for (var i = 0; i < 10000; i += 1) {
var node = popup.document.createTextNode(i + " ");
popup.document.body.appendChild(node);
}
}
</script>
<input type="button" onclick="go();" value="Open popup" />
函数go(){
var popup=window.open(“”,“open”,“width=500,height=300,scrollbars=yes,resizable=yes”);
popup.document.open();
popup.document.write(“”);
popup.document.close();
对于(变量i=0;i<10000;i+=1){
var node=popup.document.createTextNode(i+“”);
popup.document.body.appendChild(节点);
}
}
(另存为.html文件)
说明:
- 在Internet Explorer 9中打开
- 单击“打开弹出窗口”
- 渲染时关闭窗口
- 遵守“拒绝许可”
这里是一个JSFiddle:您可以修改您的“糟糕的解决方案”。当触发unload
事件时,可以重新定义与DOM交互的函数,而不是包装与DOM交互的函数,例如myOldFunction=function(){}代码>除非有人有更好的解决方案,否则我会选择糟糕的解决方案。这是我的密码:
function apply_window_close_fix(dom_element, wrapped_element) {
var ignore_errors = false;
dom_element.ownerDocument.defaultView.addEventListener("unload", function () {
ignore_errors = true;
});
return map(wrapped_element, function (key, func) {
return function () {
try {
return func.apply(this, arguments);
} catch (e) {
if (ignore_errors === false) {
throw e;
}
}
};
});
}
wrapped_元素
是我为修改DOM返回的API。我将所有函数包装在一个try-catch中,如果它看到窗口已关闭,它将忽略错误。我只为表现类似Internet Explorer的浏览器调用此函数
似乎只有一个非常小的性能打击。当然,这取决于调用此API的强度
一个较小的缺点是,当前在某些浏览器中会中断对某些错误的重新引用。重新触发DomeException会重置Internet Explorer和Chrome(可能还有其他浏览器)中的堆栈。我还没有找到从Internet Explorer中的DomeException获取文件名和行号的方法。再一次,一个严重的疏忽最终只会浪费每个人的时间。在尝试了几件事情(postMessage、Worker等)后,我发现IE9总是有问题,我找到了很好的解决方案。我使用setInterval函数制作了Parallel.For循环。这里有一个问题:
setInterval()方法将继续调用该函数,直到
调用clearInterval(),或关闭窗口
创建节点的逻辑在子窗口中,但它是从父窗口触发的。循环分块执行,因为使用了setInterval,所以在任何点关闭子窗口都不会产生错误。此外,浏览器不会挂起(运行时既不是父级也不是子级)。看起来是这样的:
我们有3个组件:parent-ie.html、child-ie.html和小型parallel.js文件。一个怪癖是,所有浏览器都使用了setInterval(函数,-1)。正值被忽略,即,第二个省略的参数混淆了Opera,因此它只生成函数的第一个块。无论如何,代码在这里:
parent-id.html
<!DOCTYPE html>
<html>
<head>
<title>Parent</title>
</head>
<body>
<script type="text/javascript">
'use strict';
(function(){
var popup;
var pathname = 'child-ie.html';
window.openPopup = function _open() {
popup = window.open(pathname, 'open', 'width=500,height=300,scrollbars=yes,resizable=yes');
}
window.createElements = function _createElements() {
if (popup == null || popup.closed == true) {
alert("Open new popup window first.");
return;
}
var numberOfElements = parseInt(document.getElementById('numberOfElements').value);
popup.writeElements(numberOfElements);
}
})();
</script>
<button onclick="openPopup()">Open popup</button><br />
<button onclick="createElements()">Create elements</button>
<input id="numberOfElements" type="number" value="10000" />
</body>
</html>
<!doctype html>
<html>
<head>
<title>Child window</title>
</head>
<body>
<script src="/Scripts/Parallel.js"></script>
<script>
(function(){
function _iterator(index) {
var node = document.createTextNode(index + " ");
document.body.appendChild(node);
}
window.writeElements = function (numberOfElements) {
document.body.innerHTML = '';
Parallel.For(0, numberOfElements, 100, _iterator);
}
})();
</script>
</body>
</html>
父母亲
"严格使用",;
(功能(){
var弹出窗口;
var pathname='child-ie.html';
window.openPopup=函数_open(){
popup=window.open(路径名'open','width=500,height=300,scrollbars=yes,resizeable=yes');
}
window.createElements=函数_createElements(){
if(popup==null | | popup.closed==true){
警报(“首先打开新的弹出窗口”);
'use strict';
var Parallel;
(function (Parallel) {
var Iterator = (function () {
function Iterator(from, to, step, expression) {
this._from = from;
this._to = to;
this._step = step;
this._expression = expression;
}
Object.defineProperty(Iterator.prototype, "intervalHandle", {
set: function (value) {
this._intervalHandle = value;
},
enumerable: true,
configurable: true
});
Iterator.prototype.next = function () {
var max = this._to > this._step + this._from ? this._step + this._from : this._to;
for(var i = this._from; i < max; i += 1) {
this._expression(i);
}
if(max === this._to) {
clearInterval(this._intervalHandle);
} else {
this._from = max;
}
};
return Iterator;
})();
function For(from, to, step, expression) {
var _iterator = new Iterator(from, to, step, expression);
_iterator.intervalHandle = setInterval(function () {
_iterator.next();
}, -1);
}
Parallel.For = For;
})(Parallel || (Parallel = {}));
'use strict';
module Parallel {
class Iterator {
private _from: number;
private _to: number;
private _step: number;
private _expression: (index: number) => {};
private _intervalHandle: number;
public set intervalHandle(value: number) {
this._intervalHandle = value;
}
constructor(from: number, to: number, step: number, expression: (index: number) => {}) {
this._from = from;
this._to = to;
this._step = step;
this._expression = expression;
}
next() {
var max: number = this._to > this._step + this._from ? this._step + this._from : this._to;
for (var i = this._from; i < max; i += 1) {
this._expression(i);
}
if (max === this._to) {
clearInterval(this._intervalHandle);
}
else {
this._from = max;
}
}
}
export function For(from: number, to: number, step: number, expression: (index: number) => {}) {
var _iterator = new Iterator(from, to, step, expression);
_iterator.intervalHandle = setInterval(function () {
_iterator.next();
}, -1);
}
}
<script>
function wasteTime(message) {
// TODO: waste time
}
function FirstPartOfLongOperation(){
wasteTime('long... part 1');
setTimeout(SecondPartOfLongOperation, 0);
}
function SecondPartOfLongOperation() {
wasteTime('long... part 2');
setTimeout(ThirdPartOfLongOperation, 0);
}
function ThirdPartOfLongOperation() {
wasteTime('long... part 3');
setTimeout(FourthPartOfLongOperation, 0);
}
function FourthPartOfLongOperation() {
wasteTime('long... part 4');
alert('Done!');
}
// Main entry, called from parent.
// Ex: popup.LongLastingOperation({ data: ....})
function LongLastingOperation(parametersPassedFromParentWindow) {
// decompose long operation
setTimeout(FirstPartOfLongOperation, 0);
}
</script>