原文连接:http://www.nczonline.net/blog/2009/09/15/iframes-onload-and-documentdomain/
译者:Demix
在web2.0的时代,愈来愈多的人开始关注使用iframe将第三方网站的内容嵌入本身的网站中。当javascript可以经过其域名进行数据交互后,iframe开始提供一系列的安全措施,使得一个嵌套于iframe中的第三方网站不可能获取到主体网站的脚本程序。这个跨域的限制一样也让父级页面没法读取嵌套内容的脚本。从全部的角度来讲,父级页面和被iframe包含的页面是彻底没有联系的。这个复杂的关系让javascript对象的全部权成为了许多有关iframe讨论的话题之一。
iframe 和全部权
iframe元素自己是位于父级页面中的,因此你能够像一个普通元素同样的使用和操做它。表明了iframe内容window对象是做为一个页面的属性加入到iframe中的。为了让父级页面可以以一种合适的方式获取iframe的window对象,父级页面和iframe页面的域名应该保持一致(详情)。
当域名吻合时,父级页面就能够获取到iframe的window对象了。iframe元素拥有名为contentDocument的属性,这个属性包含了iframe对象的document对象,因而咱们就可使用parentWindow这个属性取回window对象。这已经成为了获取iframe的window对象的标准方法,并被绝大多数浏览器支持。ie8之前的浏览器不支持这个属性,咱们须要用以使用其专有的contentWindow属性。如
1
function
getIframeWindow(iframeElement){
2
return
iframeElement.contentWindow
||
iframeElement.contentDocument.parentWindow;
3
}
补充一点,父级页面的window对象在iframe中可以以window.parent获取。iframe元素一样也可使用window.frameElement来获取本身的引用。因为iframe被父级元素包含但却能够直接获取到iframe的window对象,该方法普遍用于突破两者的界限。
使用iframe元素的onload事件
因为各类全部权的不一样,尝试肯定iframe什么时候装载完毕是一个颇有趣的实验。非ie浏览器提供了许多有用的方法。它们让iframe元素拥有load事件,这样咱们就能够肯定iframe什么时候装载彻底。因为iframe元素包含于父级页面中,你也不用担忧跨域的限制。装载本地数据的iframe可使用监听装载外部数据的iframe完成事件的相同方法。举例以下:
1
var
iframe
=
document.createElement(
"
iframe
"
);
2
iframe.src
=
"
simpleinner.htm
"
;
3
iframe.onload
=
function
(){
4
alert(
"
Iframe is now loaded.
"
);
5
};
6
document.body.appendChild(iframe);
上面的例子在全部非ie浏览器中均适用。我曾经尝试使用attachEvent方法,不过最终发现ie并不支持在iframe上的load事件。
使用iframe的window对象的onload事件
看起来ie又要给咱们制造难题了。不过随后,我记起来我之前没有考虑过在iframe中引用外部文件。在个人实验中,我曾经处理了同一域名下的内容。因为跨域限制不存在,我可以轻易的获取iframe对象的window对象并加上onload事件。例如:
1
var
iframe
=
document.createElement(
"
iframe
"
),
2
iframeWindow;
3
iframe.src
=
"
simpleinner.htm
"
;
4
document.body.appendChild(iframe);
5
iframeWindow
=
iframe.contentWindow
||
iframe.contentDocument.parentWindow;
6
iframeWindow.onload
=
function
(){
7
alert(
"
Local iframe is now loaded.
"
);
8
};
有趣的是,你必须在iframe元素已经加到页面中之后才能注册事件。若是先于它,iframe的window对象将不存在,咱们也固然不可能在window对象上注册事件。这个方法只在ie和ff下对于同域的两个嵌套页面有效。其余浏览器不会建立window对象并将抛出异常。
定义document.domain
我试图寻找一种能够监听ie里iframe的load事件的方法以及更多的应用于其余浏览器的方法,因而我继续了个人实验。接下来,因为我有多个须要使用iframe读取的不一样二级域名的页面,我设置了父级页面的document.domain。将document.domain设定为主域名可以容许这些iframe之间以及同父级页面的通讯。例如,若是我有须要读取一个地址为www2.nczonline.net的iframe,在技术上上说是不被容许的。不过,若是我在父级页面和iframe页面中均设置了document.domain为'nczonline.net',这两个页面将能够相互通信。以下:
1
document.domain
=
"
nczonline.net
"
;
这个声明消除了域名的区别,咱们能够像处理两个相同域名的网站同样处理这两个页面。
有一个问题又产生了。在iframe彻底加载前,它将被认为是属于iframe标签中声明的src属性标志的页面的。相对地址被自动加上了父级页面的地址(www.nczonline.net)并与咱们设置的document.domain相矛盾。这意味着在比较nczonline.net和www.nczonline.net时,咱们将通不过同域检查,因而当咱们试图获取iframe的window对象时,将引发javascript的报错。iframe页面并不会改变其关联的domain值,直到它加载完毕,届时改变domain值的脚本才会执行。当iframe已经加载完毕时,一切运行完美。可是,咱们是怎么知道iframe何时才加载完?
换个方向思考
因为一致没有找到一种能够跨浏览器解决断定iframe是否加载完毕的方法,我决定转变一下个人想法。若是咱们让iframe告诉父级页面它已经加载完毕,而不是让父级页面去获取iframe的load事件,也许可以解决问题。我但愿这种方法可以与注册一个事件句柄同样简单,因此我采用了下面的想法:我在iframe元素上声明一个方法,而后,当iframe页面加载完毕以后会执行这个函数。固然,这个方法是被声明到iframe元素自己而不是iframe的window对象上的,后一方法在前面的研究中被证实不能兼容全部的浏览器。结果看起来像这样:
1
var
iframe
=
document.createElement(
"
iframe
"
);
2
iframe.src
=
"
simpleinner.htm
"
;
3
iframe._myMethod
=
function
(){
4
alert(
"
Local iframe is now loaded.
"
);
5
};
6
document.body.appendChild(iframe);
上面的代码在iframe元素上声明了_myMethod的方法。iframe中的页面加入以下方法:
1
window.onload
=
function
(){
2
window.frameElement._myMethod();
3
}
因为上述代码是在咱们声明document.domain以后运行的,因而咱们便不用担忧任何安全限制的问题。这种方法在同一主域名下工做得很完美。它可以兼容全部的浏览器,这也正是我所须要的。可是,监听包含第三方页面的iframe的load事件仍然在困扰我。
使用iframe的onreadystatechange
我决定研究一下ie浏览器关于iframe的接口文档。若是在onload事件中声明某些事件显而易见不能达到咱们想要的效果,可是我以为确定会有相似的方法。我尝试使用attachEvent方法去增长事件句柄,可是仍然没有用。ok,显然ie中的iframe并不支持load事件。有其余方法吗?
接下来我使用了ie的一种怪异的方法——readystatechange事件。显然它与xhr对象的readystatechange事件彻底不同。我想知道是否iframe元素也支持这个事件,它会在iframe嵌套的内容加载彻底前变成'interactive',随后变成'complete'。同时,因为它是注册到iframe元素而不是iframe的window对象上,这理所固然不会存在跨域的问题。最后我整理出来的代码以下:
1
var
iframe
=
document.createElement(
"
iframe
"
);
2
iframe.src
=
"
simpleinner.htm
"
;
3
4
if
(navigator.userAgent.indexOf(
"
MSIE
"
)
>
-
1
&&
!
window.opera){
5
iframe.onreadystatechange
=
function
(){
6
if
(iframe.readyState
==
"
complete
"
){
7
alert(
"
Local iframe is now loaded.
"
);
8
}
9
};
10
}
else
{
11
iframe.onload
=
function
(){
12
alert(
"
Local iframe is now loaded.
"
);
13
};
14
}
15
16
document.body.appendChild(iframe);
判断浏览器是否为ie浏览器稍稍有些麻烦。原本我更偏向使用判断iframe.readystate是否存在来进行浏览器的检测。可是,当试图获取未加入到页面中的iframe的属性时会抛出一个错误。我也尝试使用document.readyState去判断是否使用readystatechange,然而,已经有不少浏览器支持前一属性了,因此它并非一个有效的划分手段。
ie 对onload事件的支持
在发表这篇文章后短暂的时间里, Christopher留言说在iframe元素上使用attachEvent是可以在ie下工做的。我发誓我以前已经尝试过这样的方法,可是因为他的提示,我尝试了另一个实验。随后发现,他是正确的。随后我研读了msdn上的文档,最后终于发现了一段文档说明了这个问题。它最终让咱们的代码变成了下面这个样子
1
var
iframe
=
document.createElement(
"
iframe
"
);
2
iframe.src
=
"
simpleinner.htm
"
;
3
4
if
(iframe.attachEvent){
5
iframe.attachEvent(
"
onload
"
,
function
(){
6
alert(
"
Local iframe is now loaded.
"
);
7
});
8
}
else
{
9
iframe.onload
=
function
(){
10
alert(
"
Local iframe is now loaded.
"
);
11
};
12
}
13
14
document.body.appendChild(iframe);
以上的代码仍然能正常运行于全部的浏览器之上,并能回避readystatechange事件与load事件潜在的冲突可能。
综合 在一小段调研以后,咱们发现肯定一个iframe对象什么时候加载完成的跨浏览器的方法是存在的。这让咱们对iframe的监听和错误控制变得容易的多。感谢全部的浏览器厂商看到了在iframe元素上添加这些事件的好处,而不是去依赖iframe的window对象或者认为咱们平时并不关心iframe什么时候完成加载。 好久没有翻译了,草草翻译出上面这篇文章,错误必定很多。Zakas这篇文章很搞笑,写出来几分钟后有人留言说里面有错误,又改掉了。这里咱们又看到写博客的一个好处——共同成长。若是没有后来的评论,也许Zakas会一直使用不太优雅的监听readystatechange事件来实现。另外经过这篇文章,咱们看到了大师的细致之处。虽然整篇文章所描述的问题也许咱们平时都会有接触,可是又有哪个人会有这样的细致。PPK也如此,老道也如此,全部的大师都是如此。成功,重在细节。