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. Thinkphp源码分析系列(九)–视图view类

    视图类view主要用于页面内容的输出,模板调用等,用在控制器类中,可以使得控制器类把表现和数据结合起来.下面我们来看一下执行流程. 首先,在控制器类中保持着一个view类的对象实例,只要继承自控制器父 ...

  2. PHP扩展编写、PHP扩展调试、VLD源码分析、基于嵌入式Embed SAPI实现opcode查看

    catalogue . 编译PHP源码 . 扩展结构.优缺点 . 使用PHP原生扩展框架wizard ext_skel编写扩展 . 编译安装VLD . Debug调试VLD . VLD源码分析 . 嵌 ...

  3. jQuery1.9.1源码分析--数据缓存Data模块

    jQuery1.9.1源码分析--数据缓存Data模块 阅读目录 jQuery API中Data的基本使用方法介绍 jQuery.acceptData(elem)源码分析 jQuery.data(el ...

  4. Backbone.js源码分析(珍藏版)

    源码分析珍藏,方便下次阅读! // Backbone.js 0.9.2 // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. // Backbone ...

  5. Thinkphp源码分析系列–开篇

    目前国内比较流行的php框架由thinkphp,yii,Zend Framework,CodeIgniter等.一直觉得自己在php方面还是一个小学生,只会用别人的框架,自己也没有写过,当然不是自己不 ...

  6. basket.js 源码分析

    basket.js 源码分析 一.前言 basket.js 可以用来加载js脚本并且保存到 LocalStorage 上,使我们可以更加精准地控制缓存,即使是在 http 缓存过期之后也可以使用.因此 ...

  7. Volley源码分析(2)----ImageLoader

    一:imageLoader 先来看看如何使用imageloader: public void showImg(View view){ ImageView imageView = (ImageView) ...

  8. Fresco 源码分析(三) Fresco服务端处理(2) Producer具体实现的内容

    我们以mProducerFactory.newNetworkFetchProducer()为例,因为这些创建新的producer的方式类似,区别在于是否有包装的处理器,即如果当前处理器中没有正在处理的 ...

  9. Fresco 源码分析(三) Fresco服务端处理(1) ImagePipeline为何物

    4.3 服务端的处理 备注: 因为是分析,而不是设计,所以很多知识我们类似于插叙的方式叙述,就是用到了哪个知识点,我们再提及相关的知识点,如果分析到了最后,我想想是不是应该将这个架构按照设计的方式,重 ...

  10. Fresco 源码分析(一) DraweeView-DraweeHierarchy-DraweeController(MVC) DraweeHierachy+DraweeController的分析

    4.1.5.2 模型层DraweeHierachy继承体系以及各个类的作用 DraweeHierachy (I) --| SettableDraweeHierarchy (I) ------| Gen ...

随机推荐

  1. spingMVC aop不生效的解决方式

    从网上搜索了一些资料,参考了下面的这个解决方案 http://blog.csdn.net/mmm333zzz/article/details/16858209

  2. 【面经】腾讯和YY实习生面试总结

    [前言] 之前的四月份和五月份各面试了腾讯和YY的暑假实习,腾讯的失败了,YY的成功了.面试中我总会遇到自己不懂的,所幸的是不懂的越来越少,自己也一步一脚印得攻克自己不懂的.此时六月份的我再回顾起来, ...

  3. [翻译]成为顶尖程序员应当学什么?Python、C还是Ruby?

    原文地址(墙外):https://medium.com/life-tips/should-you-learn-python-c-or-ruby-to-be-a-top-coder-infographi ...

  4. Visual Studio 2017离线安装包下载、安装

    1. 首先下载在线安装exe,官网地址https://www.visualstudio.com/zh-hans/downloads/ 2. 运行CMD, 执行脚本 vs_enterprise.exe ...

  5. CentOS 下搭建FTP服务器

    vsftpd是Linux下比较著名的FTP服务器,搭建FTP服务器当然首选这个.本文介绍了在CentOS 6 4下安装vsftpd.配置虚拟用户登录FTP的过程.正 vsftpd是Linux下比较著名 ...

  6. shell十分钟教程

    1.先介绍下shell的工作原理 Shell可以被称作是脚本语言,因为它本身是不需要编译的,而是通过解释器解释之后再编译执行,和传统语言相比多了解释的过程所以效率会略差于传统的直接编译的语言. 但是s ...

  7. Unreal Engine 4 Radiant UI 入门教程(零)在场景中摆放网页

    相关的学习资源: https://forums.unrealengine.com/showthread.php?12097-PLUGIN-RadiantUI-SDK-UIs-HUDs-Interact ...

  8. Java电器商场小系统--简单的java逻辑

    //商场类public class Goods { int no; //编号 String name; //商品名称 double price; //商品价格 int number; //商品数量 / ...

  9. 设计模式 - 观察者模式(JDK)

    定义:观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新. 对象:    抽象主题角色:每个抽象主题角色都可以有任意数量的观察者.抽象主题提供可 ...

  10. JS实现悬浮导航的制作--web前端

    思想:导航在这里只有两种状态,一种是初始状态.一种是固定布局状态.实现悬浮导航其实就是通过Javascript脚本语言控制导航的两种状态,主要是对两种状态成立条件的判断,明确了这些,实现起来就不会太难 ...