跨域

這篇文章我們來聊聊「跨域」,這個概念在很多公司的面試中會被問到,網(wǎng)上也有數(shù)不清的關(guān)于「跨域」的文章。

那我為什么還要費神去寫一篇?

因為我發(fā)現(xiàn)很多文章都是互相抄襲,寫了一大堆你也沒有搞懂他到底在講什么,基本上技術(shù)文章都有這個通病,不知道是作者覺很多知識是不言自明的,所以不屑于講;還是說其實他自己也沒搞清楚,把別人的文章搬過來,隨便改改,就成自己的了。

廢話不多說了,下面進入正題。

往往我們要解釋清楚一個概念,必須引入另一個概念,「跨域」也是如此。

要搞清楚「跨域」,先要搞清楚什么是「同源策略」(Same-origin policy),為了搞清楚「同源策略」,我們先講一些其他的東西。

我們寫任何程序,本質(zhì)上只做兩件事件:計算數(shù)據(jù)和存儲數(shù)據(jù),CPU 負責計算,內(nèi)存負責存儲。計算機之所以牛逼,不是因為它能做的事情多,而是因為它計算的速度相當快,能存儲的數(shù)據(jù)非常多。
既然計算和存儲都由計算機搞定了,那數(shù)據(jù)呢,數(shù)據(jù)從哪來呢?
數(shù)據(jù)的來源有很多種,比如你可以在程序里自己造數(shù)據(jù),也稱為字面量(literal)數(shù)據(jù);也可以從本機磁盤里讀取文件數(shù)據(jù);可以從內(nèi)存中讀取其他程序或資源的數(shù)據(jù);還可以從網(wǎng)絡服務器上請求數(shù)據(jù),等等。
但是對于前端開發(fā)來說,我們使用 JavaScript 語言來寫程序,一般是操作網(wǎng)頁,獲取數(shù)據(jù)的途徑一般就三種:自己造數(shù)據(jù);讀取當前頁面的數(shù)據(jù);從網(wǎng)絡服務器上請求數(shù)據(jù)。

  • 第一種沒什么好說的,就是寫一堆字面量:const greeting = 'hello world'
  • 第二種也簡單,比如獲取 DOM 元素的數(shù)據(jù):const lists = document.querySelectorAll('li'),或者獲取各種 Storage(Local Storage, Session Storage, IndexedDB, Cookies 等) 里面存的數(shù)據(jù):localStorage.getItem('person_key')
    • 這里存在一種情況,就是在當前頁面里面有 iframe 元素時,iframe 里面本質(zhì)上是嵌入了另一個頁面
    • 這個頁面有它自己的一個全局環(huán)境(window 對象),所以它也有自己完整的一套 DOM,Storage
    • 所以這時如果要從當前頁面讀取這個嵌入頁面的數(shù)據(jù)的話,也會存在是否同源的問題,如果不同源的話,也需要跨域
    • 但是因為 iframe 元素用的不多,所以我們這里先不做考慮
  • 第三種就麻煩一點了,我們一般是發(fā)送 Ajax 請求來獲取服務器端的數(shù)據(jù)

我們重點討論第三種方式。

要發(fā)送 Ajax 請求,就必然要指定一個 URL,這樣才能告訴瀏覽器的 Ajax 引擎去哪獲取數(shù)據(jù)。

先說一個概念,我們寫的一段 JavaScript 程序,不管你是用原生 JavaScript 和最原始的方式寫(script 標簽),還是用各種庫、框架、構(gòu)建工具寫,最終都必然要運行在某個頁面上,這個頁面也必然對應著一個它自己的 URL。

那么這時候問題就來了,如果你在 Ajax 請求中指定的這個 URL 跟你當前運行這段 JavaScript 程序的頁面的 URL 不同源的話,你的這次請求很可能就會被瀏覽器拒絕(在沒有實現(xiàn)跨域的情況下,一定會被拒絕),拒絕的表現(xiàn)就是在控制臺給你報一個錯,當然數(shù)據(jù)也拿不到。

問題又來了,什么叫同源,什么叫不同源?為什么不同源瀏覽器會拒絕呢?這就終于涉及到了我們在文章開頭說的「同源策略」。

讓我們先在腦中有一個基本的概念,雖然我們現(xiàn)在可能還不清楚同源的概念,但是既然涉及到相同和不相同,那一定要掌握兩方面的信息才能得出是否相同的結(jié)論:誰跟誰做比較?比較的東西是什么?比如「你跟你爸的長相是否相同」,比較的雙方就是你和你爸,比較的東西就是你們的長相。
具體到同源這個概念,用來做比較的雙方分別是:

  • 在當前頁面下運行的 JavaScript 程序中,發(fā)送 Ajax 請求的代碼里面指定的 URL
  • 當前頁面的 URL

比較什么東西呢?就是同源里的「源」(origin),要知道什么是「源」,我們先看一下 URL 的組成

我們知道,一個完整的 URL 由以下幾部分組成
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]

其中協(xié)議(scheme),域名(host),端口號(port)共同組成了「源」,所以是否同源就是比較它們?nèi)齻€。
這三者必須完全相同,兩個 URL 才算是同源,只要有一個不相同,就是不同源。判斷是否完全相同是按照字符逐個比較,字符沒有完全匹配即是不相同。
比如域名 127.0.0.1localhost 都表示本機域名,但是因為它們的字符不同,所以也算不同源。
具體例子有很多,我就不寫了,網(wǎng)上一大把,寫這個沒意思。

知道了什么是同源和不同源,我們再來說什么是「同源策略」。

首先要知道同源策略是瀏覽器實現(xiàn)的一種網(wǎng)頁安全機制,所謂策略或者說機制,就是瀏覽器根據(jù)某些條件和計劃來做出對應的行為,這樣說有點虛。舉個例子:你爸跟你說,下次考試你要是考到了全班前三名,就獎勵你一個蘋果手機,你要是不及格,就給你一頓揍。這就是你爸制定的一個激勵你學習的策略。
具體到同源策略,就是瀏覽器說,你要是給我的 URL 同源,我就給你數(shù)據(jù);要是不同源,呵呵,我就給你報錯。

雖然它是一種實現(xiàn)(implementation),但是并不存在一個與之對應的規(guī)范(specification),這里插一句,說一下什么是規(guī)范,什么是實現(xiàn)。

舉個例子,如果說「規(guī)范」就是一份說明怎么制造一個杯子的文件(用什么材質(zhì),什么結(jié)構(gòu),制作工序等等),那么「實現(xiàn)」就是根據(jù)這個規(guī)范做出來的杯子。
在 Web 里面,制定規(guī)范的都是一些非盈利性的組織和機構(gòu),比如 W3C,ECMA 等等,實現(xiàn)規(guī)范的就是各大瀏覽器廠商,比如 Chrome, IE, FireFox 等等,對某個規(guī)范的實現(xiàn)就是某個具體的功能,比如 HTML 規(guī)范的實現(xiàn)就是瀏覽器能正確解析 HTML 文件,將它們顯示成頁面的這個功能。
需要注意的是,實現(xiàn)并不一定完全符合規(guī)范,就好比你生產(chǎn)一個杯子,如果消費者喜歡有把手的杯子,但是規(guī)范上又沒說要加把手,這時候你很可能會不按照規(guī)范,做一個有把手的杯子出來。
Web 里面同樣如此,有些規(guī)范里有的內(nèi)容瀏覽器沒有實現(xiàn),有些規(guī)范里沒有的內(nèi)容,瀏覽器又實現(xiàn)了。說白了規(guī)范對實現(xiàn)沒有約束力,我覺得你好我就實現(xiàn),覺得不好壓根不理你。

扯遠了,回到同源策略。前面說到同源策略沒有規(guī)范,所以瀏覽器廠商各自的實現(xiàn)并不完全一樣,但基本目的是一樣的:一個頁面中的 JavaScript 程序,無法訪問到跟它不同源的另一個頁面(或者URL)里面的數(shù)據(jù)。這里所說的數(shù)據(jù),就是我們前面說的 DOM 元素數(shù)據(jù),Storage 數(shù)據(jù),還有 Ajax 請求的數(shù)據(jù)。

那么問題來了,瀏覽器為什么要這么做呢?

其實很好理解,它是出于網(wǎng)絡安全的考慮。想像一下如果沒有同源策略,那任何一個網(wǎng)頁都可以通過編寫 JavaScript 程序來獲取其他網(wǎng)頁的敏感數(shù)據(jù),這不是很恐怖嗎?假如你剛剛登陸了網(wǎng)銀,然后又訪問了某個惡意網(wǎng)頁,那這個惡意網(wǎng)頁就可以訪問甚至修改你的網(wǎng)銀賬戶數(shù)據(jù)。這樣的話,網(wǎng)絡就一點都不安全了。

雖然瀏覽器搞的這個同源策略看起來很美好,但凡事有利就有弊,有時候我們也不希望它存在。

比如我要發(fā)送 Ajax 請求訪問某個 URL 來獲取數(shù)據(jù),這些數(shù)據(jù)是別人網(wǎng)站提供的公共數(shù)據(jù),沒有安全性問題,誰都能訪問(比如一些網(wǎng)部的天氣數(shù)據(jù) URL 接口,新聞數(shù)據(jù) URL 接口等等),但是因為同源策略,不好意思,瀏覽器直接給你拒了。
還比如我們公司是百度,baidu.com 這個主域名下有很多子域名:news.baidu.com, tieba.baidu.com 等等,那我的主域名頁面要獲取子域名頁面的數(shù)據(jù),還有子域名頁面相互之間通信都會受到同源策略的限制。

那這要怎么辦呢?這時候就終于引出了我們要說的重點:「跨域」

要解決同源策略對于不同源網(wǎng)頁之間通信的限制問題,就要通過跨域來處理。所謂跨域就是下面我們要說的解決方案的統(tǒng)稱,是一套技術(shù)的集合。
通過前面的學習,我們發(fā)現(xiàn)跨域這個詞其實并不太準確,應該叫「跨源」,因為域名僅僅是源的一部分而已(其他兩部分是協(xié)議名和端口號),但是我們在實際開發(fā)中遇到最多的還是域名不同導致的不同源,所以才有了跨域這個說法(僅僅是筆者的猜測)。

好了,大家可以發(fā)現(xiàn),我們花了很多時間來解釋概念性問題,寫了這么多還沒說到跨域應該怎么跨,別的技術(shù)文章到這估計都把跨域的幾種方式總結(jié)完了,但是那樣又有什么意義呢?你總結(jié)了十來種方式,其實工作中用到的也就那么一兩種,你是人不是機器,那么多技術(shù)方案你都要背下來嗎?
很多東西搞清楚 what 和 why 比 how 更重要,因為 how 是不斷變化的,但是原理性的東西一般不會輕易改變。掌握了 what 和 why,再來學 how 就很輕松了。

其實寫到這里,后面跨域的幾種方案,都可以在網(wǎng)上找到詳細的說明。筆者主要想詳細說一下 JSONP 和 CORS 這兩種常用的方法,今天太累了,明天再寫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 什么是跨域? 2.) 資源嵌入:、、、等dom標簽,還有樣式中background:url()、@font-fac...
    電影里的夢i閱讀 2,459評論 0 5
  • 跨域問題的場景和解決方案多種多樣,只要是做前端開發(fā),總會遇到。而且面試時也是必問的問題。所以自己學習總結(jié)記錄一下。...
    花開_陳鳳娟閱讀 778評論 0 0
  • 前言 關(guān)于前端跨域的解決方法的多種多樣實在讓人目不暇接。以前碰到一個公司面試的場景是這樣的,好幾個人一起在等待面試...
    andreaxiang閱讀 525評論 1 4
  • 壹 兒子很喪氣地回家了。娘笑著把鍋從爐子上面端下來,一樣一樣地端出放到桌子上,嘴里還說著:這豬肝可是對眼睛好,剩下...
    瓦艾特閱讀 341評論 0 1
  • 我的心情非常的不好
    賈玉君閱讀 184評論 0 0

友情鏈接更多精彩內(nèi)容