实现跨域请求的方案--JSONP
JSONP简介
JSON with Padding,是一种借助于 script
标签发送跨域请求的技巧。
其原理:
-
script的src标签是可以发跨域请求的,借助
script
标签src请求服务端上的接口 -
服务端的接口返回JavaScript 脚本,并附上要返回的数据。
以后绝大多数情况都是采用 JSONP 的手段完成不同源地址之间的跨域请求,它其实并不是ajax请求。
让script标签的src指向一个接口
前端:让script标签的src指向一个后端接口的地址;
后端:接口的返回值是一个js函数调用语句
前端页面
<script src="http://localhost:3000/getTime"></script>
注意:
-
script标签中的src会指向一个后端接口的地址。由于script标签并不会导致跨域问题,所以这里的请求是可以正常发送和接收的。
后端接口
const express = require('express'); const app = express(); app.get('/gettime', (req, res) => { res.end(`alert(1)`); }) app.listen(3000, () => { console.log('你可以通过http://localhost:3000来访问...'); });
注意:
-
后端接口的返回值是一个特殊的字符串: 一个刻意拼写的js函数调用语句
传递函数名到后端
前端:让script标签的src指向一个后端接口的地址,并附加函数名;
后端:接口的返回值是一个js函数调用语句
目标:当请求成功时,执行前端指定的函数
前端页面
<script> function fn(){ console.log() } </script> <script src="http://localhost:3000/getTime?callback=fn"></script>
注意:
-
在前端自己定义一个函数,把函数名传给后端
-
使用get方式传参,并且参数名是callback。这个参数名要与后端保持一致。
后端接口
const express = require('express'); const app = express(); app.get('/gettime', (req, res) => { let { callback } = req.query; res.end(`${callback}()`); }) app.listen(3000, () => { console.log('你可以通过http://localhost:3000来访问...'); });
注意:
-
后端接口接收函数名,并返回一个字符串,其内容是
函数调用语句
后端回传数据
前端:让script标签的src指向一个后端接口的地址,并附加函数名;
后端:接口的返回值是一个js函数调用语句,并附加实参;
目标:当请求成功时,执行前端指定的函数
前端页面
<script> function dosomething(rs) { console.log(rs); } </script> <script src="http://localhost:3000/getTime?callback=dosomething"></script>
注意:
-
script标签中的src会指向一个后端接口的地址。由于script标签并不会导致跨域问题,所以这里的请求是可以常发送的。
-
把前端的函数名传给后端
后端接口
const express = require('express'); const app = express(); app.get('/gettime', (req, res) => { let { callback } = req.query; let data = JSON.stringfy( {a:1,b:2} ) res.end(`${callback}(${data})`); }) app.listen(3000, () => { console.log('你可以通过http://localhost:3000来访问...'); });
注意:
-
接收函数名,组装一个特殊的字符串:
函数调用语名
-
把要回传的参数转成字符串,并嵌入返回值,当作函数的实参。
jQuery封装的jsonp
jquery中的ajax已经封装好了的jsonp方式。你可以直接使用,具体来说就是给ajax请求添加一个dataType属性,其值为"jsonp"。示例如下:
前端页面
$.ajax({ type: 'GET', url: 'http://localhost:4000/getTime', success: function (result) { console.log(result); }, dataType: 'jsonp' // 必须要指定dataType为jsonp });
后端接口
const express = require('express'); const app = express(); app.get('/gettime', (req, res) => { let { callback } = req.query; let data = JSON.stringfy( {a:1,b:2} ) res.end(`${callback}(${data})`) }) app.listen(3000, () => { console.log('你可以通过http://localhost:3000来访问...'); });
像express框架已经提供了一个名为jsonp的方法来处理jsonp请求:
const express = require('express'); const app = express(); app.get('/gettime', (req, res) => { let data = {a:1,b:2} res.jsonp(data) }) app.listen(3000, () => { console.log('你可以通过http://localhost:3000来访问...'); });
jquery封装的jsonp原理分析
第一步:产生一个随机函数名;并创建这个函数(jQuery34109553463653123275_1565234364495);
第二步:动态创建script标签,其src就是拼接的后端接口地址及callback值,如果有其它参数,则正常传递
第三步:请求成功返回之后,执行第一步中创建的函数(jQuery34109553463653123275_1565234364495)。这个函数最终会指向$.ajax({success:function(){}}) 中的success
第四步:销毁
第一步中创建的函数及第二步中创建的script标签
-
添加断点调试
-
这里有一段模拟代码(面试,手写jsonp)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>html页面</title> </head> <body> <div class="container"> <h1>jsonp</h1> <div>需要后端接口的配合:http://localhost:3005/jsonp</div> <pre> //--后端测试代码如下 const express = require('express'); const app = express() // 留言板接口 -- 获取所有数据 app.get('/jsonp', (req, res) => { var { callback } = req.query; res.setHeader('content-type', 'application/javascript'); res.end(callback + '({a:1,b:2})'); }); app.listen(3000,()=>{}) </pre> </div> <script> function buildCallBackFunction(options, callbackFunName) { window[callbackFunName] = function(result) { options.success(result); window[callbackFunName] = null; delete window[callbackFunName]; }; } function buildParam(options) { var params = options.params; if (!params) { return ''; } if (typeof params === 'object') { var arr = []; for (var p in params) { arr.push(`p=${params[p]}`); } return arr.join('&'); } else if (typeof params === 'string') { return params; } else { return ''; } } function buildScript(url) { var script = document.createElement('script'); script.setAttribute('src', url); script.onload = function() { document.getElementsByTagName('head')[0].removeChild(script); }; document.getElementsByTagName('head')[0].appendChild(script); } function json(options) { var { url, params, success } = options; var callbackFunName = 'callback_' + Date.now(); var params = buildParam(options); params += !params ? 'callback=' + callbackFunName : '&callback=' + callbackFunName; url += '?' + params; buildCallBackFunction(options, callbackFunName); buildScript(url); } json({ url: 'http://localhost:3005/jsonp/jsonp', // params: 'a=1&b=2', params: { a: 1, b: 2 }, success: function(result) { console.log(result); } }); </script> </body> </html>
实现跨域请求的方案--CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10(ie8通过XDomainRequest能支持CORS)。
通过在被请求的路由中设置header头,可以实现跨域。不过这种方式只有最新的浏览器(IE10)才支持。
app.get('/time', (req, res) => { // // 允许任意源访问,不安全 // res.setHeader('Access-Control-Allow-Origin', '*') // 允许指定源访问 res.setHeader('Access-Control-Allow-Origin', 'http://www.xxx.com') res.send(Date.now().toString()) })
-
这种方案无需客户端作出任何变化(客户端不用改代码),就当跨域问题不存在一样。
-
服务端响应的时候添加一个
Access-Control-Allow-Origin
的响应头 -
如果ajax请求中还附加了cookie,则还需要设置一句:
res.setHeader('Access-Control-Allow-Credentials', 'true');
自行下载使用 npm cors cors - npm
jsonp vs cors 对比
jsonp:
-
不是ajax
-
只能使用
get方式
传参 -
兼容性好
cors:
-
就是ajax
-
支持各种方式的请求(post,get....)
-
浏览器的支持不好