我遇到了一个令人烦恼的小烦恼。
问题1:在关闭窗口(通过window.open
打开)的Internet Explorer中,ownerDocument
将随之消失。
这意味着任何对DOM的调用(例如appendChild
或createElement
)都会因SCRIPT70: Permission Denied
或SCRIPT1717: The interface is unknown
而失败。
我查看了Chrome等其他浏览器的行为。在Chrome中ownerDocument
仍然引用#document
,但ownerDocument.defaultView
最终会undefined
。这对我来说很有意义。致appendChild
和createElement
的电话将通过。我认为一切都很好,只要你不试图直接引用defaultView
。
问题2:在Internet Explorer中单击生成窗口的关闭按钮时,它似乎不尊重事件循环。我将unload
事件附加到生成的窗口,它立即触发 ,而不是在Event循环结束时对其进行排队。这对不对我有意义。处理这个相当微不足道的问题变得十分不可能。
如果我们只是遇到问题1 ,那么会有一个令人痛苦但又直截了当的解决方案:检查ownerDocument
是否存在,如果不存在则跳过。{因为ownerDocument
在同步JavaScript代码的中间消失了。
预期的行为:如果您引用了DOM节点,它不应该消失 - 垃圾收集整理。
预期的行为2: DOM节点不应该在同步代码中消失。 (除非你当然删除它。)
已知的解决方法:将与DOM交互的所有代码移动到窗口中,以便在窗口关闭时,JavaScript运行时环境也是如此。这不是一个简单的解决方案,可能需要对您的架构进行重大更改。
Crappy解决方案:在一个函数中包装任何与DOM交互的函数,如果它检测到元素的窗口已经关闭,它将消耗错误。这是非常具有侵略性并且对性能有重大影响,IE已经很慢了。
有更好的解决方案吗?
我想要的是,忽略由于用户关闭窗口而引发的任何Error
的方法。 问题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" />
(另存为.html文件)
说明:
这是一个JSFiddle:http://jsfiddle.net/C9p2R/1/
答案 0 :(得分:1)
除非有人有更好的解决方案,否则我会选择糟糕的解决方案。这是我的代码:
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_element
是我为修改DOM而返回的API。我已将所有函数包装在try-catch中,如果它看到窗口已关闭,它将忽略错误。我只对那些表现得像Internet Explorer的浏览器调用此函数。
似乎只有非常小的性能影响。当然,这取决于您对此API的称呼程度。
一个小的缺点是,在某些浏览器中,当前重新抛出某些错误会被破坏。重新抛出DOMException会重置Internet Explorer和Chrome(以及可能的其他)中的堆栈。我还发现无法从Internet Explorer中的DOMException获取文件名和亚麻布。再次,一个严重的疏忽,最终会浪费时间给每个人。
答案 1 :(得分:0)
您可以修改“Crappy Solution”。您可以在unload
事件以不与DOM交互的方式触发时重新定义它们,而不是包装与DOM交互的函数。 myOldFunction = function (){};
。
答案 2 :(得分:0)
在尝试了几件事(postMessage,Worker,......)总是遇到IE9问题之后,我找到了很好的解决方案。我使用setInterval函数创建了Parallel.For循环。 Catch在这里:
setInterval()方法将继续调用函数直到 调用clearInterval(),或关闭窗口。
创建节点的逻辑位于子窗口中,但它是从父窗口触发的。循环以块的形式执行,因为使用了setInterval,所以在任何点关闭子窗口都不会产生错误。此外,浏览器不会挂起(在运行时不会挂起父级或子级)。它看起来像这样:
我们有3个组件:parent-ie.html,child-ie.html和small parallel.js文件。一个怪癖是,所有浏览器都使用setInterval(函数,-1)。正值是制动IE,第二个省略参数混淆Opera所以它只产生第一个功能块。无论如何,代码在这里:
<强>父 - 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>
儿童-ie.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>
<强> /Scripts/Parallel.js 强>
'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 = {}));
Javascript是从typescript文件生成的(可能比javascript更清晰):
'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);
}
}
就是这样。
答案 3 :(得分:0)
好的,让我试着简化一下。为了获得窗口的响应性并避免在孩子关闭时破坏应用程序,必须结合几件事。
setTimeout
或
setInterval
,用于调度DOM修改函数,委托
执行到孩子并立即返回。 此步骤可以自动清理! setTimeout
或setInterval
,以便不锁定事件循环
需很长时间。 这为两个窗口提供了响应。最简单的儿童javascript示例。从父进程调用的函数是LongLastingOperation
,它已被拆分为4个块:
<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>