先写个小栗子
1. 使用原生http
const http = require('http')
let server = http.createServer((req, res) => {
res.end('hellow candy')
})
server.listen(4000)
2. 使用koa
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next) => {
ctx.body = 'Hello Candy';
})
3.对比
- 相比于原生,Koa多了use和listen方法
- use接收2个参数use(ctx,next)
- ctx携带req,res等常用方法
- next()让出当前进程,执行下一个
listen
listen是http的语法糖,底层原理还是是用了http.createServer()进行监听端口。
ctx
合并了之前的req,res,并拓展了更多的属性和方法,如ctx.query,ctx.path,ctx.body
use
koa的核心---中间件(middleware),解决了异步编程中回调地狱的问题,基于promise,洋葱模型,其原理主要是一个极其精妙的 compose 函数。在使用时,用 next() 方法,从上一个中间件跳到下一个中间件。
koa源码地址
手写koa
在lib下创建四个文件application.js,context.js,request.js,response.js
- 通过源码package.json,application.js为入口文件。
- context.js是上下文对象相关
- request.js是请求文件
- response.js是响应文件
application.js
- 通过上面的栗子可以看出,koa是一个类,通过new得到一个实例,实例上主要有2个方法,use和listen
- listen是http的语法糖,所以这里要引入http,
- koa引入events模块继承EventEmitter,监听实例的error事件,实现自己的错误处理机制。
先把需要引入的几个文件对象导出---context.js
let proto = {}
module.exports = proto
先把需要引入的几个文件对象导出---request.js
let request = {}
module.exports = request
先把需要引入的几个文件对象导出---response.js
let response = {}
module.exports = response
然后开始写----application.js
let http = require('http')
let EventEmitter = require('events')
let context = require('./context')
let request = require('./request')
let response = require('./response')
// Koa继承于EventEmitter
class Koa extends EventEmitter {
constructor () {
super()
}
// use方法
use () {
}
// listen方法
listen () {
}
}
module.exports = Koa
- 实现创建服务的功能,use方法接收一个回调函数,存起来,listen创建一个http服务并监听端口
class Koa extends EventEmitter {
constructor () {
super()
this.fn
}
use (fn) {
this.fn = fn // this.fn并保存起来相当于一个中间人,调用use的时候传入回调,listen中创建服务的时候使用这个回调函数
}
listen (...args) {
let server = http.createServer(this.fn) // 把this.fn放入回调
server.listen(...args) // 解构传入的所有参数
}
}
- 测试一下listen
let Koa = require('./application')
let app = new Koa()
app.use((req, res) => { // 还没写中间件,所以这里还不是ctx和next
res.end('hello world')
})
app.listen(3000)
- use中回调函数的参数之一-----ctx
思路:
- ctx绑定了很多请求和响应相关的数据和方法,如ctx.path、ctx.query、ctx.body()
- 用户调用use方法时,把这个回调fn存起来
- 创建一个createContext函数用来创建上下文
- 创建一个handleRequest函数用来处理请求
- 用户listen时将handleRequest放进createServer回调中
- 在函数内调用fn并将上下文对象传入,用户就得到了ctx
class Koa extends EventEmitter {
constructor () {
super()
// 全局保存传入的回调函数
this.fn
// 将引入的三个模块全局保存到实例中
this.context = context
this.request = request
this.response = response
}
use (fn) {
this.fn = fn
}
// 这是核心,创建ctx,加强之后返回ctx
createContext(req, res){
// Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__,修改创建出来新对象,不会影响老对象
const ctx = Object.create(this.context)
const request = ctx.request = Object.create(this.request)
const response = ctx.response = Object.create(this.response)
// 拓展属性
ctx.req = request.req = response.req = req
ctx.res = request.res = response.res = res
request.ctx = response.ctx = ctx
request.response = response
response.request = request
return ctx
}
// 创建一个处理请求的函数
handleRequest(req,res){
let ctx = this.createContext(req, res) // 创建ctx,得到加强的ctx
this.fn(ctx) // 调用用户给的回调,把ctx作为参数返回
res.end(ctx.body) // ctx.body用来输出到页面
}
listen (...args) {
let server = http.createServer(this.handleRequest.bind(this))// 这里使用bind调用,以防this丢失,注意bind(this)后面需要加()才能执行函数。
server.listen(...args)
}
}
- 测试一下ctx
app.use((ctx) => {
console.log(1,ctx.req.url)
console.log(2,ctx.request.req.url)
console.log(3,ctx.response.req.url)
console.log(4,ctx.request.url)
console.log(5,ctx.request.path)
console.log(6,ctx.url)
console.log(7,ctx.path)
ctx.body = 'Hello Candy';
})
- 访问localhost:3000/candy
1 /candy
2 /candy
3 /candy
4 undefined
5 undefined
6 undefined
7 undefined
request.js
- request可以拓展更多属性和方法,url,method,path
let url = require('url')
let request = {
// ctx.request.url上获取url,不用通过原生的req
get url() {
return this.req.url
},
get path() {
return url.parse(this.req.url).pathname
},
get query() {
return url.parse(this.req.url).query
},
get href() {
// support: `GET http://example.com/foo`
if (/^https?:\/\//i.test(this.originalUrl)) return this.originalUrl;
return this.origin + this.originalUrl;
},
get method() {
return this.req.method;
}
}
module.exports = request