在浏览器脚本的概念没有出现以前,全部的网页都是静态的。咱们知道浏览器的工做模式是:php
看起来就像下面这样:jquery
Client Request
+-------------+ +--------+
+------+ | User Agent | +--------------------------------> | |
| User +------> | | Server |
+--^---+ | (Browser) | <--------------------------------+ | |
| +-------+-----+ +--------+
| | Server Response
| |
| |
| +---------v--------+
| | Close Connection |
| +---------+--------+
| |
| |
| +--------v--------+
^---------+ Render response |
+-----------------+
复制代码
咱们看到,一旦用户代理(浏览器)关闭了和服务器之间的连接以后,客户端和服务器之间将不能继续通讯。编程
为了让页面能够给用户带来更多的交互,浏览器开发厂商们制造出了名为浏览器脚本的东西。好比你在浏览一个页面的时候,你以为页面的字体过小了。在静态页面的时候,页面制做者在右上角给你提供了名为 “放大字体” 的按钮,你点击那个按钮,而后开启一轮新的请求,显著的说就是说你感受到浏览器刷新了。这实际上是浏览器从新从服务器加载页面的资源,只不过这一次的资源是用于显示字体放大后的页面。(尽管这个例子如今看来有些奇怪,由于若是仅仅是改变页面字体大小的话,彷佛直接操做 element.style.fontSize
就能够了,不过请求另一个包含更大字体的内联 CSS 页面在最终效果上也是说得通的,因此这里还请多多包含了。)json
浏览器脚本就是一小段由浏览器执行的代码,页面制做者将这一小段代码,和网页面的内容(好比一篇优美的散文,和它右上角的 “放大字体” 按钮)一块儿返回给浏览器。浏览器接收到页面资源后,首先就是先将散文和 “放大字体” 按钮显示出来。注意到返回的内容实际上还有一段由浏览器执行的代码,页面制做者在这段带代码中告诉浏览器:若是用户点击了 “放大字体” 按钮,那么你就将页面的字体放大。因而,当你点击 “放大字体” 按钮以后,浏览器严格执行页面制做者在脚本中撰写的内容 - 将页面的字体放大。浏览器
注意在静态页面中浏览器和服务器之间的通讯过程。浏览器在向服务器发起了对页面的请求以后,在服务器没有将页面的内容返回以前,页面是没法被显示出来的,最显著的特征就是咱们在点击了浏览器的 “刷新” 按钮以后,页面会 “白屏” 一小段时间。bash
起初浏览器脚本是没有网络通讯的功能的,只能作一些页面的特效,好比“点击按钮放大了字体”。不过浏览器厂商发现,若是给脚本赋予网络通讯的功能,将使得页面制做者能够给用户提供更好的页面交互体验。因而在早期的 IE 浏览器中,首先赋予了浏览器脚本的通讯功能。服务器
浏览器脚本能够和服务器进行网络通讯以后,页面制做者能够作出具备更好体验的页面。好比你如今须要搜索商品,假设是要买一本编程的书,你在网页的搜索框中输入了 “编程的数”,很明显是输错了,你将 “书” 错输成了 “数”。在你点击了 “搜索” 按钮以后,进太短暂的白屏以后,页面中显示了:网络
找不到关于 “编程的数” 的产品,你是否是要找 “编程的书”app
很不错,网站给了咱们一个提示,这样咱们就能够发现本身的输入错误。不过这个体验仍是有待提升的,由于每一次的搜索都会有一个短暂的 “白屏”,在白屏期间用户只能等待。在浏览器脚本能够通讯以后,搜索就能够以一个异步的方式进行:异步
这样的话,用户没必要在搜索时面对页面的刷新时的 “白屏” 了,有一个提示框告诉用户稍等片刻。
为了定位网络上的资源,咱们采用了统一资源定位符 URL,就像是一个门牌号同样, URL 标识出资源在网络上的位置。咱们浏览的网页,其中的内容可能会来自不一样的提供者,好比散文来自一位做家,而其中的配图来自一位美术家。散文的 URL 是 http://writer.com/new-world
,配图的 URL 是 http://artist.com/new-world
。
咱们须要有一种方式将网络上的资源(好比散文和图画)标识出来,区别它们是来自于不一样的做者。若是咱们将颗粒度定位到每个独立的资源,理论上是可行的,可是咱们知道做家不可能只有一篇散文,而美术家也不会只有一幅画。因而咱们选择了使用:通讯协议,完整的域名,以及端口号去描述一个源,只有三者都相同,才标识两个资源是同源的。
下面的几个资源是同源的:
http://example.com/
http://example.com:80/
http://example.com/path/file
复制代码
下面的资源是不一样源的:
http://example.com/
http://example.com:8080/
http://www.example.com/
https://example.com:80/
https://example.com/
http://example.org/
http://ietf.org/
复制代码
如今知道了同源,那么同源策略是什么意思呢?同源策略就是,两个不一样源的资源相互是不能访问对方的资源的。同源策略主要就是限制脚本的网络访问。
好比咱们打开了一个页面 http://example.com
,这个页面有两段脚本,一个段使用的内联的方式称为 A,它主要就是在用户点击了按钮以后显示一段文字,告诉用户点击了按钮;另外一段做为外部资源进行加载称为 B,B 是 A 的基础代码,好比 B 是 jQuery,它被放在了 http://cdn.jquery.com
上。首先咱们知道,这两段代码若是按照同源的定义,确定是不一样源的。也就是说咱们在 http://example.com
的页面上是不能加载 http://cdn.jquery.com
上的资源的。
好像与现实状况有点矛盾。之因此如今能够,是由于浏览器为了适应实际的生产状况,放宽了对同源策略的检查,由于咱们知道,不可能将全部的资源都放在同一台机器上。那么在页面彻底加载好以后,页面中的脚本(内联的和外部引入)的都被浏览器概括到了和当前页面相同的源,都属于 http://example.com
了。这么作的意思就是,脚本没法访问与之不一样源的资源,也就是此时的脚本(内联的和外部引入的)没法访问资源 https://example.com/user-info
。
有时好比上面的例子,咱们确实须要在脚本中加载和当前页面不一样源的资源,好比在 http://example.com
页面中使用脚本加载 https://example.com/user-info
中的内容。那么如何绕过浏览器的同源策略呢?
咱们知道直接在页面中载入不一样源的外部资源是能够的,那么咱们就能够动态的载入一段外部的脚本。
首先,咱们的 http://example.com
中有这么一段脚本:
(function () {
window['showNickname'] = function (json) {
alert(json['nickname']);
};
var userInfoServiceUrl = 'https://example.com/user-info';
var doCrossSiteRequest = function (url, callback) {
var script = document.createElement('script');
script.src = url + '?callback=' + callback;
var head = document.getElementsByTagName('head');
if (head[0]) {
head.append(script);
}
};
document.querySelector('#btnShowNickName').addEventListener('click', function () {
doCrossSiteRequest(userInfoServiceUrl, 'showNickname');
});
})();
复制代码
而 https://example.com/user-info
的服务端内容为:
<?php
$callback = isset($_GET['callback']) ? $_GET['callback'] : null;
if ($callback === null) die('invalid request');
$userInfo = [
'nickname' => 'net-user'
];
$json = json_encode($userInfo);
echo "{$callback}({$json});";
复制代码
那么在浏览器加载了 https://example.com/user-info
的脚本为,获得的是:
showNickname({"nickname":"net-user"});
复制代码
这就和咱们最早在 http://example.com
留下的 window['showNickname']
对接上了。