[评论送书 ]手撕源码,实现一个Koa。,2024年最新学生会面试答题技巧

简介: [评论送书 ]手撕源码,实现一个Koa。,2024年最新学生会面试答题技巧

正文

Koa Context 将 node 的 requestresponse 对象封装到单个对象中,为编写 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.contextctx 添加其他属性。这对于将 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 无内容响应
  1. nodehttp中是 res.end(“我是返回内容”) 返回响应内容的。在koa中我们是通过ctx.body=“” 来设置响应内容的。这里有人会问了,ctx.body和resopnse.body 有啥关系。其实他们是一个东西,ctx里面封装了response.body。
  2. 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 替我们做了哪些工作?



相关文章
|
2天前
|
安全 Java 数据安全/隐私保护
Java基础4-一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!(二)
Java基础4-一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!(二)
12 0
|
2天前
|
JSON 安全 Java
Java基础4-一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!(一)
Java基础4-一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!(一)
10 0
|
28天前
|
数据安全/隐私保护 Python 算法
Python 蜻蜓fm有声书批量下载 支持账号登录 原创源码,2024年最新Python面试回忆录
Python 蜻蜓fm有声书批量下载 支持账号登录 原创源码,2024年最新Python面试回忆录
|
28天前
|
前端开发 JavaScript 程序员
async-validator 源码学习(一):文档翻译,2024年最新如何面试大厂
async-validator 源码学习(一):文档翻译,2024年最新如何面试大厂
|
28天前
|
移动开发 前端开发 JavaScript
10款精美的web前端源码的特效,2024年最新面试题+笔记+项目实战
10款精美的web前端源码的特效,2024年最新面试题+笔记+项目实战
|
28天前
|
移动开发 前端开发 JavaScript
10款精美的web前端源码的特效(1),头条前端面试节奏
10款精美的web前端源码的特效(1),头条前端面试节奏
|
29天前
|
NoSQL 算法 Java
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
|
29天前
|
JSON Java API
Android 深入Http(5)从Retrofit源码来看Http,最新Android开发面试解答
Android 深入Http(5)从Retrofit源码来看Http,最新Android开发面试解答
|
1月前
|
存储 NoSQL Redis
Redis源码、面试指南(5)多机数据库、复制、哨兵、集群(下)
Redis源码、面试指南(5)多机数据库、复制、哨兵、集群
236 1
|
10天前
|
存储 算法 Java
JAVA后端开发面试题库
JAVA后端开发面试题库
19 1