對iframe的探究
iframe的基本概念
iframe通常作為網頁中的內聯框架在網站中使用。
優點:
1.iframe 可以和主頁面並行加載而不會阻塞主頁面的渲染加載
2.可以和主頁面進行相互的事件溝通和dom操作
iframe.contentWindow, 獲取iframe的window對象
iframe.contentDocument, 獲取iframe的document對象
在同域下,父頁面可以獲取子iframe的內容,那麽子iframe同樣也能操作父頁面內容。
window.parent 獲取上一級的window對象,如果還是iframe則是該iframe的window對象
window.top 獲取最頂級容器的window對象,即,就是你打開頁面的文檔
window.self 返回自身window的引用。可以理解 window===window.self(腦殘)
不同域的情況下,主頁面可以通過路由給iframe傳遞信息,而且html5支持的postMessage也允許來自不同源的腳本采用異步方式進行有限的通信,可以實現跨文本檔、多窗口、跨域消息傳遞。
發送消息: 使用postmessage方法
window.frames[‘name‘].postMessage(data, origin),data:要傳遞的數據(最好是字符串);origin:字符串參數,指明目標窗口的源,協議+主機+端口號,這個參數是為了安全考慮,someWindow.postMessage方法只會在someWindow所在的源(url的protocol, host, port/
"
接受消息: 監聽message事件
window.addEventListener(‘message‘, function(message) {
//todo message有三個屬性,data:顧名思義,是傳遞來的message;source:發送消息的窗口對象;origin:發送消息窗口的源(協議+主機+端口號)
}, false)
缺點:
1.iframe會阻塞主頁面的onload事件,iframe加載渲染完之後才會觸發主頁面的onload事件
2.iframe和主頁面共享同一個連接池
作用:
1.利用它的特性可以提供跨域的解決辦法;
2.它還可以模擬實現ajax;
3.可以利用它獨立封閉的特點來作為網頁中的廣告彈窗以及第三方網頁的展示框架;
這裏主要探究 1 和 2 的實現
在跨域中的應用
跨域是指js代碼在不同域之間進行數據傳輸和通訊的情況。只要是協議、域名、端口任一不同都是這裏的不同域的情況。另外的情況,域名和對應的ip地址也屬於不同域,統一域名、不同二級域名也屬於不同域
這裏跨域主要指一個域的網頁js對另一個域下的服務器進行網絡請求。
為了演示方便,這裏不同域的情況為端口不同。
跨域演示步驟:
這裏的示例是hostA(127.0.0.1:3005)域下的 requestA.html 頁面對 hostB (127.0.0.1:3006) 域下的 /getUserInfo 接口請求用戶信息。
首先將requestA.html放在hostA域下
// hostA.js
let Http = require(‘http‘) let fs = require(‘fs‘) const server = Http.createServer((req, res) => { res.setHeader(‘Content-Type‘, ‘text/html‘) //設置響應格式為html頁面 res.writeHead(200, ‘ok‘) if (req.url === ‘/requestA.html‘) { res.write(fs.readFileSync(‘./requestA.html‘)) //返回requestA頁面 } res.end() }) server.listen(3005, ‘127.0.0.1‘, () => { //服務器為本機,端口3005 console.log(‘serverA listen in 127.0.0.1:3005‘) })
然後在requestA頁面向hostB服務器發送請求,並將響應打印出來
// requestA.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>requestA</title> <link rel="stylesheet" href=""> </head> <body> <h1>我是requestA頁面</h1> <button type="" onclick="request(‘getUserInfo‘)">getUserInfo</button> //點擊按鈕向hostB發送請求 <script type="text/javascript"> function request(api) { var ajaxObj if (window.XMLHttpRequest) { ajaxObj = new XMLHttpRequest } else { ajaxObj = new ActiveXObject("Microsoft.XMLHTTP") } ajaxObj.onreadystatechange = function() { if (ajaxObj.readyState === 4 && ajaxObj.status === 200) { document.write(ajaxObj.responseText) //打印返回信息 } } ajaxObj.open(‘get‘, ‘http://127.0.0.1:3006/‘ + ‘api‘, true) ajaxObj.send() } </script> </body> </html>
設置hostB服務器的處理
// hostB.js
let Http = require(‘http‘) let fs = require(‘fs‘) const server = Http.createServer((req, res) => { res.setHeader(‘Content-Type‘, ‘application/json‘) res.writeHead(200, ‘ok‘) if (req.url === ‘/getUserInfo‘) { res.write(JSON.stringify({name: ‘zzp‘, age: 22})) //返回用戶信息 } res.end() }) server.listen(3006, ‘127.0.0.1‘, () => { console.log(‘serverA listen in 127.0.0.1:3006‘) })
請求結果,如下圖提示因為發生了跨域而不能正常的完成http請求響應(從127.0.0.1:3005到127.0.0.1:3006的http請求因為CROS(跨域資源共享)協議被鎖定)
當然解決這個跨域問題最簡單的方法就是根據這個錯誤提示 (No ‘Access-Control-Allow-Origin‘ header is present on the requested resource.) 來在服務端 hostB.js 設置 ‘Access-Control-Allow-Origin‘ 響應頭信息
// hostB.js
res.writeHead(200, ‘ok‘, {‘Access-Control-Allow-Origin‘: ‘http://127.0.0.1:3005‘}) //允許127.0.0.1:3005對本服務器進行http跨域請求
//或者
res.writeHead(200, ‘ok‘, {‘Access-Control-Allow-Origin‘: ‘*‘}) //允許所有域對本服務器進行http跨域請求
可見,這樣就解決了跨域的問題,但是因為這裏我們想要研究的是iframe在跨域中的應用,而且這種方法需要後端來實現,所以這種方法不再予以考慮。
下面接著研究如何通過iframe來解決上面的跨域問題
第一種方法:通過一個主頁面和2個嵌套的iframe來實現跨域的解決方案
技術要點:同域下的主頁面和iframe可以互相拿到window對象,進而傳遞事件操作DOM;同域下的html頁面可以向服務器發送ajax請求並不被跨域協議限制;不同域下的主頁面可以通過路由向iframe傳遞信息
首先在requestA.html頁面裏設置一個iframeB,這個iframeB鏈接向hostB下的一個html頁面iframeB.html,將requestA.html請求的參數通過路由傳遞給iframeB.html,然後由iframeB.html向hostB發送ajax請求並接收響應結果,之後在iframeB.html頁面裏面再嵌套
一個iframeA,iframeA又鏈接向hostA下的iframeA.html頁面,iframeB同樣將響應信息通過路由傳遞給iframeA,最後再由iframeA將響應信息直接傳回requestA.html。
將iframeA和iframeB分別添加到hostA和hostB服務器下面
// hostA.js
if (req.url === ‘/iframeA.html‘) { res.write(fs.readFileSync(‘./iframeA.html‘)) }
// hostB.js
if (req.url === ‘/iframeB.html‘) { res.write(fs.readFileSync(‘./iframeB.html‘)) }
對requestA.html頁面做一些修改,刪除ajax函數,添加iframeB生成函數,並通過路由將請求參數傳給iframeB,最好將iframeB設為隱藏,以防影響主頁面布局
// requestA.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>requestA</title> <link rel="stylesheet" href=""> <style type="text/css"> #iframeB { width: 500px; height: 400px; display: none; } </style> </head> <body> <h1>我是requestA頁面</h1> <button type="" onclick="request(‘getUserInfo‘)">getUserInfo</button> <script type="text/javascript"> function request(api) { let iframeB = document.createElement(‘iframe‘) iframeB.src = "http://127.0.0.1:3006/iframeB.html#" + api // 將請求參數傳給iframeB.html頁面 iframeB.id = "iframeB" document.body.appendChild(iframeB) } </script> </body> </html>
在iframeB.html頁面裏首先拿到requestA傳過來的請求參數,然後根據請求參數向hostB發送ajax請求,拿到響應數據之後在頁面裏面嵌套一個iframeA,同時將響應數據通過路由傳給iframeA。
// iframeA.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>iframeB</title> <link rel="stylesheet" href=""> <style type="text/css"> #iframeA { width: 400px; height: 300px; display: none; } </style> </head> <body> <h1>iframeB</h1> <script type="text/javascript"> window.onload = function() { let api = window.location.hash.split(‘#‘)[1] //拿到請求參數 request(api) //向hostB發送ajax請求 } function request(api) { var ajaxObj var data if (window.XMLHttpRequest) { ajaxObj = new XMLHttpRequest } else { ajaxObj = new ActiveXObject("Microsoft.XMLHTTP") } ajaxObj.onreadystatechange = function() { if (ajaxObj.readyState === 4 && ajaxObj.status === 200) { data = ajaxObj.responseText callbackIframeA(data) } } ajaxObj.open(‘get‘, ‘http://127.0.0.1:3006/‘ + api, true) ajaxObj.send() } function callbackIframeA(data) { //嵌套iframeA,將響應數據傳給iframeA let iframeA = document.createElement(‘iframe‘) iframeA.src = "http://127.0.0.1:3005/iframeA.html#" + data iframeA.id= "iframeA" document.body.appendChild(iframeA) } </script> </body> </html>
在iframeA裏面拿到路由傳過來的響應數據並在主頁面requestA.html裏面顯示出來
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>iframeA</title> <link rel="stylesheet" href=""> </head> <body> <h1>iframeA</h1> <script type="text/javascript"> window.onload = function() { let data = decodeURIComponent(window.location.hash).split(‘#‘)[1] let p = document.createElement(‘p‘) p.innerHTML = data window.top.document.body.appendChild(p) //window.top可以直接拿到最頂層容器的window對象 } </script> </body> </html>
實現成果
可以看出,在requestA頁面成功拿到了向hostB請求的信息並且沒有任何錯誤警告。
至此,就完成了requestA.html對hostB的跨域請求,事件路線為:requestA.html————iframeB.html————hostB————iframeB.html————iframeA.html————requestA.html
第二種方法:使用CDM(cross document messaging)進行不同域下的主頁面和iframe的跨域消息傳遞,hostA域下的主頁面requestA.html通過postmessage將請求參數傳給內嵌的hostB域下的iframeB.html,iframeB.html收到請求參數之後向hostB服務器發送
ajax請求,接收到響應後將響應信息通過postmessage再傳回主頁面。
技術要點:不同域下主頁面和iframe可以通過postmessage進行通信。
在requestA.html裏面給iframeB發送請求參數
// requestA.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>requestA</title> <link rel="stylesheet" href=""> <style type="text/css"> #iframeB { width: 500px; height: 400px; display: none; } </style> </head> <body> <h1>我是requestA頁面</h1> <button type="" onclick="request(‘getUserInfo‘)">getUserInfo</button> <iframe src="http://127.0.0.1:3006/iframeB.html" id="iframeB" name="iframeB"></iframe> <script type="text/javascript"> function request(api) { window.frames[‘iframeB‘].postMessage(api, ‘http://127.0.0.1:3006/‘) //給iframeB發送請求參數,需要指定目標窗體iframeB的源‘http://127.0.0.1:3006‘ } window.addEventListener(‘message‘ ,function(message) { //監聽iframeB傳回的響應信息 let p = document.createElement(‘p‘) p.innerHTML = message.data document.body.appendChild(p) }, false) </script> </body> </html>
在iframeB拿到請求參數並向hostB發送ajax請求並將響應信息再通過postMessage傳回requestA.html
// iframeB.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>iframeB</title> <link rel="stylesheet" href=""> <style type="text/css"> #iframeA { width: 400px; height: 300px; display: none; } </style> </head> <body> <h1>iframeB</h1> <script type="text/javascript"> window.addEventListener(‘message‘, function (message) { //接受請求參數 request(message.data) }, false) function callbackRequestA(data) { parent.postMessage(data, ‘http://127.0.0.1:3005/‘) //將響應信息通過postMessage傳給requestA.html } function request(api) { var ajaxObj var data if (window.XMLHttpRequest) { ajaxObj = new XMLHttpRequest } else { ajaxObj = new ActiveXObject("Microsoft.XMLHTTP") } ajaxObj.onreadystatechange = function() { if (ajaxObj.readyState === 4 && ajaxObj.status === 200) { data = ajaxObj.responseText callbackRequestA(data) } } ajaxObj.open(‘get‘, ‘http://127.0.0.1:3006/‘ + api, true) ajaxObj.send() } </script> </body> </html>
實現成果
和第一種方法相比明顯的簡潔了很多,不過這種方法只能在html5中使用,兼容性在ie8+。
實現路線總結:requestA.html————iframeB.html————hostB————iframeB.html————requestA.html
第二種方法:對於相同主域,不同子域的情況,比如 http://www.a.com 和 http://a.com 。可以通過設置兩個頁面 document.domain = ‘http://a.com‘ 使他們指向相同的域名,之後就可以直接獲取相互的window進行通信了。因為這種方式需要配合域名來驗證,
所以就不再這裏進行演示。
搭配form實現ajax請求
運用iframe的特性可以搭配form表單模擬實現ajax請求。
通過form表單submit將表單數據提交給action指向的地址時候,如果沒有指定form的target屬性時頁面會自動跳轉到form的action所指向的地址,導致頁面刷新或者跳轉,而對大部分請求來說這種行為是沒有必要甚至不合理的。ajax的出現就是為了解決這個問題,通過ajax可以在頁面不跳轉/刷新的前提下達到向服務器進行請求的目的。
不過不使用ajax也是可以通過iframe來實現相同的效果,首先需要將form表單的target屬性指向頁面內嵌的一個iframe(target屬性可以指定在哪個頁面顯示服務器返回的數據,不指定則默認在當前頁面跳轉到action指定的頁面顯示返回的數據),然後在iframe中獲取到服務器返回的數據並傳遞給主頁面。這樣就做到了不用刷新頁面也可以和服務器進行通信的效果。
對iframe的探究