手撕koa,从零掌握koa的实现原理(3)

572 阅读3分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

手撕koa,从零掌握koa的实现原理(3)

往期更文

手撕koa,从零掌握koa的实现原理(1)

手撕koa,从零掌握koa的实现原理(2)

前言

  • 回顾昨天的内容,我们先初步实现了listen方法,listen方法的主要作用是使用http模块开启一个服务,最后我们导出了Application
  • koa/koa/lib/application.js
    const http = require('http')
    const EventEmitter = require('events')
    class Application extends EventEmitter {
      constructor(){
        super()
      }
      handleRequest = (req, res) => {
        res.statusCode = 200
        //因为我们响应的是中文,所以需要设置一下content-type,否则浏览器会显示乱码
        res.setHeader('content-type','text/plain;charset=utf-8')
        res.end('自己实现的Koa')
      }
      listen(){
        const server = http.createServer(this.handleRequest)
        server.listen(...arguments)
      }
    }
    
    module.exports = Application
    

如何让每个应用的上下文对象分离?

  • 每个koa的实例上会有一个上下文对象context,那么koa在创建单独的应用的时候是如何让每个应用的上下文对象context分离的呢?带着这个问题我们来往下走。
    1. 创建三个文件
    • koa/koa/lib/context用于扩展我们的koa上下文对象。
    • koa/koa/lib/request用于扩展我们的koa上下文对象中的request
    • koa/koa/lib/response用于扩展我们的koa上下文对象中的response
    1. 每个文件先简单声明三个对象,然后导出。
    • koa/koa/lib/context.js
      const context = {}
      
      module.exports = context
      
    • koa/koa/lib/request.js
      const request = {}
      
      module.exports = request
      
    • koa/koa/lib/response.js
      const response = {}
      
      module.exports = response
      
    1. 在我们new Application的时候我们应该给应用的实例创建context request response三个对象,来改造我们的Application吧。
    • koa/koa/lib/application.js
      const http = require('http')
      const EventEmitter = require('events')
      const context = require('./context');
      const request = require('./request');
      const response = require('./response');
      class Application extends EventEmitter {
        constructor(){
          super()
          this.context = context
          this.request = request
          this.response = response
        }
        handleRequest = (req, res) => {
          res.statusCode = 200
          res.setHeader('content-type','text/plain;charset=utf-8')
          res.end('自己实现的Koa')
        }
        listen(){
          const server = http.createServer(this.handleRequest)
          server.listen(...arguments)
        }
      }
      
      module.exports = Application
      
  • 需要注意的是我们的构造函数constructor中这样写可以吗?
      this.context = context
      this.request = request
      this.response = response
    
    答案当然是否定的,这样写的话,我们在每次new Koa()的时候拿到的上下文就都是同一个,我们需要的是每次new Koa()的时候,都单独创建一个单独的上下文对象,即每个应用实例的上下文对象应该都是分离的。
  • 那我们就需要重新改造我们的Application了,像下面这样,我们需要使用Object.create()方法,每次都创建一个新的对象,让新对象的__proto__指向我们扩展的context上下文对象,从而实现继承,这样我们就可以让每个应用实例的上下文对象分离了,但是他们又可以共享我们扩展的公共的属性和方法。
    const http = require('http')
      const EventEmitter = require('events')
      const context = require('./context');
      const request = require('./request');
      const response = require('./response');
      class Application extends EventEmitter {
        constructor(){
          super()
          this.context = Object.create(context)
          this.request = Object.create(request)
          this.response = Object.create(response)
        }
        handleRequest = (req, res) => {
          res.statusCode = 200
          res.setHeader('content-type','text/plain;charset=utf-8')
          res.end('自己实现的Koa')
        }
        listen(){
          const server = http.createServer(this.handleRequest)
          server.listen(...arguments)
        }
      }
    
      module.exports = Application
    

最后需要强调的

我们学习源码,不只是简单的看懂作者的怎么写的,怎么去实现的,代码层面的东西固然是重要的,但是最重要的还是,我们要吸收作者背后优秀、巧妙的思想,这些优秀巧妙的思想,才是我们进步的强大源泉,在代码的枯燥中,方能苦中作乐,对作者的构思巧妙惊为天人。