純前端實現(xiàn)網(wǎng)頁跨域交互數(shù)據(jù)的兩個方法

背景

我們Web前端開發(fā)者一般都會有這樣的常識:同一瀏覽器打開了兩個不同域名的網(wǎng)頁,兩個頁面之間被沙箱隔了乳绕,因此是無法進(jìn)行數(shù)據(jù)交互的绞惦。這兩個頁面一旦嘗試用JS相互訪問資源,便會報錯洋措。

除非济蝉,你是借助服務(wù)端的能力(例如:AJAX或者WebSocket之類),間接地進(jìn)行通訊菠发。

瀏覽器限制站點跨域訪問是出于保護(hù)客戶端信息安全需要王滤,這一點無可厚非。但我們Web前端的某些開發(fā)場景滓鸠,確實有需求跨域做交互雁乡,怎么辦呢?

下面分享一下我的做法糜俗。

PS. 因為今天我想討論的是純前端的技術(shù)解決方案踱稍,依賴服務(wù)端解決辦法忽略掉。Ajax 和 WebSocket悠抹,你們都要出局啦珠月!

兩種跨域場景

Web跨域的場景通常有兩種:

  • 1.iframe的內(nèi)嵌頁面與主窗口頁面之間的通訊
  • 2.通過window.open打開新窗口與母窗口(opener)之間的通訊

我下面的案例會分別針對這兩種場景提供對應(yīng)的方案。

方案1:使用window.postMessage的API

window.postMessage 是 html5 引入的新API楔敌,它允許來自不同源的腳本采用異步方式進(jìn)行有效的通信啤挎,可以實現(xiàn)跨文本文檔、多窗口卵凑、跨域消息傳遞庆聘,多用于窗口間數(shù)據(jù)通信胜臊。

從上面的介紹可以看出來,這算是w3c標(biāo)準(zhǔn)的跨域通信的解決方案伙判。

首先我們模擬一個跨域場景:假設(shè)我有兩個網(wǎng)頁象对,訪問地址分別為

1、處理iframe跨域的代碼示例

  • A頁面代碼:
<html>
    <head>
        <title>Example CORS for iframe</title>
    </head>
    <body>
        <button>Post Message</button>
        <p></p>
        <iframe src="http://b.example.com/test.html" width="500" height="300"></iframe>
    </body>
    <script>
        const iframe = document.querySelector('iframe');
        const button = document.querySelector('button');
        const msg = document.querySelector('p');
        button.addEventListener('click', () => {
            iframe.contentWindow.postMessage('hello', '*');
        });

        window.addEventListener('message', (event) => {
            msg.innerText = `Message from iframe: ${event.data}`;
        });

    </script>
</html>
  • B頁面代碼:
<html>
    <head>
        <title>Inner Page</title>
    </head>
<body>
    <h1>Hi, this is an inner page</h1>
    <p></p>
    <div></div>
</body>
</html>
<script>

window.onload = ()=>{

    setInterval(()=>{
        var time = new Date().getTime().toString();
        document.querySelector('p').innerHTML = `Current Timestamp:${time}`;
        
        window.parent.postMessage(time,'*');    
    },1000);

    window.addEventListener('message',(e)=>{    
        document.querySelector('div').innerHTML = `Message from Parent:${e.data}`;  
    });
}
</script>

2杨何、處理彈窗的跨域的代碼示例

  • A頁面代碼:
<html>
    <head>
        <title>Example CORS for window.open</title>
    </head>
    <body>
        <button>Post Message</button>
        <p></p>
    </body>
    <script>
        var childWindow;
        function openWindow(url, width, height) {
            var h = screen.height * height / 100;
            var w = screen.width * width / 100;
            var x = (screen.width - w) / 2;
            var y = (screen.height - h) / 2;

            return window.open(url, '_blank', 'width=' + w + ',height=' + h + ',top=' + y + ',left=' + x);
        }

        window.onload = function() {
            childWindow = openWindow('http://b.example.com/test.html', 40, 30);
        };

        const button = document.querySelector('button');
        const msg = document.querySelector('p');
        button.addEventListener('click', () => {
            childWindow.postMessage('hello', '*');
        });

        window.addEventListener('message', (event) => {
            msg.innerText = `Message from iframe: ${event.data}`;
        });

    </script>
</html>
測試結(jié)果
  • B頁面代碼:
<html>
    <head>
        <title>Inner Page</title>
    </head>
    <body>
        <h1>Hi, this is an inner page</h1>
        <p></p>
        <div></div>
    </body>
</html>
<script>
    window.onload = ()=>{
        setInterval(()=>{
            var time = new Date().getTime().toString();
            document.querySelector('p').innerHTML = `Current Timestamp:${time}`;
            window.opener.postMessage(time,'*');
        },1000);

        window.addEventListener('message',(e)=>{            
            document.querySelector('div').innerHTML = `Message from Parent:${e.data}`;
        })
    }    
</script>
測試結(jié)果

方案2:利用window.name傳遞信息

處理iframe跨域的代碼示例(一)

  • A頁面代碼:
<html>
    <head>
        <title>Example CORS for iframe</title>
    </head>
    <body>
        <button>Post Message</button>
        <p></p>
        <iframe src="http://b.example.com/test.html" width="500" height="300"></iframe>
    </body>
    <script>
        const iframe = document.querySelector('iframe');
        const button = document.querySelector('button');
        const msg = document.querySelector('p');
        button.addEventListener('click', () => {
            iframe.contentWindow.title = 'ToChild:hello';
        });

        setInterval(() => {
            var name = iframe.contentWindow.name;
            if(name && name.startsWith('ToParent:')) {
                msg.innerHTML = 'Message from iframe:'+name.substring(9);
            }
        }, 200);

    </script>
</html>
  • B頁面代碼:
<html>
    <head>
        <title>Inner Page</title>
    </head>
    <body>
        <h1>Hi, this is an inner page</h1>
        <p></p>
        <div></div>
    </body>
</html>
<script>
    window.onload = ()=>{
        setInterval(()=>{
            var name = window.name;
            if(name && name.startsWith('ToChild:')){
                var message = name.substring(8);
                document.querySelector('div').innerHTML = `Message from Parent:${message}`;
            }
            var time = new Date().getTime().toString();
            document.querySelector('p').innerHTML = `Current Timestamp:${time}`;
            window.name=`ToParent:${time}`;
        },1000);
    }    
</script>
  • 注意:不出意外的話就要出意外了……


    測試出現(xiàn)報錯

以上報錯證明:非辰此可惜,現(xiàn)代瀏覽器已經(jīng)把這個口子給堵上了危虱,這個方法僅適用于古代的瀏覽器羊娃。但是不要沮喪,既然我敢發(fā)出來埃跷,肯定還有別的招數(shù)蕊玷。雖然不完太美,能夠使用的范圍有限弥雹,聊勝于無吧垃帅。

處理iframe跨域的代碼示例(二)

這個方案我們需要增加一個跟頁面A同域的空白頁面作為跳板,例如:

  • http://a.example.com/empty.html
    empty.html頁面完全為空剪勿,不包含任何代碼也沒關(guān)系贸诚。做好這一步后,我們來重寫A厕吉、B兩個頁面的代碼:

  • A頁面代碼:

<html>
    <head>
        <title>Example CORS for iframe</title>       
    </head>
    <body>
        <button>Post Message</button>
        <p></p>
    </body>
</html>

<script>
    const msg = document.querySelector('p');
    function openFrame(url, fn) {
        var iframe = document.createElement('iframe');
        iframe.style.display = 'none';
        var state = 0;
        var cb = fn;
        iframe.onload = function() {
            if(state === 1) {
                cb(iframe.contentWindow.name);
                iframe.contentWindow.document.write('');
                iframe.contentWindow.close();
                document.body.removeChild(iframe);
            } else if(state === 0) {
                state = 1;
                setTimeout(() => {
                    iframe.contentWindow.location = 'empty.html';
                },300)
            }
        };
        iframe.src = url;
        document.body.appendChild(iframe);
    }    
    setInterval(() => {
        var url = 'http://b.example.com/test.html';
        openFrame(url, (data)=> {
            message = data;
            if(data && data.startsWith('ToParent:')) {
                msg.innerHTML = 'Message from iframe:'+data.substring(9);
            }
        });  
    }, 1000);
</script>
  • B頁面代碼:
<script>
    var time = new Date().getTime().toString();
    window.name=`ToParent:${time}`;
</script>
測試成功

總結(jié)

  • 以上兩種方法酱固,便是純前端實現(xiàn)的跨域通訊。
  • window.postMessage是瀏覽器的標(biāo)準(zhǔn)API头朱,它實現(xiàn)的通訊非常實時运悲,而且代碼的實現(xiàn)方式也相當(dāng)優(yōu)雅,強(qiáng)烈推薦大家使用项钮。當(dāng)然班眯,使用的時候要注意瀏覽器兼容性,兼容舊版的瀏覽器比如IE烁巫,那么可以考慮用另外一個方案鳖敷。
  • 利用window.name也確實能夠?qū)崿F(xiàn)在iframe跨域通訊的目標(biāo),盡管代碼看起來比較繞程拭。不過定踱,也確實是打了折扣,因為信息傳遞只能有單向的通道(A頁面獲取B頁面的信息)恃鞋。當(dāng)然崖媚,如果想實現(xiàn)A=>B傳遞信息亦歉,也有其他手段,譬如利用URL來傳遞參數(shù)畅哑‰瓤總的來說,該方案應(yīng)該屬于遠(yuǎn)古時代的產(chǎn)物荠呐,如果不考慮兼容舊的瀏覽器赛蔫,不推薦大家在項目上使用∧嗾牛可以把它放到代碼的博物館中呵恢,慢慢欣賞,就好比今天我們像看古人如何鉆木取火一般媚创。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渗钉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子钞钙,更是在濱河造成了極大的恐慌鳄橘,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芒炼,死亡現(xiàn)場離奇詭異瘫怜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)本刽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門宝磨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盅安,你說我怎么就攤上這事唤锉。” “怎么了别瞭?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵窿祥,是天一觀的道長。 經(jīng)常有香客問我蝙寨,道長晒衩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任墙歪,我火速辦了婚禮听系,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘虹菲。我一直安慰自己靠胜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著浪漠,像睡著了一般陕习。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上址愿,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天该镣,我揣著相機(jī)與錄音,去河邊找鬼响谓。 笑死损合,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的娘纷。 我是一名探鬼主播嫁审,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼失驶!你這毒婦竟也來了土居?” 一聲冷哼從身側(cè)響起枣购,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤嬉探,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后棉圈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涩堤,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年分瘾,在試婚紗的時候發(fā)現(xiàn)自己被綠了胎围。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡德召,死狀恐怖白魂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情上岗,我是刑警寧澤福荸,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站肴掷,受9級特大地震影響敬锐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜呆瞻,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一台夺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧痴脾,春花似錦颤介、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丰泊。三九已至,卻和暖如春始绍,著一層夾襖步出監(jiān)牢的瞬間瞳购,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工亏推, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留学赛,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓吞杭,卻偏偏與公主長得像盏浇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子芽狗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容

  • 前端開發(fā)中童擎,跨域使我們經(jīng)常遇到的一個問題滴劲,也是面試中經(jīng)常被問到的一些問題,所以顾复,這里班挖,我們做個總結(jié)。小小問題芯砸,不足...
    Nealyang閱讀 470評論 0 0
  • 1萧芙、JSONP 受瀏覽器同源策略的限制,網(wǎng)頁無法向其他域發(fā)送ajax請求假丧,但在頁面中引入其他域的腳本是可以的双揪,最常...
    cfighter閱讀 503評論 0 1
  • 前端跨域問題 瀏覽器的同源策略 提到跨域不能不先說一下”同源策略”。何為同源包帚?只有當(dāng)協(xié)議渔期、端口、和域名都相同的頁面...
    單只蝴蝶_569d閱讀 370評論 0 0
  • 什么是跨域 瀏覽器出于安全方面的考慮婴噩,只允許客戶端與本域(同協(xié)議擎场、同域名、同端口几莽,三者缺一不可)下的接口交互迅办。不同...
    七里之境閱讀 1,339評論 0 0
  • 同源:協(xié)議、域名章蚣、端口相同 跨域通信: js進(jìn)行DOM操作站欺,通信時如果目標(biāo)與當(dāng)前窗口不滿足同源條件姨夹,瀏覽器為了安全...
    silly鴻閱讀 614評論 0 1