fetch 是什么

XMLHttpRequest的最新替代技术

fetch优点

  • 接口更简单、简洁,更加语义化
  • 基于promise,更加好的流程化控制,可以不断then把参数传递,外加 async/await,异步变同步的代码书写风格
  • 利于同构,isomorphic-fetch 是对 whatwg-fetch和node-fetch的一种封装,你一份代码就可以在两种环境下跑起来了
  • 新的web api很多内置支持fetch,比如 service worker

fetch 缺点

  • 兼容性
  • 不支持progress事件(可以借助 response.body.getRender方法来实现)
  • 默认不带cookie
  • 某些错误的http状态下如400、500等不会reject,相反它会被resolve
  • 不支持timeout处理
  • 不支持jsonp,当然可以引入 fetch-jsonp 来支持

这些缺点,后面的参考里面有各种解决方案

fetch兼容性(2017-08-08):

fetch是基于promise设计的,

fetch参数

 参考 Fetch Standard 或者 Using Fetch

上面你对fetch有基本的了解了,而且提供了不少的链接解惑,那么我们进入正题,whatwg-fetch源码分析

依旧是先删除无用的代码,

(function (self) {
'use strict';
if (self.fetch) {
return
} // 封装的 Headers,支持的方法参考https://developer.mozilla.org/en-US/docs/Web/API/Headers
function Headers(headers) {
......
} //方法参考:https://developer.mozilla.org/en-US/docs/Web/API/Body
function Body() {
......
} // 请求的Request对象 ,https://developer.mozilla.org/en-US/docs/Web/API/Request
// cache,context,integrity,redirect,referrerPolicy 在MDN定义中是存在的
function Request(input, options) {
......
} Body.call(Request.prototype) //把Body方法属性绑到 Reques.prototype function Response(bodyInit, options) {
} Body.call(Response.prototype) //把Body方法属性绑到 Reques.prototype self.Headers = Headers //暴露Headers
self.Request = Request //暴露Request
self.Response = Response //暴露Response self.fetch = function (input, init) {
return new Promise(function (resolve, reject) {
var request = new Request(input, init) //初始化request对象
var xhr = new XMLHttpRequest() // 初始化 xhr xhr.onload = function () { //请求成功,构建Response,并resolve进入下一阶段
var options = {
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || '')
}
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
var body = 'response' in xhr ? xhr.response : xhr.responseText
resolve(new Response(body, options))
} //请求失败,构建Error,并reject进入下一阶段
xhr.onerror = function () {
reject(new TypeError('Network request failed'))
} //请求超时,构建Error,并reject进入下一阶段
xhr.ontimeout = function () {
reject(new TypeError('Network request failed'))
} // 设置xhr参数
xhr.open(request.method, request.url, true) // 设置 credentials
if (request.credentials === 'include') {
xhr.withCredentials = true
} else if (request.credentials === 'omit') {
xhr.withCredentials = false
} // 设置 responseType
if ('responseType' in xhr && support.blob) {
xhr.responseType = 'blob'
} // 设置Header
request.headers.forEach(function (value, name) {
xhr.setRequestHeader(name, value)
})
// 发送请求
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
})
}
//标记是fetch是polyfill的,而不是原生的
self.fetch.polyfill = true
})(typeof self !== 'undefined' ? self : this); // IIFE函数的参数,不用window,web worker, service worker里面也可以使用

简单分析一下

  • 如果自身支持fetch,直接返回,用自身的
  • 内部核心 Headers, Body, Request, Response,
    • Request和Resonse原型上有Body的方法属性,或者说,继承了
    • Headers,Request ,Reponse暴露到全局 
  • fetch本质就是对XMLHttpRequest 请求的封装  

这么一看其实到没什么了,不过完整代码里面有一些东西还是提一下(后面的参考都有链接)

  • Symbol, Iterator : ES6里面很多集合是自带默认Iterator的,作用就是在 let...of,数组解构,新Set,Map初始化等情况会被调用。
  • DataView , TypedArray:都是对TypeArray读写的API
  • Blob,FileReader :File API,这个也没啥多说的  
  • URLSearchParams: 这个支持度还不高,用来解析和构建 URL Search 参数的,例如  new URLSearchParams(window.location.search).get('a')

对外暴露的对象或者方法有

  • fetch

   封装过后的fetch,关于参数和使用

  • Headers

     http请求头,属性方法和使用

  • Request

      请求对象 ,属性方法和使用

  • Response

   请求的响应对象,属性方法和使用

这面重点解析几个重点函数和方法,其他的相对容易

iteratorFor

在定义中,Headers实例,headers.keys(), headers.values(), headers.entries()返回的都是Iterator, 下面代码读起来可能有点绕,

你这样理解,定义iterator 是保证能使用next方法来遍历

定义iterator[Symbol.iterator] 是设置默认 Iterator,能使用 let...of,Array.from,数组解构等相对高级一些方法访问到

  // 枚举器, http://es6.ruanyifeng.com/#docs/iterator
// 觉得可以如下 ,同样支持 next() 和 for ...of 等形式访问 ,之后才是不支持iterable的情况,添加next方法来访问
// if ((support.iterable && items[Symbol.iterator]) {
// return items[Symbol.iterator]()
// }

function iteratorFor(items) {
// 这里你就可以 res.headers.keys().next().value这样调用
var iterator = {
next: function () {
var value = items.shift()
return { done: value === undefined, value: value }
}
} if (support.iterable) {
// 添加默认Iterator
// for...of,解构赋值,扩展运算符,yield*,Map(), Set(), WeakMap(), WeakSet(),Promise.all(),Promise.race()都会调用默认Iterator
iterator[Symbol.iterator] = function () {
return iterator
}
} // 到这里就支持了两种访问形式了
// res.headers.keys().next().value
// for(let key in headers.keys())
return iterator
}

Body.call

实现继承,把body的方法属性绑定指定对象原型

Body.call(Request.prototype)
Body.call(Response.prototype)

这两个理解上,就基本可以无大碍了,那我贴出完整带注释的代码

(function (self) {
'use strict'; //如果自身支持fetch,直接返回原生的fetch
if (self.fetch) {
// return
} // 一些功能检测
var support = {
searchParams: 'URLSearchParams' in self, // queryString 处理函数,https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams,http://caniuse.com/#search=URLSearchParams
iterable: 'Symbol' in self && 'iterator' in Symbol, // Symbol(http://es6.ruanyifeng.com/#docs/symbol)E6新数据类型,表示独一无二的值 和 iterator枚举
blob: 'FileReader' in self && 'Blob' in self && (function () {
try {
new Blob()
return true
} catch (e) {
return false
}
})(), // Blob 和 FileReader
formData: 'FormData' in self, // FormData
arrayBuffer: 'ArrayBuffer' in self // ArrayBuffer 二进制数据存储
} // 支持的 ArrayBuffer类型
if (support.arrayBuffer) {
var viewClasses = [
'[object Int8Array]',
'[object Uint8Array]',
'[object Uint8ClampedArray]',
'[object Int16Array]',
'[object Uint16Array]',
'[object Int32Array]',
'[object Uint32Array]',
'[object Float32Array]',
'[object Float64Array]'
] // 检查是不是DataView,DataView是来读写ArrayBuffer的 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView
var isDataView = function (obj) {
return obj && DataView.prototype.isPrototypeOf(obj)
} // 检查是不是有效的ArrayBuffer view,TypedArray均返回true ArrayBuffer.isView(new ArrayBuffer(10)) 为false, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/isView
var isArrayBufferView = ArrayBuffer.isView || function (obj) {
return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
}
} // 检查header name,并转为小写
function normalizeName(name) {
// 不是字符串,转为字符串
if (typeof name !== 'string') {
name = String(name)
}
// 不以 a-z 0-9 -#$%*+.^_`|~ 开头,抛出错误
if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) {
throw new TypeError('Invalid character in header field name')
}
//转为小写
return name.toLowerCase()
} // 转换header的值
function normalizeValue(value) {
if (typeof value !== 'string') {
value = String(value)
}
return value
} // 枚举器, http://es6.ruanyifeng.com/#docs/iterator
// 觉得可以如下 ,同样支持 next() 和 for ...of 等形式访问 ,之后才是不支持iterable的情况,添加next方法来访问
// if ((support.iterable && items[Symbol.iterator]) {
// return items[Symbol.iterator]()
// }
function iteratorFor(items) {
// 这里你就可以 res.headers.keys().next().value这样调用
var iterator = {
next: function () {
var value = items.shift()
return { done: value === undefined, value: value }
}
} if (support.iterable) {
// 添加默认Iterator
// for...of,解构赋值,扩展运算符,yield*,Map(), Set(), WeakMap(), WeakSet(),Promise.all(),Promise.race()都会调用默认Iterator
iterator[Symbol.iterator] = function () {
return iterator
}
} // 到这里就支持了两种访问形式了
// res.headers.keys().next().value
// for(let key in headers.keys())
return iterator
} // 封装的 Headers,支持的方法参考https://developer.mozilla.org/en-US/docs/Web/API/Headers
function Headers(headers) {
this.map = {} // headers 最终存储的地方 if (headers instanceof Headers) { // 如果已经是 Headers的实例,复制键值
headers.forEach(function (value, name) {
this.append(name, value)
}, this) // this修改forEach执行函数上下文为当前上下文,就可以直接调用append方法了
} else if (Array.isArray(headers)) { // 如果是数组,[['Content-Type':''],['Referer','']]
headers.forEach(function (header) {
this.append(header[0], header[1])
}, this)
} else if (headers) {
// 对象 {'Content-Type':'',Referer:''}
Object.getOwnPropertyNames(headers).forEach(function (name) {
this.append(name, headers[name])
}, this)
}
} // 添加或者追加Header
Headers.prototype.append = function (name, value) {
name = normalizeName(name)
value = normalizeValue(value)
var oldValue = this.map[name]
// 支持 append, 比如 Accept:text/html ,后来 append('Accept','application/xhtml+xml') 那么最终 Accept:'text/html,application/xhtml+xml'
this.map[name] = oldValue ? oldValue + ',' + value : value
} //删除名为name的Header
Headers.prototype['delete'] = function (name) {
delete this.map[normalizeName(name)]
} //获得名为Name的Header
Headers.prototype.get = function (name) {
name = normalizeName(name)
return this.has(name) ? this.map[name] : null
} //查询时候有名为name的Header
Headers.prototype.has = function (name) {
return this.map.hasOwnProperty(normalizeName(name))
}
//设置或者覆盖名为name,值为vaue的Header
Headers.prototype.set = function (name, value) {
this.map[normalizeName(name)] = normalizeValue(value)
}
//遍历Headers
Headers.prototype.forEach = function (callback, thisArg) {
//遍历属性
//我觉得也挺不错 Object.getOwnPropertyNames(this.map).forEach(function(name){ callback.call(thisArg, this.map[name], name, this) },this)
for (var name in this.map) {
//检查是不是自己的属性
if (this.map.hasOwnProperty(name)) {
//调用
callback.call(thisArg, this.map[name], name, this)
}
}
} // 所有的键,keys, values, entries, res.headers返回的均是 iterator
Headers.prototype.keys = function () {
var items = []
this.forEach(function (value, name) { items.push(name) })
return iteratorFor(items)
}
// 所有的值,keys, values, entries, res.headers返回的均是 iterator
Headers.prototype.values = function () {
var items = []
this.forEach(function (value) { items.push(value) })
return iteratorFor(items)
}
// 所有的entries,格式是这样 [[name1,value1],[name2,value2]],keys, values, entries, res.headers返回的均是 iterator
Headers.prototype.entries = function () {
var items = []
this.forEach(function (value, name) { items.push([name, value]) })
return iteratorFor(items)
} //设置Headers原型默认的Iterator,keys, values, entries, res.headers返回的均是 iterator
if (support.iterable) {
Headers.prototype[Symbol.iterator] = Headers.prototype.entries
} //是否已经消费/读取过,如果读取过,会直接到catch或者error处理函数
function consumed(body) {
if (body.bodyUsed) {
return Promise.reject(new TypeError('Already read'))
}
body.bodyUsed = true
} // FileReader读取完毕
function fileReaderReady(reader) {
return new Promise(function (resolve, reject) {
reader.onload = function () {
resolve(reader.result)
}
reader.onerror = function () {
reject(reader.error)
}
})
} // 读取blob为ArrayBuffer对象,https://www.w3.org/TR/FileAPI/#dfn-filereader
function readBlobAsArrayBuffer(blob) {
var reader = new FileReader()
var promise = fileReaderReady(reader)
reader.readAsArrayBuffer(blob)
return promise
}
// 读取blob为文本,https://www.w3.org/TR/FileAPI/#dfn-filereader
function readBlobAsText(blob) {
var reader = new FileReader()
var promise = fileReaderReady(reader)
reader.readAsText(blob)
return promise
} // ArrayBuffer读为文本
function readArrayBufferAsText(buf) {
var view = new Uint8Array(buf)
var chars = new Array(view.length) for (var i = 0; i < view.length; i++) {
chars[i] = String.fromCharCode(view[i])
}
return chars.join('')
} //克隆ArrayBuffer
function bufferClone(buf) {
if (buf.slice) { //支持 slice,直接slice(0)复制,数据基本都是这样复制的
return buf.slice(0)
} else {
//新建填充模式复制
var view = new Uint8Array(buf.byteLength)
view.set(new Uint8Array(buf))
return view.buffer
}
} //方法参考:https://developer.mozilla.org/en-US/docs/Web/API/Body
function Body() {
this.bodyUsed = false this._initBody = function (body) {
// 把最原始的数据存下来
this._bodyInit = body
// 判断body数据类型,然后存下来
if (!body) {
this._bodyText = ''
} else if (typeof body === 'string') {
this._bodyText = body
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
this._bodyBlob = body
} else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
this._bodyFormData = body
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
this._bodyText = body.toString() //数据格式是这样的 a=1&b=2&c=3
} else if (support.arrayBuffer && support.blob && isDataView(body)) {
// ArrayBuffer一般是通过DataView或者各种Float32Array,Uint8Array来操作的, https://hacks.mozilla.org/2017/01/typedarray-or-dataview-understanding-byte-order/
// 如果是DataView, DataView的数据是存在 DataView.buffer上的
this._bodyArrayBuffer = bufferClone(body.buffer) // 复制ArrayBuffer
// IE 10-11 can't handle a DataView body.
this._bodyInit = new Blob([this._bodyArrayBuffer]) // 重新设置_bodyInt
} else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
// ArrayBuffer一般是通过DataView或者各种Float32Array,Uint8Array来操作的,
// https://hacks.mozilla.org/2017/01/typedarray-or-dataview-understanding-byte-order/
this._bodyArrayBuffer = bufferClone(body)
} else {
throw new Error('unsupported BodyInit type')
} // 设置content-type
if (!this.headers.get('content-type')) {
if (typeof body === 'string') {
this.headers.set('content-type', 'text/plain;charset=UTF-8')
} else if (this._bodyBlob && this._bodyBlob.type) {
this.headers.set('content-type', this._bodyBlob.type)
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8')
}
}
} if (support.blob) {
// 使用 fetch(...).then(res=>res.blob())
this.blob = function () {
//标记为已经使用
var rejected = consumed(this)
if (rejected) {
return rejected
} // resolve,进入then
if (this._bodyBlob) {
return Promise.resolve(this._bodyBlob)
} else if (this._bodyArrayBuffer) {
return Promise.resolve(new Blob([this._bodyArrayBuffer]))
} else if (this._bodyFormData) {
throw new Error('could not read FormData body as blob')
} else {
return Promise.resolve(new Blob([this._bodyText]))
}
}
// 使用 fetch(...).then(res=>res.arrayBuffer())
this.arrayBuffer = function () {
if (this._bodyArrayBuffer) {
return consumed(this) || Promise.resolve(this._bodyArrayBuffer)
} else {
return this.blob().then(readBlobAsArrayBuffer) //如果有blob,读取成ArrayBuffer
}
}
} // 使用 fetch(...).then(res=>res.text())
this.text = function () {
var rejected = consumed(this)
if (rejected) {
return rejected
} if (this._bodyBlob) {
return readBlobAsText(this._bodyBlob)
} else if (this._bodyArrayBuffer) {
return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))
} else if (this._bodyFormData) {
throw new Error('could not read FormData body as text')
} else {
return Promise.resolve(this._bodyText)
}
} // 使用 fetch(...).then(res=>res.formData())
if (support.formData) {
this.formData = function () {
return this.text().then(decode)
}
} // 使用 fetch(...).then(res=>res.json())
this.json = function () {
return this.text().then(JSON.parse)
} return this
} // HTTP methods whose capitalization should be normalized
var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] // 方法名大写
function normalizeMethod(method) {
var upcased = method.toUpperCase()
return (methods.indexOf(upcased) > -1) ? upcased : method
} // 请求的Request对象 ,https://developer.mozilla.org/en-US/docs/Web/API/Request
// cache,context,integrity,redirect,referrerPolicy 在MDN定义中是存在的
function Request(input, options) {
options = options || {}
var body = options.body //如果已经是Request的实例,解析赋值
if (input instanceof Request) {
if (input.bodyUsed) {
throw new TypeError('Already read')
}
this.url = input.url //请求的地址
this.credentials = input.credentials //登陆凭证
if (!options.headers) { //headers
this.headers = new Headers(input.headers)
}
this.method = input.method //请求方法 GET,POST......
this.mode = input.mode // same-origin,cors,no-cors
if (!body && input._bodyInit != null) { //标记Request已经使用
body = input._bodyInit
input.bodyUsed = true
}
} else {
this.url = String(input)
} this.credentials = options.credentials || this.credentials || 'omit'
if (options.headers || !this.headers) {
this.headers = new Headers(options.headers)
}
this.method = normalizeMethod(options.method || this.method || 'GET')
this.mode = options.mode || this.mode || null //same-origin,cors,no-cors
this.referrer = null if ((this.method === 'GET' || this.method === 'HEAD') && body) {
throw new TypeError('Body not allowed for GET or HEAD requests')
}
this._initBody(body) //解析值 和设置content-type
} // 克隆
Request.prototype.clone = function () {
return new Request(this, { body: this._bodyInit })
} // body存为 FormData
function decode(body) {
var form = new FormData()
body.trim().split('&').forEach(function (bytes) {
if (bytes) {
var split = bytes.split('=')
var name = split.shift().replace(/\+/g, ' ')
var value = split.join('=').replace(/\+/g, ' ')
form.append(decodeURIComponent(name), decodeURIComponent(value))
}
})
return form
} // 用于接续 xhr.getAllResponseHeaders, 数据格式
//Cache-control: private
//Content-length:554
function parseHeaders(rawHeaders) {
var headers = new Headers()
// Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
// https://tools.ietf.org/html/rfc7230#section-3.2
var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ')
preProcessedHeaders.split(/\r?\n/).forEach(function (line) {
var parts = line.split(':')
var key = parts.shift().trim()
if (key) {
var value = parts.join(':').trim()
headers.append(key, value)
}
})
return headers
} Body.call(Request.prototype) //把Body方法属性绑到 Reques.prototype // Reponse对象,https://developer.mozilla.org/en-US/docs/Web/API/Response
function Response(bodyInit, options) {
if (!options) {
options = {}
} this.type = 'default'
this.status = options.status === undefined ? 200 : options.status
this.ok = this.status >= 200 && this.status < 300 // 200 - 300 ,https://developer.mozilla.org/en-US/docs/Web/API/Response/ok
this.statusText = 'statusText' in options ? options.statusText : 'OK'
this.headers = new Headers(options.headers)
this.url = options.url || ''
this._initBody(bodyInit) // 解析值和设置content-type
} Body.call(Response.prototype) //把Body方法属性绑到 Reques.prototype // 克隆Response
Response.prototype.clone = function () {
return new Response(this._bodyInit, {
status: this.status,
statusText: this.statusText,
headers: new Headers(this.headers),
url: this.url
})
} //返回一个 error性质的Response,静态方法
Response.error = function () {
var response = new Response(null, { status: 0, statusText: '' })
response.type = 'error'
return response
} var redirectStatuses = [301, 302, 303, 307, 308] // 重定向,本身并不产生实际的效果,静态方法,https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect
Response.redirect = function (url, status) {
if (redirectStatuses.indexOf(status) === -1) {
throw new RangeError('Invalid status code')
} return new Response(null, { status: status, headers: { location: url } })
} self.Headers = Headers //暴露Headers
self.Request = Request //暴露Request
self.Response = Response //暴露Response self.fetch = function (input, init) {
return new Promise(function (resolve, reject) {
var request = new Request(input, init) //初始化request对象
var xhr = new XMLHttpRequest() // 初始化 xhr xhr.onload = function () { //请求成功,构建Response,并resolve进入下一阶段
var options = {
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || '')
}
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
var body = 'response' in xhr ? xhr.response : xhr.responseText
resolve(new Response(body, options))
} //请求失败,构建Error,并reject进入下一阶段
xhr.onerror = function () {
reject(new TypeError('Network request failed'))
} //请求超时,构建Error,并reject进入下一阶段
xhr.ontimeout = function () {
reject(new TypeError('Network request failed'))
} // 设置xhr参数
xhr.open(request.method, request.url, true) // 设置 credentials
if (request.credentials === 'include') {
xhr.withCredentials = true
} else if (request.credentials === 'omit') {
xhr.withCredentials = false
} // 设置 responseType
if ('responseType' in xhr && support.blob) {
xhr.responseType = 'blob'
} // 设置Header
request.headers.forEach(function (value, name) {
xhr.setRequestHeader(name, value)
})
// 发送请求
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
})
}
//标记是fetch是polyfill的,而不是原生的
self.fetch.polyfill = true
})(typeof self !== 'undefined' ? self : this); // IIFE函数的参数,不用window,web worker, service worker里面也可以使用

 

小结:

  • 可以看出,有些属性是没有实现的,但是一般的请求足以
  • Response.body 这种ReadableStream没有实现,自然就没有fetch原生处理progress的方法    
fetch('/').then(response => {
// response.body is a readable stream.
// Calling getReader() gives us exclusive access to the stream's content
var reader = response.body.getReader();
var bytesReceived = 0; // read() returns a promise that resolves when a value has been received
reader.read().then(function processResult(result) {
// Result objects contain two properties:
// done - true if the stream has already given you all its data.
// value - some data. Always undefined when done is true.
if (result.done) {
console.log("Fetch complete");
return;
} // result.value for fetch streams is a Uint8Array
bytesReceived += result.value.length;
console.log('Received', bytesReceived, 'bytes of data so far'); // Read some more, and call this function again
return reader.read().then(processResult);
});
});

参考:

使用fetch遇到过的坑

fetch使用的常见问题及解决办法

Fetch Standard

Fetch相比Ajax有什么优势

Fetch API

Iterator

URLSearchParams - Web APIs | MDN

whatwg-fetch的更多相关文章

  1. [转] 传统 Ajax 已死,Fetch 永生

    原谅我做一次标题党,Ajax 不会死,传统 Ajax 指的是 XMLHttpRequest(XHR),未来现在已被 Fetch 替代. 最近把阿里一个千万级 PV 的数据产品全部由 jQuery 的  ...

  2. 传统 Ajax 已死,Fetch 永生

    原谅我做一次标题党,Ajax 不会死,传统 Ajax 指的是 XMLHttpRequest(XHR),未来现在已被 Fetch 替代. 最近把阿里一个千万级 PV 的数据产品全部由 jQuery 的  ...

  3. Fetch和ajax的比较和区别

    传统 Ajax 已死,Fetch 永生   Ajax 不会死,传统 Ajax 指的是 XMLHttpRequest(XHR),未来现在已被 Fetch 替代. 最近把阿里一个千万级 PV 的数据产品全 ...

  4. react中使用Ajax请求(axios,Fetch)

    React本身只关注于界面, 并不包含发送ajax请求的代码,前端应用需要通过ajax请求与后台进行交互(json数据),可以使用集成第三方ajax库(或自己封装) 常用的ajax请求库 jQuery ...

  5. fetch将替代ajax?

    原谅我做一次标题党,Ajax 不会死,传统 Ajax 指的是 XMLHttpRequest(XHR),未来现在已被 Fetch 替代. 最近把阿里一个千万级 PV 的数据产品全部由 jQuery 的  ...

  6. 只知道ajax?你已经out了

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由前端林子发表于云+社区专栏 随着前端技术的发展,请求服务器数据的方法早已不局限于ajax.jQuery的ajax方法.各种js库已如雨 ...

  7. 总结开发中使用到的npm 库

    1.Swiper  https://github.com/nolimits4web/Swiper 移动端slides插件 2.fetch https://github.com/whatwg/fetch ...

  8. 一文带你看清HTTP所有概念

    上一篇文章我们大致讲解了一下 HTTP 的基本特征和使用,大家反响很不错,那么本篇文章我们就来深究一下 HTTP 的特性.我们接着上篇文章没有说完的 HTTP 标头继续来介绍(此篇文章会介绍所有标头的 ...

  9. 一文带你看清HTTP所有概念(转)

    一文带你看清HTTP所有概念   上一篇文章我们大致讲解了一下 HTTP 的基本特征和使用,大家反响很不错,那么本篇文章我们就来深究一下 HTTP 的特性.我们接着上篇文章没有说完的 HTTP 标头继 ...

  10. HTTP 【一文看清所有概念】

    HTTP 标头 HTTP 1.1 的标头主要分为四种,通用标头.实体标头.请求标头.响应标头,现在我们来对这几种标头进行介绍 通用标头 HTTP 通用标头之所以这样命名,是因为与其他三个类别不同,它们 ...

随机推荐

  1. uwsgi启动报错WARNING: Can't find section "uwsgi" in INI configuration file autotestsite_uwsgi.ini

    提示配置文件头部找不到 [uwsgi] 解决: 在最上方加上[uwsgi],有时候明明有标识,但是还是提示,那就再加一个

  2. 关于.NET C#调用Sqlite的总结一

    --前记 由于自己的密码太多又不容易记住,经常性的会忘记.所以想找个管理软件管理下,可用网上下载的担心不安全.于是自己开始动手写个属于自己的密码管理软件. 因为自己一直做的是WEB开发,对WPF懂的不 ...

  3. Logstash 性能及其替代方案

    介绍 当谈及集中日志到 Elasticsearch 时,首先想到的日志传输(log shipper)就是 Logstash.开发者听说过它,但是不太清楚它具体是干什么事情的: 当深入这个话题时,我们才 ...

  4. ASP.NET Core中使用自定义验证属性控制访问权限

    在应用中,有时我们需要对访问的客户端进行有效性验证,只有提供有效凭证(AccessToken)的终端应用能访问我们的受控站点(如WebAPI站点),此时我们可以通过验证属性的方法来解决. 一.publ ...

  5. Ehcache和MemCached区别及应用

    ehcache是纯Java编写的,通信是通过RMI方式,适用于基于java技术的项目.memcached服务器端是c编写的,客户端有多个语言的实现,如c,PHP(淘宝,sina等各大门户网站),Pyt ...

  6. mysql数据的导入导出

    2017-12-15       一. mysqldump工具基本用法,不适用于大数据备份   1. 备份所有数据库: mysqldump -u root -p --all-databases > ...

  7. windows下安装ubuntu15.04

    本文主要介绍windows下安装ubuntu15.04,对与其他的版本也是适用的.现在要讲的是一种最简单ubuntu的安装方式. 1软件下载 1.磁盘分区工具DiskGenius 2.启动项修改工具E ...

  8. iOS 循环轮播框架思路

    使用3个imageview实现无线轮播的大致原理 将3个imageview添加到scrollview上面,scrollview的contensize是3个imageview的宽度,设置scrollvi ...

  9. JAVA中 package 和 import 的使用

    1.打包--package 包名一般为小写,而类名的第一个字母一般为大写,这样在引用时,可以明显的分辨出包名和类名.如果在类的定义之前没有使用package定义包名,那么该类就属于缺 省的包. 1.1 ...

  10. 为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比

    原文:https://www.sojson.com/blog/48.html 前言: MQ 是什么?队列是什么,MQ 我们可以理解为消息队列,队列我们可以理解为管道.以管道的方式做消息传递. 场景: ...