9

我有两个窗口,一个是从另一个打开的,所以,我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. 这个问题有一些解决方法吗?
4

4 回答 4

6

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

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

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

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

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

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

于 2012-05-09T21:42:28.523 回答
2

看来这是设计使然。

这是一篇关于它的文章

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

于 2012-05-15T04:03:57.130 回答
2

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

做到这一点并确保它适用于所有浏览器的唯一面向未来的方法是使用所有现代浏览器(包括 IE8 及更高版本)都支持的跨文档消息传递 API 。

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

它归结为一侧的电话以发布消息,例如:

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,但设置起来看起来有点复杂。

于 2012-05-17T18:23:11.957 回答
-1

打开子窗口后,您可以将父级创建的函数注册到其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>
于 2012-05-17T15:30:05.777 回答