跨域

这篇文章我们来聊聊「跨域」,这个概念在很多公司的面试中会被问到,网上也有数不清的关于「跨域」的文章。

那我为什么还要费神去写一篇?

因为我发现很多文章都是互相抄袭,写了一大堆你也没有搞懂他到底在讲什么,基本上技术文章都有这个通病,不知道是作者觉很多知识是不言自明的,所以不屑于讲;还是说其实他自己也没搞清楚,把别人的文章搬过来,随便改改,就成自己的了。

废话不多说了,下面进入正题。

往往我们要解释清楚一个概念,必须引入另一个概念,「跨域」也是如此。

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

我们写任何程序,本质上只做两件事件:计算数据和存储数据,CPU 负责计算,内存负责存储。计算机之所以牛逼,不是因为它能做的事情多,而是因为它计算的速度相当快,能存储的数据非常多。
既然计算和存储都由计算机搞定了,那数据呢,数据从哪来呢?
数据的来源有很多种,比如你可以在程序里自己造数据,也称为字面量(literal)数据;也可以从本机磁盘里读取文件数据;可以从内存中读取其他程序或资源的数据;还可以从网络服务器上请求数据,等等。
但是对于前端开发来说,我们使用 JavaScript 语言来写程序,一般是操作网页,获取数据的途径一般就三种:自己造数据;读取当前页面的数据;从网络服务器上请求数据。

  • 第一种没什么好说的,就是写一堆字面量:const greeting = 'hello world'
  • 第二种也简单,比如获取 DOM 元素的数据:const lists = document.querySelectorAll('li'),或者获取各种 Storage(Local Storage, Session Storage, IndexedDB, Cookies 等) 里面存的数据:localStorage.getItem('person_key')
    • 这里存在一种情况,就是在当前页面里面有 iframe 元素时,iframe 里面本质上是嵌入了另一个页面
    • 这个页面有它自己的一个全局环境(window 对象),所以它也有自己完整的一套 DOM,Storage
    • 所以这时如果要从当前页面读取这个嵌入页面的数据的话,也会存在是否同源的问题,如果不同源的话,也需要跨域
    • 但是因为 iframe 元素用的不多,所以我们这里先不做考虑
  • 第三种就麻烦一点了,我们一般是发送 Ajax 请求来获取服务器端的数据

我们重点讨论第三种方式。

要发送 Ajax 请求,就必然要指定一个 URL,这样才能告诉浏览器的 Ajax 引擎去哪获取数据。

先说一个概念,我们写的一段 JavaScript 程序,不管你是用原生 JavaScript 和最原始的方式写(script 标签),还是用各种库、框架、构建工具写,最终都必然要运行在某个页面上,这个页面也必然对应着一个它自己的 URL。

那么这时候问题就来了,如果你在 Ajax 请求中指定的这个 URL 跟你当前运行这段 JavaScript 程序的页面的 URL 不同源的话,你的这次请求很可能就会被浏览器拒绝(在没有实现跨域的情况下,一定会被拒绝),拒绝的表现就是在控制台给你报一个错,当然数据也拿不到。

问题又来了,什么叫同源,什么叫不同源?为什么不同源浏览器会拒绝呢?这就终于涉及到了我们在文章开头说的「同源策略」。

让我们先在脑中有一个基本的概念,虽然我们现在可能还不清楚同源的概念,但是既然涉及到相同和不相同,那一定要掌握两方面的信息才能得出是否相同的结论:谁跟谁做比较?比较的东西是什么?比如「你跟你爸的长相是否相同」,比较的双方就是你和你爸,比较的东西就是你们的长相。
具体到同源这个概念,用来做比较的双方分别是:

  • 在当前页面下运行的 JavaScript 程序中,发送 Ajax 请求的代码里面指定的 URL
  • 当前页面的 URL

比较什么东西呢?就是同源里的「源」(origin),要知道什么是「源」,我们先看一下 URL 的组成

我们知道,一个完整的 URL 由以下几部分组成
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]

其中协议(scheme),域名(host),端口号(port)共同组成了「源」,所以是否同源就是比较它们三个。
这三者必须完全相同,两个 URL 才算是同源,只要有一个不相同,就是不同源。判断是否完全相同是按照字符逐个比较,字符没有完全匹配即是不相同。
比如域名 127.0.0.1localhost 都表示本机域名,但是因为它们的字符不同,所以也算不同源。
具体例子有很多,我就不写了,网上一大把,写这个没意思。

知道了什么是同源和不同源,我们再来说什么是「同源策略」。

首先要知道同源策略是浏览器实现的一种网页安全机制,所谓策略或者说机制,就是浏览器根据某些条件和计划来做出对应的行为,这样说有点虚。举个例子:你爸跟你说,下次考试你要是考到了全班前三名,就奖励你一个苹果手机,你要是不及格,就给你一顿揍。这就是你爸制定的一个激励你学习的策略。
具体到同源策略,就是浏览器说,你要是给我的 URL 同源,我就给你数据;要是不同源,呵呵,我就给你报错。

虽然它是一种实现(implementation),但是并不存在一个与之对应的规范(specification),这里插一句,说一下什么是规范,什么是实现。

举个例子,如果说「规范」就是一份说明怎么制造一个杯子的文件(用什么材质,什么结构,制作工序等等),那么「实现」就是根据这个规范做出来的杯子。
在 Web 里面,制定规范的都是一些非盈利性的组织和机构,比如 W3C,ECMA 等等,实现规范的就是各大浏览器厂商,比如 Chrome, IE, FireFox 等等,对某个规范的实现就是某个具体的功能,比如 HTML 规范的实现就是浏览器能正确解析 HTML 文件,将它们显示成页面的这个功能。
需要注意的是,实现并不一定完全符合规范,就好比你生产一个杯子,如果消费者喜欢有把手的杯子,但是规范上又没说要加把手,这时候你很可能会不按照规范,做一个有把手的杯子出来。
Web 里面同样如此,有些规范里有的内容浏览器没有实现,有些规范里没有的内容,浏览器又实现了。说白了规范对实现没有约束力,我觉得你好我就实现,觉得不好压根不理你。

扯远了,回到同源策略。前面说到同源策略没有规范,所以浏览器厂商各自的实现并不完全一样,但基本目的是一样的:一个页面中的 JavaScript 程序,无法访问到跟它不同源的另一个页面(或者URL)里面的数据。这里所说的数据,就是我们前面说的 DOM 元素数据,Storage 数据,还有 Ajax 请求的数据。

那么问题来了,浏览器为什么要这么做呢?

其实很好理解,它是出于网络安全的考虑。想像一下如果没有同源策略,那任何一个网页都可以通过编写 JavaScript 程序来获取其他网页的敏感数据,这不是很恐怖吗?假如你刚刚登陆了网银,然后又访问了某个恶意网页,那这个恶意网页就可以访问甚至修改你的网银账户数据。这样的话,网络就一点都不安全了。

虽然浏览器搞的这个同源策略看起来很美好,但凡事有利就有弊,有时候我们也不希望它存在。

比如我要发送 Ajax 请求访问某个 URL 来获取数据,这些数据是别人网站提供的公共数据,没有安全性问题,谁都能访问(比如一些网部的天气数据 URL 接口,新闻数据 URL 接口等等),但是因为同源策略,不好意思,浏览器直接给你拒了。
还比如我们公司是百度,baidu.com 这个主域名下有很多子域名:news.baidu.com, tieba.baidu.com 等等,那我的主域名页面要获取子域名页面的数据,还有子域名页面相互之间通信都会受到同源策略的限制。

那这要怎么办呢?这时候就终于引出了我们要说的重点:「跨域」

要解决同源策略对于不同源网页之间通信的限制问题,就要通过跨域来处理。所谓跨域就是下面我们要说的解决方案的统称,是一套技术的集合。
通过前面的学习,我们发现跨域这个词其实并不太准确,应该叫「跨源」,因为域名仅仅是源的一部分而已(其他两部分是协议名和端口号),但是我们在实际开发中遇到最多的还是域名不同导致的不同源,所以才有了跨域这个说法(仅仅是笔者的猜测)。

好了,大家可以发现,我们花了很多时间来解释概念性问题,写了这么多还没说到跨域应该怎么跨,别的技术文章到这估计都把跨域的几种方式总结完了,但是那样又有什么意义呢?你总结了十来种方式,其实工作中用到的也就那么一两种,你是人不是机器,那么多技术方案你都要背下来吗?
很多东西搞清楚 what 和 why 比 how 更重要,因为 how 是不断变化的,但是原理性的东西一般不会轻易改变。掌握了 what 和 why,再来学 how 就很轻松了。

其实写到这里,后面跨域的几种方案,都可以在网上找到详细的说明。笔者主要想详细说一下 JSONP 和 CORS 这两种常用的方法,今天太累了,明天再写。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • 什么是跨域? 2.) 资源嵌入:、、、等dom标签,还有样式中background:url()、@font-fac...
    电影里的梦i阅读 2,320评论 0 5
  • 跨域问题的场景和解决方案多种多样,只要是做前端开发,总会遇到。而且面试时也是必问的问题。所以自己学习总结记录一下。...
    花开_陈凤娟阅读 702评论 0 0
  • 前言 关于前端跨域的解决方法的多种多样实在让人目不暇接。以前碰到一个公司面试的场景是这样的,好几个人一起在等待面试...
    andreaxiang阅读 458评论 1 4
  • 壹 儿子很丧气地回家了。娘笑着把锅从炉子上面端下来,一样一样地端出放到桌子上,嘴里还说着:这猪肝可是对眼睛好,剩下...
    瓦艾特阅读 267评论 0 1
  • 我的心情非常的不好
    贾玉君阅读 159评论 0 0