node---进阶篇(koa)

358 阅读3分钟

先写个小栗子

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
  1. 实现创建服务的功能,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) // 解构传入的所有参数
  }
}
  1. 测试一下listen
let Koa = require('./application')
let app = new Koa()

app.use((req, res) => { // 还没写中间件,所以这里还不是ctx和next
  res.end('hello world')
})

app.listen(3000)
  1. 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)
  }
}

  1. 测试一下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';
})
  1. 访问localhost:3000/candy
1 /candy
2 /candy
3 /candy
4 undefined
5 undefined
6 undefined
7 undefined

request.js

  1. 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

context.js

response.js