原文連接: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也如此,老道也如此,全部的大師都是如此。成功,重在細節。