Javascript 关闭窗口会中断事件循环假设

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

我遇到了一个小麻烦,后来变成了一个大问题

问题1:在Internet Explorer中,当您关闭一个窗口(通过
窗口打开)时,
所有者文档将随之消失

这意味着对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>