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
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>JSONP</title> </head> <body> <input type="text" id="input" /> <ul id="list"></ul> <script> function jsonp(url, callback, success) { let script = document.createElement('script') script.src = url script.async = true script.type = 'text/javascript' document.body.appendChild(script) }
function show(json) { let list = document.getElementById('list') list.innerHTML = ''
console.log(json)
json.s.forEach(str => { let li = document.createElement('li') li.innerHTML = str list.appendChild(li) }) }
let input = document.getElementById('input') input.oninput = function () { let url = `https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=${encodeURIComponent(this.value)}&cb=show` jsonp(url, 'callback', show) } </script> </body> </html>
|
postMessage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
window.parent.postMessage('hello', 'http://test.com') window.iframe1.contentWindow.postMessage('world', '*')
var mc = new MessageChannel() mc.addEventListener('message', event => { var origin = event.origin || event.originalEvent.origin if (origin === 'http://test.com') { console.log('验证通过') } })
window.addEventListener('message', event => { console.log(event.origin) console.log(event.data) })
|
- 和 HTTP 同属于
应用层
- 先发起一个 HTTP 请求,和服务端建立连接;连接成功之后再升级为 WebSocket 协议,然后再通讯
应用场景
消息通知,直播聊天,协同编辑
1 2 3 4 5 6 7 8 9
| import { createServer } from 'https' import { readFileSync } from 'fs' import { WebSocketServer } from 'ws'
const server = createServer({ cert: readFileSync('./cert.pem'), key: readFileSync('./key.pem') }) const wss = new WebSocketServer({ server })
|
请问 WebSocket 和 HTTP 的区别?
- 协议名称不同
ws
和 http
- HTTP 一般只能由浏览器发起请求,WebSocket 双端通讯
- WebSocket
无跨域限制
- WebSocket 通过
send
和 onmessage
进行通讯,HTTP 通过 req
和 res
通讯
ws
可以升级为 wss
协议,像 http
升级到 https
一样,增加 SSL/TLS
安全协议
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
| function ajax(options) { let opts = Object.assign({ method: 'get', url: '', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, data: null, success: function () {} }, options)
let xhr = new XMLHttpRequest()
if (opts.method.toUpperCase() === 'GET' && data) { const query = o2u(opts.data) if (queryStr) { const index = url.indexOf('?') if (index === -1) url += '?' else if (index !== url.length - 1) url += '&' opts.url += queryStr } }
xhr.open(opts.method, opts.url, true)
for (let i in opts.headers) { xhr.setRequestHeader(i, opts.headers[i]) }
xhr.onload = function () { let responseData if (xhr.getResponseHeader('Content-Type').includes('xml')) { responseData = xhr.responseXML } else { responseData = JSON.parse(xhr.responseText) } opts.success(responseData) }
if (opts.method.toUpperCase() === 'GET') { xhr.send() } else { let sendData switch (opts.headers['Content-Type']) { case 'application/x-www-form-urlencoded': sendData = o2u(opts.data) break case 'application/json': sendData = JSON.stringify(opts.data) break } xhr.send(sendData) }
function o2u(obj) { let keys = Object.keys(obj) return keys.map(i => { return `${encodeURIComponent(i)}=${encodeURIComponent(obj[i])}` }).join('&') } }
let fileInput = document.getElementById('file') fileInput.onchange = function(e) {
let data = new FormData() data.set('user', 'Bobo') Array.from(this.files).forEach(file => { data.append('myfile[]', file) })
let xhr = new XMLHttpRequest() let sTime let sloaded xhr.open('POST', 'http://localhost/upload', true)
xhr.upload.addEventListener('loadstart', () => { stime = new Date().getTime() sloaded = 0 }, false) xhr.upload.addEventListener('progress', (e) => { let cTime = new Date().getTime() let dTime = (cTime - stime) / 1000 let dloaded = e.loaded - sloaded let speed = dloaded / dTime stime = cTime sloaded = e.loaded let unit = 'b/s' if (speed / 1024 > 1) { unit = 'kb/s' speed = speed / 1024 } if (speed / 1024 > 1) { unit = 'mb/s' speed = speed / 1024 }
console.log('进度: ' + (e.loaded / e.total * 100).toFixed(2) + '%', '速度: ' + speed.toFixed(2) + unit) }, false)
xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { console.log('Success.') } } xhr.send(data)
}
|
请问 ajax fetch axios 的区别?
- ajax 是一种技术称呼(异步的 JavaScript 和 XML),不是具体的 API 和 库
- fetch 是新的异步请求
API
,可代替 XMLHttpRequest,原生支持 promise
- axios 是第三方
库
,随着 Vue框架
一起崛起
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
| let myHeader = new Headers()
myHeader.append('Content-Type', 'application/json') myHeader.append('X-Custom-Header', 'somevalue')
fetch('/fetch', { method: 'post', headers: myHeader, body: JSON.stringify({name: 'Bobo', age: 23}), }).then(res => { let result = res.clone(res) return result.json() }).then(res => { console.log(res) }).catch(err => { console.log(err) })
const urlArr = 'oss/xxx.m3u8?token'.split('?')[0].split('/') const filename = urlArr[urlArr.length - 1] fetch('oss/xxx.m3u8?token').then(res => res.blob()).then(blob => {})
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function fetchWithTimeout(url, options, timeout = 5000) { const controller = new AbortController() const signal = controller.signal
const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { controller.abort() reject(new Error('Request timed out')) }, timeout) })
const fetchPromise = fetch(url, { ...options, signal })
return Promise.race([fetchPromise, timeoutPromise]) }
|
协议 域名 端口 有一个不同就是跨域
CORS(Cross-Origin Resource Sharing)跨域资源共享,是一份浏览器技术的规范,用来避开浏览器的同源策略
1 2 3 4 5 6 7 8 9 10 11 12 13
| app.use(, ctx => { ctx.set('Access-Control-Allow-Origin', 'https://www.5207.fun/')
ctx.set('Access-Control-Allow-Credentials', true) ctx.set('Access-Control-Allow-Headers', 'Origin, Content-Type, Content-Length, Authorization, Accept, X-Requested-With') ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') ctx.set('Access-Control-Expose-Headers', 'Content-Disposition') ctx.set('Access-Control-Max-Age', 3600) })
|
简单请求
请求方式
(HTTP1.0) GET POST HEAD
Headers
不超出以下字段
- Accept Accept-Language Content-Language Last-Event-ID Content-Type
Content-Type
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
满足以上条件的,就是简单请求。浏览器在头信息之中,增加一个Origin
字段
Origin: https://www.5207.fun/
Origin
字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口),服务器根据这个值,决定是否同意这次请求
- 同意: 服务器返回的响应,会多出一个头信息字段
Access-Control-Allow-Origin: https://www.5207.fun/
- 不同意: 如果 Origin 指定的域名,不在许可范围内,服务器仍会正常返回 HTTP 响应
浏览器发现头信息没有包含 Access-Control-Allow-Origin 字段,从而抛出一个错误,被 XMLHttpRequest 的 onerror 回调函数捕获
预检请求
请求方式
(HTTP1.1) PUT PATCH DELETE CONNECT OPTIONS
TRACE
Content-Type
application/json
有一些请求对服务器有着特殊的要求,会在正式通信之前,增加一次预检请求(preflight)
- 确认当前网页所在的域名是否在服务器的许可名单之中,明确可以使用的 HTTP 请求方法和头信息字段
- 只有在这个请求返回成功的情况下,浏览器才会发出正式的请求
- 这样做的目的是为了避免“无用功”。一般来说,正式请求要携带一些信息,它体积可能比较大。如果我们背着这么大一个包袱到了服务端那边,却发现对方根本不接受你,那岂不是白费力气了嘛^_^
Proxy 代理
跨域
是浏览器规范,通过服务器的请求转发,也能解决浏览器限制
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
| const Koa = require('koa') const Router = require('koa-router') const static = require('koa-static') const koaBody = require('koa-body') const http = require('http') const url = require('url') const koaServerHttpProxy = require('koa-server-http-proxy')
let app = new Koa()
let router = new Router() app.use(router.routes())
app.use(koaBody({ multipart:true })) app.use(static(__dirname + '/static'))
app.use(koaServerHttpProxy('/api', { target: 'https://www.5207.fun/', pathRewrite: {'^/api': ''} }))
router.get('/', ctx => { ctx.body = 'Hello World!' })
router.all('/proxy', async ctx => { const options = { host: 'localhost', port: 3000, method: 'get', path: '/api' } const res = await new Promise(resolve => { const request = http.request(options, res => { let str = '' res.on('data', chunk => { str += chunk }) res.on('end', () => { resolve(str.toString()) }) }) request.end() }) ctx.body = res })
app.listen(3000)
|
devServer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const proxy = { '/api': { target: 'https://api.wawotv.com', pathRewrite: { '^/api': '/api' }, changeOrigin: true, secure: true, headers: { host: 'https://www.wawotv.com', origin: 'https://www.wawotv.com' }, cookieDomainRewrite: { 'wawotv.com': '' } } }
|
多标签页通信
WebSocket 需要服务端参与,但不限制跨域
localStorage 简单易用
1 2 3 4
| window.addEventListener('storage', event => { console.log(event.key) console.log(event.newValue) })
|
SharedWorker 本地调试不方便,考虑浏览器兼容性,不支持 IE 11
- WebWorker 专用于 JS 计算,不支持 DOM 操作
- WebWorker 是当前页面专有的,不得在多个页面、iframe 共享
- SharedWorker 可以被同域的多个页面共享
Restful API
GET
向服务器获取资源
POST
发送数据给服务器
HEAD
请求资源的头部信息,并且这些头部与 HTTP GET 方法请求时返回的一致。该请求方法的一个使用场景:下载一个大文件前先获取其大小再决定是否要下载,以此可以节约带宽资源
PUT
用于新增资源或者使用请求中的有效负载替换目标资源的表现形式
PATCH
用于对资源进行部分修改
DELETE
用于删除指定的资源
CONNECT
HTTP1.1 协议中预留给能够将连接改为管道方式的代理服务器
OPTIONS
用于获取目的资源所支持的通信选项
TRACE
回显服务器收到的请求,主要用于测试或诊断
以一个博客项目为例,实现“增删改查”功能,使用 Restful API 的接口设计
新增博客
- url
http://xxx.com/api/blog
- method
POST
删除博客
- url
http://xxx.com/api/blog/100
- method
DELETE
修改博客
- url
http://xxx.com/api/blog/100
- method
PATCH
PUT
替换全部内容
PATCH
更新部分内容
查询博客
- 查询单个博客
- url
http://xxx.com/api/blog/100
- method
GET
- 查询博客列表
- url
http://xxx.com/api/blog
- method
GET
Get 用于无副作用
,幂等
的场景,例如 查询
Post 用于有副作用,不幂等的场景,例如 注册