正文
Koa Context 将 node 的 request
和 response
对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。 这些操作在 HTTP 服务器开发中频繁使用,它们被添加到此级别而不是更高级别的框架,这将强制中间件重新实现此通用功能。
request.js和response.js文件
在核心目录,我们提到了这两个文件,这两个文件此时就派上了用场。这两个文件具体实现了啥呢?这两个文件定义了ctx.resopnse和ctx.request的结构,也就是上面使用dir输出的结果。在koa中文文档中可以具体的看到结构,可以自行查阅。
Koa Request
对象是在 node 的 原生请求对象之上的抽象,提供了诸多对 HTTP 服务器开发有用的功能。
Koa Response
对象是在 node 的原生响应对象之上的抽象,提供了诸多对 HTTP 服务器开发有用的功能。
实现ctx
定义context.js
const context={ } module.exports=context 定义request.js const request={ } module.exports=request 定义response.js const resposne={ } module.exports=response
use中封装ctx
我们在上面导出koa章节中可以看到,在app.use的时候,我们传的参数是(request,response),源koa传的的ctx,所以我们就知道了,koa是在app.use的时候创建了一个ctx。
在本章开头的时候,我们又提到每次请求的ctx都是全新的ctx。
综合以上两点,我们可以基本编写出下面的代码。(为了代码的清晰易读,我们封装了一个createcontext函数来创建上下文。)
const Context = require(‘./context’) const Request = require(‘./request’) const Response = require(‘./response’) class Application { constructor(){ this.context = Object.create(Context); this.request = Object.create(Request); this.response = Object.create(Response); } use(fn) { this.fn = fn } createContext = (req, res) => { const ctx = Object.create(this.context); const request = Object.create(this.request); const response = Object.create(this.response); ctx.app = request.app = response.app = this ctx.request = request; ctx.request.req = ctx.req = req; ctx.response = response; ctx.response.res = ctx.res = res; ctx.originalUrl = request.originalUrl = req.url ctx.state = {} return ctx } callback = (req, res) => { let ctx = this.createContext(req, res) this.fn(ctx) } listen() { const server = http.createServer(this.callback); console.log(…arguments) server.listen(…arguments) } }
首先我们在constructor中定义了一个context对象,这里会在constructor定义是因为Koa的app上默认导出context属性。
app.context
是从其创建ctx
的原型。您可以通过编辑app.context
为ctx
添加其他属性。这对于将ctx
添加到整个应用程序中使用的属性或方法非常有用,这可能会更加有效(不需要中间件)和/或 更简单(更少的require()
),而更多地依赖于ctx
,这可以被认为是一种反模式。例如,要从
ctx
添加对数据库的引用:
app.context.db = db(); app.use(async ctx => { console.log(ctx.db); });
然后再callback中,我们针对response和request进行了二次封装。
再来看看这段代码:
app.context.db = db(); app.use(async ctx => { console.log(ctx.db); });
再使用use之前,通过app.context对context进行了修改。当使用use函数的时候,是不是直接进入了callback函数,此时的this.context已经是修改过的了。
测试
const Koa=require(‘./application.js’) const app=new Koa() app.use((ctx) => { // 测试1 ctx.response.res.end(" hello my koa") // 测试2 ctx.res.end(" hello my koa") }) app.listen(3000,()=>{ console.log(‘3000’) })
正常访问!
封装request.js
明确一个事实:request类的属性是通过getter和setter设置的。为什么会这样设置?这样设置的好处是可以方便的设置和获取到值。是不是有点懵逼!请听我细细道来。
先来看一下Koa中request类所绑定的属性,官方链接。
我这里简单的列举几个:
request.header=
设置请求头对象。
request.headers
请求头对象。别名为 request.header
.
request.headers=
设置请求头对象。别名为 request.header=
request.url
获取请求 URL.
request.url=
设置请求 URL, 对 url 重写有用。
request.originalUrl
获取请求原始URL。
1. 这里对于每个属性都有设置和获取的功能,使用getter和setter可以很好的实现。 get url () { return this.req.url }, 3. 这里的每个属性是如何设置的,如果我们对request本身设置有效吗? const request={ url:‘’, header:{ } } const request={ set url (val) { this.req.url = val } get url () { return this.req.url }, } request.socket的getter socket在这里指套接字。套接字的概念这里不赘述! get socket () { return this.req.socket }, request.protocol的getter 返回请求协议,“https” 或 “http”。当 app.proxy 是 true 时支持 X-Forwarded-Proto。 先判断套接字中是否存在encrypted(加密),如果加密,就是https, X-Forwarded-Proto用来确定客户端与代理服务器或者负载均衡服务器之间的连接所采用的传输协议(HTTP 或 HTTPS) X-Forwarded-Proto: https X-Forwarded-Proto: http get protocol () { if (this.socket.encrypted) return ‘https’ if (!this.app.proxy) return ‘http’ const proto = this.get(‘X-Forwarded-Proto’) return proto ? proto.split(/\s*,\s*/, 1)[0] : ‘http’ }, 这里有一个get函数,主要时根据字段,从请求头中获取数据。 get (field) { const req = this.req switch (field = field.toLowerCase()) { case ‘referer’: case ‘referrer’: return req.headers.referrer || req.headers.referer || ‘’ default: return req.headers[field] || ‘’ } }, request.host的getter 存在时获取主机(hostname:port)。当 app.proxy 是 true 时支持 X-Forwarded-Host,否则使用 Host。 get host () { const proxy = this.app.proxy let host = proxy && this.get(‘X-Forwarded-Host’) if (!host) { if (this.req.httpVersionMajor >= 2) host = this.get(‘:authority’) if (!host) host = this.get(‘Host’) } if (!host) return ‘’ return host.split(/\s*,\s*/, 1)[0] }, request.origin的getter 获取URL的来源,包括 protocol 和 host。 例如我请求:http://localhost:3000/index?a=3, origin返回的是http://localhost:3000 get origin () { return ${this.protocol}://${this.host} }, request.href的getter 获取完整的请求URL,包括 protocol,host 和 url。 href支持解析 GET http://example.com/foo 例如我访问http://localhost:3000/index?a=3 href返回http://localhost:3000/index?a=3 get href () { if (/^https?😕//i.test(this.originalUrl)) return this.originalUrl return this.origin + this.originalUrl },
注意:这里的this.originalUrl在封装ctx的时候已经绑定过了
request.header 的getter和setter
请求头对象。这与 node http.IncomingMessage
上的 headers
字段相同
get header () { return this.req.headers }, set header (val) { this.req.headers = val },
request的属性是很多的,我们就不展开了,反正知道了原理,大家慢慢自己加吧。
封装response.js
对比request的封装,response的封装稍微有些不同,因为,对于request来说大部分的封装是getter,而response的封装大部分都是setter
在request部分我们阐述了三个使用getter和setter的原因。在resposne中最主要的原因我觉得是改变set的对象。
其实想一想很简单,例如在网络请求中我们会经常遇到各种状态:404 200等等,这些在node的http模块中,是用resposne.status进行改变的。假设我们在koa的response直接设置,你觉得会有用吗?简单概括一句话:koa的request和respsone是对nodehttp模块的二次封装,并且底层还是对nodehttp模块的操作。
response.status的getterh和setter
获取响应状态。默认情况下,response.status
设置为 404
而不是像 node 的 res.statusCode
那样默认为 200
。
默认’404’,这里的默认是在何时默认的时候呢,其实是在接收到请求后就设置为404,也就是说在callback的时候开始设置为404。(注意:http中res.statusCode用来标记状态码,在Koa中这个被封装成status)
callback = (req, res) => { let ctx = this.createContext(req, res) const res = ctx.res res.statusCode = 404 this.fn(ctx) } response.status的实现 get status () { return this.res.statusCode }, set status (code) { if (this.headerSent) return assert(Number.isInteger(code), ‘status code must be a number’) assert(code >= 100 && code <= 999, invalid status code: ${code}) this._explicitStatus = true this.res.statusCode = code if (this.req.httpVersionMajor < 2) this.res.statusMessage = statuses[code] if (this.body && statuses.empty[code]) this.body = null }, response.body的getter和setter
首先我们要知道body是用来干嘛的。body是用来设置响应主体的,也就是返回响应的内容的。这些内容支持以下格式:
string
写入Buffer
写入Stream
管道Object
||Array
JSON-字符串化null
无内容响应
- nodehttp中是 res.end(“我是返回内容”) 返回响应内容的。在koa中我们是通过ctx.body=“” 来设置响应内容的。这里有人会问了,ctx.body和resopnse.body 有啥关系。其实他们是一个东西,ctx里面封装了response.body。
- koa中通过设置ctx.body,就能返回内容,其实本质还是使用了res.end(),通过res.end(ctx.body)来返回内容。res.end的调用时机在这里是放在callback中(具体的原因我们后面会说到)
const response = { _body: undefined, get body() { return this._body }, set body(originContent) { this.res.statusCode = 200; this._body = originContent; } }; 封装context.js
先谈谈Koa用到的delegates。这是一个实现了代理模式的包。对于Koa来说,context就是response和request的代理,通过ctx可以直接拿到request和response的属性和方法。
下面的是Koa主要用到的两个方法。其实最终的效果和封装request和response的效果一致。
__defineGetter__
方法可以将一个函数绑定在当前对象的指定属性上,当那个属性的值被读取时,你所绑定的函数就会被调用。
__defineSetter__
方法可以将一个函数绑定在当前对象的指定属性上,当那个属性被赋值时,你所绑定的函数就会被调用。
(这两个方法已废弃: 该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。)
Delegator.prototype.setter = function (name) { var proto = this.proto; var target = this.target; this.setters.push(name); proto.defineSetter(name, function (val) { return this[target][name] = val; }); return this; }; Delegator.prototype.getter = function (name) { var proto = this.proto; var target = this.target; this.getters.push(name); proto.defineGetter(name, function () { return this[target][name]; }); return this; }; 这里我们将delegates的核心逻辑抽离,封装context function defineGetter(target, key) { context.defineGetter(key, function () { return this[target][key] }) } function defineSetter(target, key) { context.defineSetter(key, function (value) { this[target][key] = value }) } const context = {}; defineGetter(‘request’, ‘path’) defineGetter(‘response’, ‘body’) ) module.exports = context;
这里我们就列了两个,其他的不再赘述。
ctx.body再追述
在上面我们谈到了response.body以及ctx通过代理模式,拿到了response.body.
在Koa的源码中,针对不同格式的内容进行了不同的处理.大家简单看一下就可以。
response = { set body (val) { const original = this._body this._body = val // no content if (val == null) { if (!statuses.empty[this.status]) { if (this.type === ‘application/json’) { this._body = ‘null’ return } this.status = 204 } return } // 内容存在(设置了内容),这是状态码为200 if (!this._explicitStatus) this.status = 200 // string 字符串 if (typeof val === ‘string’) { if (setType) this.type = /^\s* this.length = Buffer.byteLength(val) return } // buffer if (Buffer.isBuffer(val)) { if (setType) this.type = ‘bin’ this.length = val.length return }
Vue 面试题
1.Vue 双向绑定原理
2.描述下 vue 从初始化页面–修改数据–刷新页面 UI 的过程?
3.你是如何理解 Vue 的响应式系统的?
4.虚拟 DOM 实现原理
5.既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异?
6.Vue 中 key 值的作用?
7.Vue 的生命周期
8.Vue 组件间通信有哪些方式?
9.watch、methods 和 computed 的区别?
10.vue 中怎么重置 data?
11.组件中写 name 选项有什么作用?
12.vue-router 有哪些钩子函数?
13.route 和 router 的区别是什么?
14.说一下 Vue 和 React 的认识,做一个简单的对比
15.Vue 的 nextTick 的原理是什么?
16.Vuex 有哪几种属性?
17.vue 首屏加载优化
18.Vue 3.0 有没有过了解?
19.vue-cli 替我们做了哪些工作?