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. 【原创翻译】ArcGis Android 10.2.4更新内容简介

    翻译不当和错误之处敬请指出 更新内容官方描述 https://developers.arcgis.com/android/guide/release-notes-10-2-4.htm 10.2.4的版 ...

  2. C#操作windows服务,安装、卸载、停止、启动

    public class ServiceUtil { private string _ServiceName = string.Empty; private string _AppName = str ...

  3. WinForm中Button的使用

    自定义样式 先要清除系统风格影响:this.FlatStyle = FlatStyle.Flat; FlatStyle.Flat FlatStyle.System FlatStyle.Standard ...

  4. 达梦数据库(DaMeng)如何删除IDENTITY自增属性字段

    今天工作中使用到达梦数据库,要求删除具有IDENTITY自增属性的字段. 直接执行删除:ALTER TABLE <表名> DROP COLUMN <列名> CASCADE; 删 ...

  5. HAOI2010 工厂选址

    题目链接:戳我 数组开小火葬场qwqwq 就是一个贪心吧.对于一个数,我们知道只有两种摆放方式.所以我们可以先都放到新的里面,然后做一下新的-原先的差,按照差从大到小排序,依次提取数值减去即可. 代码 ...

  6. SDOI2010粟粟的书架

    题目传送:https://www.luogu.org/problemnew/show/P2468 这是一个二合一的题目,前50% \(n!=1\)的分数中,我们考虑用动态规划来做. 设\(sum[i] ...

  7. django重写form表单中的局部钩子函数

    from django import forms from django.core.exceptions import ValidationError from jax import models c ...

  8. 【bzoj5084】 hashit(广义SAM+set)

    题面 传送门 题解 后缀平衡树是个啥啊我不会啊-- 那么我们来考虑一下\(SAM\)的做法好了 不难发现它的本义是要我们维护一棵\(trie\)树,并求出\(trie\)树上每一个节点到根的这段串的不 ...

  9. objectARX加载lisp函数、源码的一种方式

    //感谢高飞鸟highflybird版主的思路以及研究. //先声明非公开函数acedEvaluateLisp extern int acedEvaluateLisp(const ACHAR*,str ...

  10. php从文本读入数据,处理结果再导入到文本

    1,php从文本逐行读入数据,保存到数据组.使用fopen读取文本内容,逐行读取文本是$majorId = trim(fgets($rfile, 4096));. $rfile = fopen(&qu ...