[评论送书 ]手撕源码,实现一个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 替我们做了哪些工作?



相关文章
|
24天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
64 2
|
4月前
|
JavaScript 前端开发
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
这篇文章主要讨论了axios的使用、原理以及源码分析。 文章中首先回顾了axios的基本用法,包括发送请求、请求拦截器和响应拦截器的使用,以及如何取消请求。接着,作者实现了一个简易版的axios,包括构造函数、请求方法、拦截器的实现等。最后,文章对axios的源码进行了分析,包括目录结构、核心文件axios.js的内容,以及axios实例化过程中的配置合并、拦截器的使用等。
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
|
8天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
23 2
|
4月前
|
JavaScript 前端开发
【Vue面试题二十七】、你了解axios的原理吗?有看过它的源码吗?
文章讨论了Vue项目目录结构的设计原则和实践,强调了项目结构清晰的重要性,提出了包括语义一致性、单一入口/出口、就近原则、公共文件的绝对路径引用等原则,并展示了单页面和多页面Vue项目的目录结构示例。
|
3月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
464 37
面试官: 请你手写一份 Call()源码,看完此篇不用担心!
面试官: 请你手写一份 Call()源码,看完此篇不用担心!
|
4月前
|
存储 JavaScript 前端开发
JS浅拷贝及面试时手写源码
JS浅拷贝及面试时手写源码
|
5月前
|
存储 安全 Java
Android面试题之ArrayList源码详解
ArrayList是Java中基于数组实现的列表,提供O(1)的索引访问,但插入和删除操作平均时间复杂度为O(n)。默认容量为10,当需要时会通过System.arraycopy扩容。允许存储null,非线程安全。面试常问:List是接口,ArrayList是其实现之一,推荐使用List接口编程以实现更好的灵活性。更多详情见[ArrayList源码](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/ArrayList.java#ArrayList.Node)。
34 2
|
6月前
|
存储 安全 Java
《ArrayList & HashMap 源码类基础面试题》面试官们最喜欢问的ArrayList & HashMap源码类初级问,你都会了?
《ArrayList & HashMap 源码类基础面试题》面试官们最喜欢问的ArrayList & HashMap源码类初级问,你都会了?
42 0
|
6月前
|
安全 Java 数据安全/隐私保护
Java基础4-一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!(二)
Java基础4-一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!(二)
48 0

热门文章

最新文章