该篇讲述了同源策略,再到跨域的解决。学习分析了流行的jsonp方案从源码上加深了对jsonp的理解和认识
同源策略
要理解跨域,先要了解一下“同源策略”。所谓同源是指,域名,协议,端口相同。所谓“同源策略“,简单的说就是基于安全考虑,当前域不能访问其他域的东西。
http://www.abc.com:8080/home?k=v
这么一个url地址,由协议,域名,端口,路径等部分组成。怎么来区分跨域与否呢? 这里简单列了一下:
跨域
不跨域
跨域情况
1 2 3 4 5 6 7 8 9 10 11
| var btn = document.getElementById('btn'); btn.onclick = function() { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { console.log(xhr.responseText); } }; xhr.open('get', 'https://api.douban.com/v2/book/search?q=javascript&count=1', true); xhr.send(); }
|
但<img>
的src(获取图片),<link>
的href(获取css),<script>
的src(获取javascript)这三个都不符合同源策略,它们可以跨域获取数据。这里要介绍的JSONP就是利用<script>
的src来实现跨域获取数据的。
Jsonp
JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写。
JSONP实现跨域请求的原理简单的说,就是动态创建<script>
标签,然后利用<script>
的 src 不受同源策略约束来跨域获取数据。
JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。
1 2 3 4 5
| var script = document.createElement("script"); script.src = "https://api.douban.com/v2/book/search?q=javascript&count=1&callback=handleResponse"; document.body.insertBefore(script, document.body.firstChild);
function handleResponse(response){}
|
JSONP目前还是比较流行的跨域方式,虽然JSONP使用起来方便,但是也存在一些问题:
首先, JSONP 是从其他域中加载代码执行。如果其他域不安全,很可能会在响应中夹带一些恶意代码,而此时除了完全放弃 JSONP 调用之外,没有办法追究。因此在使用不是你自己运维的 Web 服务时,一定得保证它安全可靠。
其次,要确定 JSONP 请求是否失败并不容易。虽然 HTML5 给 <script>
元素新增了一个 onerror事件处理程序,但目前还没有得到任何浏览器支持。为此,开发人员不得不使用计时器检测指定时间内是否接收到了响应。
下面来看看 jsonp 的源码解读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
|
let count = 0
function noop() {}
function jsonp(url, opts, fn) { if (typeof opts === 'function') { fn = opts opts = {} }
if (!opts) opts = {}
const prefix = opts.prefix || '__jp'
let id = opts.name || (prefix + (count++))
let param = opts.param || 'callback' let timeout = null != opts.timeout ? opts.timeout : 60000 const enc = encodeURIComponent let target = document.getElementsByTagName('script')[0] || document.head let script let timer
if (timeout) { timer = setTimeout(() => { cleanup() if (fn) fn(new Error('Timeout')) }, timeout) }
function cleanup() { if (script.parantNode) script.parantNode.removeChild(script) window[id] = noop if (timer) clearTimeout(timer) }
function cancel() { if (window[id]) { cleanup() } }
window[id] = function(data) { cleanup() if (fn) fn(null, data) }
url += `${~url.indexOf('?') ? '&' : '?'}${param}=${enc(id)}` url = url.replace('?&', '?')
script = document.createElement('script') script.src = url target.parentNode.insertBefore(script, target)
return cancel }
module.exports = jsonp
|
参考资料
开源jsonp实现 github