大家好,我是wo不是黄蓉,今年学习目标从源码共读开始,希望能跟着若川大佬学习源码的思路学到更多的东西。
可以以通过new
关键字创建,说明是个构造函数,也可以用es6
的class
来实现,内置use
和listen
方法,需要构造函数支持这两个方法
//创建koa对象,内置use和listen方法
class Koa {
constructor() {}
use() {
console.log('use')
}
listen() {
console.log('listen')
}
}
module.exports = Koa
创建好后,执行index.cjs
打印
use
use
listen
index.cjs
测试文件内容
// const Koa = require('koa')
const Koa = require('./koa/index.cjs')
const app = new Koa()
//默认中间件函数是个异步函数,如果是同步的会怎么样
app.use((ctx, next) => {
// app.emit('sayHello', 'hello world')
console.log('1')
next()
console.log('2')
ctx.body = 'Hello World'
})
app.use((ctx) => {
console.log('3')
// ctx.body = 'hello'
})
// app.on('sayHello', (args) => {
// console.log(111, args)
// })
app.listen(3000)
实现listen
方法。创建一个服务并且启动
//创建服务
listen() {
const server = http.createServer(this.requestListener)
server.listen(3001, '0.0.0.0', () => {
console.log('server is running on :', 'http://localhost:3001')
})
}
//请求回调函数,当发起请求时会被调用
requestListener(req, res) {
//当请求时返回hello world,请求http://localhost:3001页面会打印出来hello world
res.end('hello world')
}

处理中间件
use(middleware) {
//校验middleware是否是个函数
if (!Utils.isFunction(middleware)) {
throw new TypeError('middleware 必须是个函数!')
}
this.middleware.push(middleware)
}
listen
完请求时requestListener
方法中this
丢失问题

用箭头函数,使用function
会形成一个自己的作用域会造成this
丢失,导致调不到this.handleRequest
方法
//请求监听函数
requestListener() {
//处理请求监听函数,也就是compose函数
const fn = compose(this.middleware)
//没有触发就调用了handleRequest方法,使用箭头函数,将this指向父级作用域
const handleRequest = (req, res) => {
const ctx = {
request: req,
response: res
}
return this.handleRequest(ctx, fn)
}
return handleRequest
}
handleRequest
方法,处理请求函数和处理响应,执行fn
方法就是触发compose
方法实现挨个调用中间件函数
//请求处理函数
handleRequest(ctx, fn) {
//根据上下文执行fn
//第一次执行第0个监听函数
//执行compose函数,这边是个函数,不然第一次不会执行,也不会按照顺序执行compose返回结果是个promise
fn(ctx)
}
compose
方法,我是放在koa
对象外面的,也可以当成一个工具类方法
//中间件挨个调用函数
function compose(middlewares) {
return function (context, next) {
//第一次执行fn(0)
return dispatch(0)
function dispatch(i) {
//只能执行第一个函数
let middlewareFn = middlewares[i]
//当i和中间件长度相等时退出,可执行的函数为空
if (i === middlewares.length) middlewareFn = next
//不判断的话会循环执行,i一直在++,i终止的条件就是中间件函数执行完毕了
if (!middlewareFn) return Promise.resolve()
//返回一个函数异步函数,dispatch.bind 绑定参数,可以在调用函数时,预先传入一些参数,
return Promise.resolve(middlewareFn(context, dispatch.bind(null, i + 1)))
}
}
}

处理响应
koa
是通过getter/ setter
来实现代理的,只是对原生的request
,response
做了封装。再将request
和response
对象都封装在context
中,通过ctx.body
返回响应内容,但是response
上本上没有body
属性,因此需要通过自定义封装来扩展body
属性。
源码中为了方便代理,引入了delegates
库,通过delegate
实现对response
和request
的代理。
response.js
封装res.end
用来设置body
module.exports = {
get body() {
return this._body
},
set body(val) {
this._body = val
//原生是通过res.end返回响应的
this.res.end(val)
}
}
context.js
通过response
来代理context
module.exports = {
get body() {
this.response.body
},
set body(val) {
this.response.body = val
}
}
设置上下文,将request
和response
设置到conetxt
上
//创建上下文
createContext(req, res) {
//将原有的方法做转换
const context = Object.create(this.context)
const request = (context.request = Object.create(this.request))
const response = (context.response = Object.create(this.response))
context.app = request.app = response.app = this
context.req = request.req = response.req = req
context.res = request.res = response.res = res
request.ctx = response.ctx = context
request.response = response
response.request = request
context.originalUrl = request.originalUrl = req.url
context.state = {}
return context
}
将request
,response
,context
关联到koa
对象上
const context = require('./context.cjs')
const request = require('./request.cjs')
const response = require('./response.cjs')
constructor() {
//保存中间件
this.middleware = []
this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)
}
重启项目

这样一个简单的koa
实现就好了,源码中还有一些错误处理的地方,属于优化部分,整体流程大概就是这样的。
源码地址
小结
整体代码不是很难,但是可以给我们提供一种代码编程思路,当你想对原有的东西进行扩展api
时就可以使用setter/getter
代理来实现新属性的代理,这样不仅不会影响原有的东西,而且还能更灵活方便的处理原有的api
操作。
网友评论