【问题标题】:Can't pass a function from one window to another in IE无法在 IE 中将函数从一个窗口传递到另一个窗口
【发布时间】:2012-05-08 13:30:13
【问题描述】:

我有两个窗口,一个是从另一个窗口打开的,所以,我在“子”窗口中有一个opener 属性。

父窗口在全局范围内有一些函数,必须以函数作为第一个参数调用(它将用作回调)。

两个页面都是从同一个域打开的,所以我没有任何同源策略限制(我希望如此)...

在子窗口中我有这样的代码

if(window.opener) {
    window.opener.myFunction(function() { ... });
}

一切正常,直到我尝试在 IE 中运行它。在此浏览器中,myFunction 收到的参数始终为 Object 类型(使用 typeof 检查)。 myFunction 的代码是这样的:

window.myFunction = function(cb) {
    alert('Callback type is ' + (typeof cb));
    if(typeof cb == 'function') 
        cb();
    else
        alert('Not a function!');
}

现场演示:http://elifantiev.ru/ie-opener-issue/first.html

问题是:

  1. 这是符合标准的行为吗?
  2. 是否有解决此问题的方法?

【问题讨论】:

  • 请说明您遇到此问题的 IE 版本。

标签: javascript internet-explorer function window.opener


【解决方案1】:

即使 typeof 返回“object”,该函数仍然按预期工作。调用cb()会执行函数。

使用typeof 来确定参数是否为函数的替代方法是测试所有函数都应具有的调用属性:

if (cb && cb.call) { cb(); }

如果你传递的东西需要一个函数,它可以像这样包装:

function newCb() {
    return cb.apply(object, arguments);
}

还要注意,从父级向子级传递函数时,typeof 也是对象。将原始函数与作为对象的函数(在往返之后)进行比较返回 true。如果出于某种原因您需要对原始函数的引用(例如退订时),这一点很重要。

【讨论】:

  • +1 for if (cb && cb.call)...最好远离typeof
  • 包装很好,但不能使用,因为'我需要在框架的任何函数中添加包装,它可以接收一个函数作为参数...
  • 我不喜欢它们,但您可以对 Function 进行原型设计,这样您就不必担心重写整个框架,只需您的客户端调用。我会在一分钟内更新我的答案。
【解决方案2】:

即使您现在可以让它工作,我也不指望两个浏览器窗口直接调用彼此的函数的能力会持续更长时间。即使您从场景中消除了跨域问题,也存在太多安全问题。

做到这一点并确保它适用于所有浏览器的唯一面向未来的方法是使用所有现代浏览器(包括 IE8 及更高版本)都支持的cross-document messaging APIs

当我需要解决这个问题时,我能找到的最详细的例子是window.postMessage article on MDN

归根结底是一方面调用以发布消息,例如:

otherWindow.postMessage(message, targetOrigin);

然后是另一边的事件处理程序:

window.addEventListener("message", receiveMessage, false);

function receiveMessage(event)
{
  alert("I got it!\n" + event.data);
}

如果窗口来自同一个域,IE6 和 IE7 可能允许你现在进行这些跨窗口调用,但 IE8 及更高版本可能希望你使用这个 API,我猜其他浏览器最终会恢复完全回到这种更安全的通信机制。

鉴于这种情况,我强烈建议使用共享库来共享您的代码(使用类似 LAB.js 或 require.js 甚至只是 Jquery 的 getScript() 函数),然后使用跨文档消息传递系统发送事件,而不是您今天尝试使用的回调函数。

更新


有一些 polyfill 可用于为旧版浏览器添加 postMessage 支持。由于您不进行跨域调用,我将从 Ben Alman 的 Jquery postMessage plugin 开始。我没用过,但我用过 Ben 的优秀 Jquery BBQ 插件。如果支持,它应该回退到内置方法,并且只在旧浏览器中的替代方法中填充。它声称可以在 IE7 中工作...

如果 Ben 的插件不能解决问题,那么您可以尝试 easyXDM,但设置起来看起来有点复杂。

【讨论】:

  • 我认为这是一个最好的、符合标准的工作环境。但这对我来说是个问题,因为 IE7 不支持它。无论如何,我认为你是赢家。
  • 我以前没想过(我赶上了时间期限),但是在 IE7 中有一些 polyfills 可以提供这种方法。我更新了答案以包含它们。
  • 不错的库,但它使用 location.hash 来传递消息...在我的环境中,哈希高度用于内部需求(例如导航等)。但它可以修改以满足我的需求,而不是破坏任何已经工作的东西。谢谢。
【解决方案3】:

这似乎是设计使然。

Here's an article about it

https://bugzilla.mozilla.org/show_bug.cgi?id=475038

因为每个窗口可以有不同的原型链,所以无法按预期传递函数。尝试做这样的事情来确认:

   var popup = window.open('second.html');

   window.myFunction = function(cb) {
            alert(cb instanceof Function);       //false
            alert(cb instanceof popup.Function); //true
   }

因此,如果在父端需要担心许多函数(我讨厌原型,但这次我认为你被卡住了),我将如何进行正确的函数验证:

父窗口:

<html>
<script type="text/javascript">
    /* give all our functions the validation check at once */
    Function.prototype.remote = function(cb) {
        if(cb instanceof popup.Function) {
            this(cb);
        } else {
            alert('Not a function!');
        }
    };

    var popup,
    myFunction = function(cb) {
        cb();
    },
    test = function(cb) {
        cb();
    }

</script>   
<body>
    <a href="#" onclick="popup = window.open('second.html'); return false;">Open window</a>
</body>
</html>

子窗口:

<html>
<body>
    <script type="text/javascript">
        var Parent = window.opener;
        if(Parent) {
            Parent.myFunction.remote(function(){
                alert('callback!');
            });

            Parent.test.remote(function() {
               alert('callback again');
            });
        }
    </script>
    This is a second page!
</body>
</html>

下面是一个工作示例: http://dl.dropbox.com/u/169857/first.html

【讨论】:

    【解决方案4】:

    打开子窗口后,您可以将父级创建的函数注册到其window 上下文中。可以看到jsFiddle中运行如下代码:

    <html>
    <head>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
    <script>
    
    // will be called by the child window
    function MyExportedCallback(x) {
        $("#my-callback-result").text("this callback was run with '" + x + "'.");
    }
    
    // will open a child window and create content for testing
    function MyLinkHandler() {
        var wnd = window.open("about:blank");
    
        // IMPORTANT: this is where the parent defined function is "registered"
        // into child window scope
        wnd["myExternal"] = MyExportedCallback;
    
        // auxiliary code to generate some HTML to test the external function
        var put = function(x) { wnd.document.write(x); };
        put("<html><body>");
        put("<a href='#' onclick='window.myExternal(123);'>");
        put("click me to run external function");
        put("</a>");
        put("</body></html>");
    }
    
    // attach events
    $.ready(function() {
        $("#my-link").click(MyLinkHandler);
    });
    
    </script>
    <body>
    <p><a id="my-link" href="#">Open window</a></p>
    <p id="my-callback-result">waitting callback...</p>
    </body>
    </html>
    

    【讨论】:

    • 主要问题是如何将在子窗口中创建的函数作为参数传递给父窗口函数。
    • @Olegas,您的原始帖子说:“父窗口在全局范围内有一些函数,必须使用函数作为第一个参数调用它(它将用作回调)。”所以我预计问题是从子窗口调用父窗口中定义的函数。实际上,在 Javascript 中传递一个函数就像传递任何其他参数一样,这就是我没有探索它的原因。但没关系,看起来你已经解决了你的问题,你没有任何进一步的帮助。
    • 打电话 - 不是问题。传递一个函数 - 是问题所在。在 IE 中,它在通过窗口边界时将其 typeof 更改为 Object
    • 对于 IE6 ​typeof(new Function("x", "return x*2;")) == 'object',但是,虽然它很奇怪,但它的行为就像一个常规函数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-11
    • 1970-01-01
    相关资源
    最近更新 更多