我有两个窗口,一个是从另一个窗口打开的,所以,我在“子”窗口中有一个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
问题是:
答案 0 :(得分:6)
即使typeof返回"对象"该功能仍然按预期工作。调用cb()
将执行该功能。
使用typeof
确定参数是否为函数的替代方法是测试所有函数都应具有的call属性:
if (cb && cb.call) { cb(); }
如果你传递的东西需要一个函数,它可以像这样包装:
function newCb() {
return cb.apply(object, arguments);
}
另请注意,将函数从父级传递给子级时,typeof也是对象。将原始函数与函数作为对象进行比较(往返之后)返回true。如果由于某种原因需要引用原始函数(例如取消订阅时),这很重要。
答案 1 :(得分: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
答案 2 :(得分:2)
即使你现在可以让它工作,我也不会指望两个浏览器窗口直接调用彼此的函数的能力要长得多。即使您从场景中删除了跨域问题,也存在太多安全问题。
唯一能够面向未来的方法,并确保它适用于所有浏览器,是使用所有现代浏览器支持的cross-document messaging APIs,包括IE8及更高版本。
当我需要解决这个问题时,我能找到的最详细的例子是window.postMessage article on MDN。
它归结为一方呼叫发布消息,例如:
otherWindow.postMessage(message, targetOrigin);
然后是另一方的事件处理程序:
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event)
{
alert("I got it!\n" + event.data);
}
如果Windows来自同一个域,IE6和IE7可能允许你现在进行那些跨窗口调用,但是IE8及以上版本可能会期望你使用这个API,我猜其他浏览器最终还原完全回到这个更安全的沟通机制。
鉴于这种情况,我强烈建议使用共享库来共享您的代码(使用类似LAB.js或require.js或甚至只是Jquery的getScript()函数),然后使用跨文档消息传递系统发送事件,而不是你今天尝试使用的回调函数。
更新
可以使用polyfill为旧浏览器添加postMessage支持。由于你没有进行跨域调用,我将从Ben Alman的Jquery postMessage plugin开始。我没有用它,但我使用了Ben的优秀Jquery BBQ插件。如果支持,它应该回退到内置方法,并且只在旧浏览器中使用替代方法。它声称可以在IE7中工作......
如果Ben的插件不能解决问题,那么你可以尝试使用easyXDM,但设置起来看起来有点复杂。
答案 3 :(得分:-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>