koa源码打开有四个文件:
application.js是核心文件;context.js是对ctx的封装;request.js是对请求的封装;response.js是对ctx.body的封装;
1.app.use的简单实现
const app = {}
app.middlewares = []
app.use = function(cb){
app.middlewares.push(cb)
}
function dispath(index) {
if (index === app.middlewares.length) { return () => {}}
const route = app.middlewares[index]
route(() => dispath(index + 1))
}
app.use((next) => {
console.log(1)
next()
console.log(5)
})
app.use((next) => {
console.log(2)
next()
console.log(4)
})
app.use(() => {
console.log(3)
})
dispath(0)
😂,发现没😄上边这简单的代码就实现了洋葱模型;把传到app.use的函数先用数组保存起来,然后再依次遍历,执行
还可以用下边这种方式实现:
const app = {}
app.middlewares = []
app.use = function(cb){
app.middlewares.push(cb)
}
app.use((next) => {
console.log(1)
next()
console.log(5)
})
app.use((next) => {
console.log(2)
next()
console.log(4)
})
app.use((next) => {
console.log(3)
next()
})
// 这里的写法很像redux源码,`...args就是fn传的参数;pre函数在执行的时候,传了个空函数进去,执行了next函数,这这这...太棒了`
const fn = app.middlewares.reduce((pre,next) => (...args) => pre(() => next(...args)))
fn(() => {})
koa 简单版本
application文件
const Emitter = require('events')
const http = require('http')
const Stream = require('stream')
const request = require('./request')
const response = require('./response')
const context = require('./context')
class Application extends Emitter{
constructor () {
super()
this.middleware = []
// 使用 Object.create(),拥有了原有的功能,仅仅拥有了原有的功能,将对象原型链上hasOwnProperty等属性去掉了
this.request = Object.create(request)
this.response = Object.create(response)
this.context = Object.create(context)
}
createContext (req, res) {
const ctx = this.context
ctx.request = this.request // ctx.request ctx.response是koa封装的
ctx.response = this.response
ctx.req = ctx.request.req = req // ctx.req ctx.res是默认的请求和响应
ctx.res = ctx.request.res = res
return ctx
}
// 中间件组合
compose(ctx, middleware) { //处理了promise的逻辑
function dispath(index) {
if (index === middleware.length) { return Promise.resolve()}
const middlewareitem = middleware[index]
return Promise.resolve(middlewareitem(ctx, () => dispath(index + 1)))
}
return dispath(0)
}
// 处理request请求
handleRequest (req, res) {
// 先创建上下文
const ctx = this.createContext(req, res)
res.statusCode = 404
// 再把所有中间件进行组合
let p = this.compose(ctx, this.middleware)
p.then( () => {
const body = ctx.body
if (body instanceof Stream) {
// res.setHeader('Content-Disposition','attachment')
body.pipe(res)
} else if(typeof body === 'number') {
res.setHeader('Content-Type', 'text/plain;charset=utf8')
res.end(body.toString())
} else if (typeof body === 'object') {
res.setHeader('Content-Type', 'application/json;charset=utf8')
res.end(JSON.stringify(body))
} else if(typeof body === 'string' || Buffer.isBuffer(body)) {
res.setHeader('Content-Type', 'text/plain;charset=utf8')
res.end(body)
} else {
res.end(`not found`)
}
}).catch((e) => {
this.emit(`error`, e)
})
}
// 收集中间件
use (fn) {
this.middleware.push(fn)
}
// 创建服务并监听端口号
listen (...args) {
const server = http.createServer(this.handleRequest.bind(this))
server.listen(...args)
}
}
module.exports = Application
context文件
const proto = {};
function defineGetter(property, key) {
// Object.prototype.__defineGetter__()
// 已废弃;方法可以将一个函数绑定在当前对象的指定属性上,当那个属性的值被读取时,你所绑定的函数就会被调用。
// __defineSetter__同理
// koa源码用的是delegates这个npm包,这个npm包内部也是用的__defineGetter__,和__defineSetter__
proto.__defineGetter__(key, function() {
return this[property][key];
});
}
function defineSetter(property, key) {
proto.__defineSetter__(key, function(value) {
this[property][key] = value
});
}
defineGetter("request", "path");
defineGetter('request','url')
defineGetter('response','body')
defineSetter('response','body')
module.exports = proto;
request文件
const url = require("url");
module.exports = {
// 访问器的写法
get url() {
return this.req.url;
},
get path() {
const { pathname } = url.parse(this.req.url, true);
return pathname;
}
};
response文件
module.exports = {
set body(val) {
console.log(this)
// this.res.statusCode = 200
this._body = val
},
get body() {
return this._body
}
}
简单版本看起来更加通俗易懂,不像koa2官方库引用了npm官网上其他的包;当然也没有太多兼容处理,只是表达出来了核心内容