有很多种方式可以发送HTTP请求,比如以下(及存在的局限):
- 用 form 可以发请求,但是会刷新页面或新开页面;
- 用 a 可以发 get 请求,但是也会刷新页面或新开页面;
- 用 img 可以发 get 请求,但是只能以图片的形式展示;
- 用 link 可以发 get 请求,但是只能以 CSS、favicon 的形式展示;
- 用 script 可以发 get 请求,但是只能以脚本的形式运行。
JSONP
JSONP即“JSON Padding”,当两个网站(如x.com访问y.com,不同域)之间需要访问,可以通过script作为交互方式,具体过程为:
- 请求方(
x.com
前端)定义一个发送请求成功/失败后执行的函数f(回调函数,即使用方提供函数给对方调用); - 请求方动态创建script(添加到body),其src指向响应方url(
y.com
后端),同时将回调函数名作为参数传递,即http://y.com?callback=f
; - 响应方接收请求,根据查询参数f和返回的数据、构造调用这个函数的JavaScript代码字符串,形如
f.call(undefined, data)
或f(data)
作为响应结果返回给请求方; - 请求方浏览器接收响应(一段JS代码),被添加到body就会执行
f.call(undefined, data)
,从而获得需要的数据data。
示例:JSONP请求
请求方html
<script>
button.addEventListener(
'click', (e) => {
let functionName = 'x' + parseInt(Math.random() * 10000, 10) // 随机生成回调函数名称
window[functionName] = function(result) {
if (result === 'success') {
// ...
}
}
let script = document.createElement('script')
script.src = 'http://y.com/?callback=' + functionName // 发送请求获取script
document.body.appendChild(script) // 把script加入body中,自动执行
script.onload = function(e) {
e.currentTarget.remove()
delete window[functionName]
}
script.onerror = function(e) {
e.currentTarget.remove()
delete window[functionName]
}
}
)
</script>
也可以使用jQuery:
$.ajax({
url: 'http://y.com/?callback=' + functionName,
dataType: 'jsonp',
success: function(response) {
if(response === 'success'){
// ...
}
}
})
响应方node.js
if (path === '/' && method === 'GET') {
response.setHeader('Content-Type', 'application/javascript')
response.write(`
${query.callback}.call(undefined, 'success')
`)
response.end()
}
一些细节:
- 除了
<a>
标签外,<script>
(只能以脚本形式运行)、<img>
(只能展示为图片)都可以用作发送请求(Http HeadersContent-Type
中的image/jpg
、text/javascript
); - 发送请求、接收响应然后可以把数据填充到页面上,而不需要刷新整个页面;
- 不同网站之间script访问不受限制(防盗链除外),因此可以在页面上
<script src='xxx'></script>
引入script并自动执行,JSONP也常被用作前后端数据交互的方式; - 请求方动态创建回调函数,名称一般使用随机数、执行后销毁,避免污染命名空间;而且执行成功/失败后会从页面上把script删除;
- 由于页面上执行的逻辑完全由请求方前端实现,响应方后端只需要写好执行回调函数的字符串(函数名称为请求参数)、填充返回的数据即可,实现了前后端解耦;
- 由于JSONP是通过动态创建script实现的,所以只支持GET请求,且只能以脚本形式运行。
AJAX
同源策略
- 只有协议+端口+域名完全一样,浏览器才允许发送XMLHttpRequest请求(可以发送请求,但不能获取响应);
- CORS(Cross-Origin Resource Sharing)跨域:要发送不同源(即协议、端口、域名中一或多个不同)请求,需要服务端配合,在响应头中加入
Access-Control-Allow-Origin
字段、内容为请求方域名即可放行。
AJAX即“Asynchronous Javascript And XML”,以XML和JSON格式作前后端交互,支持发送各种HTTP请求及任何形式展示响应,这个过程:
- 使用XMLHttpRequest发送请求;
- 服务器返回XML/JSON格式字符串;
- 前端JavaScript解析XML,并更新局部页面。
示例(发送XMLHttpRequest请求):
let request = new XMLHttpRequest()
request.open('get', 'http://x.com')
request.onreadystatechange = () => {
if (request.readyState === 4 && request.status >= 200 && request.status < 300 ) {
let string = request.responseText
let object = window.JSON.parse(string)
}
}
request.send()
XML
目前已很少用作前后端交互,前端JS解析XML字符串:
let parser = new DOMParser()
let xmlDoc = parser.parseFromString(xmlString)
// 然后可以使用DOM API操作XML,很麻烦
xmlDoc.getElementsByTagName('heading')[0].textContent
JSON
JSON是一种类似JavaScript的数据格式化语言:
类型 | JavaScript | JSON |
---|---|---|
未定义 | undefined |
- |
空 | null |
null |
数组 | ['a', ['b'] |
["a", "b"] |
函数 | function(){} |
- |
对象 | {name: 'ywh'} |
{"name": "ywh"} |
字符串 | 'ywh' |
"ywh" |
变量 | var a = {}; a.self = a |
- |
原型链 | {__proto__} |
- |
注意JSON字符串的表示必须用双引号,前端JS解析JSON字符串:
let jsonObj = window.JSON.parse(jsonString) // 返回JS对应类型的变量
实现AJAX
HTTP请求设置
request line
request.open('post', '/xxx')
request headers
request.setRequestHeader('Content-Type', 'x-www-form-urlencoded')
request playload(GET请求默认不显示)
request.send('ywh 18')
HTTP响应读取
response line(注意状态码不代表返回信息,即使是404也有可能带响应)
let status = request.status
let statusText = request.statusText
response headers
let headers = request.getAllResponseHeaders()
let contentType = request.getResponseHeader('Content-Type')
response body
let body = request.responseText
模拟jQuery发送HTTP请求
前面提到使用原生JS自行实现jQuery:
window.jQuery = function(nodesOrSelector) {
// 判断传入的是节点还是选择器字符串,转换成统一的对象(伪数组)
let nodes = {}
if (typeof nodesOrSelector === 'string') {
let temp = document.querySelectorAll(nodesOrSelector)
for (let i = 0; i < temp.length; i++) {
nodes[i] = temp[i]
}
nodes.length = temp.length
}
else if (nodesOrSelector instanceof Node) {
nodes = {
0: nodesOrSelector,
length: 1
}
}
return nodes
}
window.$ = jQuery // 起别名
var node = $(item) // 返回一个对象,内部封装了多个函数
把AJAX封装为jQuery一个函数来调用:
window.$.ajax = function(options) {
let url // 接受两种形式的参数:(url, options)或(options)(options中包含url)
if (arguments.length === 1) {
url = options.url
}
else {
url = arguments[0]
options = arguments[1]
}
let method = options.method
let headers = options.headers
let body = options.body
let success = options.success
let fail = options.fail
let reqeust = new XMLHttpRequest()
for (let key in headers) {
request.setRequestHeader(key, headers[key])
}
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status >= 200 && request.status < 300) {
success.call(undefined, request.responseText)
}
else {
fail.call(undefined, request)
}
}
}
request.send(body)
}
btn.addEventListener('click', (e) => {
window.$.ajax({
url: '/xxx',
method: 'get',
// headers: '',
// body: '',
success: (x) => {
console.log(x)
},
fail: () => {} // 注意箭头函数没有arguments
})
})
// 使用结构化参数,如果改由逐个参数传入存在问题:
// 封装后无法获取函数参数名称(应该传入什么?);
// 没有默认参数,只能传入undefined/null占位(很难看);
依然存在问题:调用函数依然依然需要通过文档获悉参数的名称,调用不方便
使用Promise优化:then可以连续根据每次成功/失败处理后的结果,调用指定的函数做多次处理,而不需要把所有函数都封装在success/fail的函数中。
window.$.ajax = function(options) {
return new Promise( // 返回Promise对象
function (resolve, reject) {
if (arguments.length === 1) {
url = options.url
}
else {
url = arguments[0]
options = arguments[1]
}
let request = new XMLHttpRequest()
request.open(options.method, url)
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status >= 200 && request.status < 300) {
resolve.call(undefined, request.responseText) // 成功:对应Promise对象的第一个函数参数
}
else {
reject.call(undefined, request) // 失败:对应Promise对象的第二个函数参数
}
}
}
request.send(options.body)
}
)
}
let promise = window.$.ajax({
url: '/xxx',
method: 'get'
})
promise.then(
(text) => {}, // success
(request) => {} // fail
)
网友评论