빠른 구현 원리 학습 - 간단한 시뮬레이션

이 노트북은 Express의 일부 기능을 시뮬레이션하여 Express의 구현 원리를 학습합니다.

애플리케이션 초기화

mkdir my-express
cd ./my-express
npm init -y
npm i express

추가 app.js:

// app.js
const express = require('express')

const app = express()

app.get('/', (req, res) => {
    
    
  res.end('get /')
})

app.get('/about', (req, res) => {
    
    
  res.end('get /about')
})

app.listen(3000, () => {
    
    
  console.log('server is running on http://localhost:3000')
})

소스 코드 구조

코드 관점 에서 Express의 npm 패키지가 app.js먼저 로드되고 에서 필드를 찾습니다 .require('express')node_modules/express/package.jsonmain

main필드는 모듈의 엔트리 파일을 지정하며 필드가 없으면 main기본적으로 npm 패키지의 루트 디렉토리에 있는 파일이 로드됩니다 index.js.

node_modules/express/index.js방금 다른 모듈을 가져왔습니다.

'use strict';
module.exports = require('./lib/express');

node_modules/express/lib/express.jscreateApplication주로 메소드 수출

// 只截取了片段
// 主要导出了一个 `createApplication` 方法
exports = module.exports = createApplication;

// 创建并返回 app
function createApplication() {
    
    
  // app 本身是一个函数
  var app = function(req, res, next) {
    
    
    app.handle(req, res, next);
  };

  // 通过 mixin 方法扩展其他成员
  mixin(app, EventEmitter.prototype, false);
  // proto --> require('./application')
  mixin(app, proto, false);

  // 扩展 request
  app.request = Object.create(req, {
    
    
    app: {
    
     configurable: true, enumerable: true, writable: true, value: app }
  })

  // 扩展 response
  app.response = Object.create(res, {
    
    
    app: {
    
     configurable: true, enumerable: true, writable: true, value: app }
  })

  app.init();
  return app;
}

createApplication메서드는 mixin메서드를 통해 다른 많은 멤버를 내부적으로 확장하는 앱 함수를 만들고 반환합니다.

멤버의 핵심은 앱 개체가 생성되고 내부적 node_modules/express/lib/application.js으로 반환된 다음 이 개체에 , 등과 같은 많은 멤버가 추가된다는 것 use입니다 .routerlisten

// 只截取了片段
var app = exports = module.exports = {
    
    };

app.use = function use(fn) {
    
    ...}
app.route = function route(path) {
    
    ...}
app.listen = function listen() {
    
    ...}

mixin방법은 application.js반환된 개체의 구성원을 app현재 방법 createApplication으로 만든 개체에 혼합하는 것입니다 app.

기타 포함:

  • 노드의 http 모듈 위에 확장 request및 개체: responselib/request.jslib/response.js
  • 일부 유틸리티 기능이 내장되어 있습니다.lib/utils.js
  • 템플릿 엔진의 관련 설정 처리:lib/view.js
  • 라우팅 관련 처리:lib/router
  • 내부 미들웨어:lib/middleware

다음으로 Express의 디렉토리 구조를 모방하여 일부 콘텐츠를 구현합니다.

빠른 경험

프로젝트 아래에 디렉토리를 생성하고 express, 시뮬레이션 익스프레스를 구현하고, 디렉토리 아래에 파일을 추가합니다.

// express\index.js
module.exports = require('./lib/express')

// express\lib\express.js
const http = require('http')
const url = require('url')

// 路由表
const routes = [
  // { path: '', method: '', handler: () => { } }
]

function createApplication() {
    
    
  return {
    
    
    get(path, handler) {
    
    
      // 收集路由
      routes.push({
    
    
        path,
        method: 'get',
        handler,
      })
    },
    listen(...args) {
    
    
      const server = http.createServer((req, res) => {
    
    
        /* 接收到请求的时候处理路由 */
        const {
    
     pathname } = url.parse(req.url)
        const method = req.method.toLowerCase()
        const route = routes.find(route => route.path === pathname && route.method === method)
        if (route) {
    
    
          return route.handler(req, res)
        }

        res.end('404 Not Found.')
      })

      server.listen(...args)
    }
  }
}

module.exports = createApplication

앱을 생성하는 모듈 추출

앱을 편리하게 확장하기 위해 이제 내부 메서드를 생성자로 추출하고 를 통해 new인스턴스를 생성하고 이러한 메서드를 App의 인스턴스 메서드로 정의합니다.

// express\lib\application.js
const http = require('http')
const url = require('url')

function App() {
    
    
  // 路由表
  this.routes = []
}

App.prototype.get = function (path, handler) {
    
    
  // 收集路由
  this.routes.push({
    
    
    path,
    method: 'get',
    handler,
  })
}

App.prototype.listen = function (...args) {
    
    
  const server = http.createServer((req, res) => {
    
    
    /* 接收到请求的时候处理路由 */
    const {
    
     pathname } = url.parse(req.url)
    const method = req.method.toLowerCase()
    const route = this.routes.find(route => route.path === pathname && route.method === method)
    if (route) {
    
    
      return route.handler(req, res)
    }

    res.end('404 Not Found.')
  })

  server.listen(...args)
}

module.exports = App

// express\lib\express.js
const App = require('./application')

function createApplication() {
    
    
  return new App()
}

module.exports = createApplication

라우팅 모듈 추출

소스 코드:node_modules\express\lib\router\index.js

앱에서 라우팅을 보다 편리하게 개발하고 관리하기 위해서는 라우팅 관련 처리를 별도의 모듈로 캡슐화하는 것을 권장합니다.

마찬가지로 Router 생성자를 만들고 모든 라우팅 관련 작업은 Router의 라우팅 인스턴스를 통해 수행됩니다.

// router\index.js
const url = require('url')

function Router() {
    
    
  // 路由记录栈
  this.stack = []
}

Router.prototype.get = function (path, handler) {
    
    
  this.stack.push({
    
    
    path,
    method: 'get',
    handler,
  })
}

Router.prototype.handle = function (req, res) {
    
    
  const {
    
     pathname } = url.parse(req.url)
  const method = req.method.toLowerCase()
  const route = this.stack.find(route => route.path === pathname && route.method === method)
  if (route) {
    
    
    return route.handler(req, res)
  }

  res.end('404 Not Found.')
}

module.exports = Router

라우팅 테이블은 routesexpress 의 이름으로 변경됩니다 stack.

// express\lib\application.js
const http = require('http')
const Router = require('./router')

function App() {
    
    
  // 路由表
  this._router = new Router()
}

App.prototype.get = function (path, handler) {
    
    
  this._router.get(path, handler)
}

App.prototype.listen = function (...args) {
    
    
  const server = http.createServer((req, res) => {
    
    
    this._router.handle(req, res)
  })

  server.listen(...args)
}

module.exports = App

다양한 요청 방법 처리

라우팅 예시

// app.js
app.get('/', (req, res) => {
    
    
  res.end('get /')
})

app.get('/about', (req, res) => {
    
    
  res.end('get /about')
})

app.post('/about', (req, res) => {
    
    
  res.end('post /about')
})

app.patch('/about', (req, res) => {
    
    
  res.end('patch /about')
})

app.delete('/about', (req, res) => {
    
    
  res.end('delete /about')
})

익스프레스 소스 코드

각 메소드의 요청 처리 로직은 동일하지만 메소드 이름이 다르며 일반적인 요청 메소드를 순회하여 처리 로직을 배치로 추가할 수 있습니다.

Express는 내부적으로 일반적인 HTTP 요청 메서드를 내보내는 메서드 패키지를 사용합니다.

node_modules\methods\index.js하나의 js 파일( ) 만 있습니다 .

'use strict';
// 导入 nodejs 原生模块 http
var http = require('http');

// 优先 http 中支持的 methods,如果没有则导出手动整理的 methods
module.exports = getCurrentNodeMethods() || getBasicNodeMethods();

// 获取 nodejs 原生模块 http 的 METHODS 属性
// 它标识 node 环境中支持的 HTTP 请求方法
// 将它们遍历小写处理并返回
function getCurrentNodeMethods() {
    
    
  return http.METHODS && http.METHODS.map(function lowerCaseMethod(method) {
    
    
    return method.toLowerCase();
  });
}

// 手动整理 methods
function getBasicNodeMethods() {
    
    
  return [
    'get',
    'post',
    'put',
    'head',
    'delete',
    'options',
    'trace',
    'copy',
    'lock',
    'mkcol',
    'move',
    'purge',
    'propfind',
    'proppatch',
    'unlock',
    'report',
    'mkactivity',
    'checkout',
    'merge',
    'm-search',
    'notify',
    'subscribe',
    'unsubscribe',
    'patch',
    'search',
    'connect'
  ];
}

시뮬레이션 구현

methods 모듈을 직접 사용할 수 있습니다.

// express\lib\application.js
const http = require('http')
const Router = require('./router')
const methods = require('methods')

function App() {
    
    
  // 路由表
  this._router = new Router()
}

// 为每个请求方法添加处理函数
methods.forEach(method => {
    
    
  App.prototype[method] = function (path, handler) {
    
    
    this._router[method](path, handler)
  }
})

App.prototype.listen = function (...args) {
    
    
  const server = http.createServer((req, res) => {
    
    
    this._router.handle(req, res)
  })

  server.listen(...args)
}

module.exports = App

// router\index.js
const url = require('url')
const methods = require('methods')

function Router() {
    
    
  // 路由记录栈
  this.stack = []
}

// 为每个请求方法添加处理函数
methods.forEach(method => {
    
    
  Router.prototype[method] = function (path, handler) {
    
    
    this.stack.push({
    
    
      path,
      method,
      handler,
    })
  }
})

Router.prototype.handle = function (req, res) {
    
    
  const {
    
     pathname } = url.parse(req.url)
  const method = req.method.toLowerCase()
  const route = this.stack.find(route => route.path === pathname && route.method === method)
  if (route) {
    
    
    return route.handler(req, res)
  }

  res.end('404 Not Found.')
}

module.exports = Router

참고: 현재 이러한 타사 패키지는 이 프로젝트에 Express가 설치되어 종속 항목이 설치되어 있으므로 직접 사용할 수 있습니다. 고유한 express 구현을 별도로 릴리스하려면 이 패키지의 디렉토리에 종속 항목을 설치해야 합니다.

# 进入到模拟 express 的模块目录下
cd express
npm init -y
# 安装依赖
npm i methods
# 建议安装和 Express 一样的版本
# npm i [email protected]

/express이제 익스프레스(시뮬레이션)에서 메서드를 로드할 때 현재 디렉터리( ) 아래에 있는 메서드가 node_modules/methods먼저 로드됩니다.

보다 강력한 라우팅 경로 일치 모드 구현

소스 코드:node_modules\express\lib\router\layer.js

Express는 라우팅 경로에 대한 여러 일치 모드를 내부적으로 지원합니다. 경로 경로

라우팅 예시

// app.js
app.get('/', function (req, res) {
    
    
  res.end('root')
})

app.get('/about', function (req, res) {
    
    
  res.end('about')
})

app.get('/random.text', function (req, res) {
    
    
  res.end('random.text')
})

app.get('/ab?cd', function (req, res) {
    
    
  res.end('ab?cd')
})

app.get('/ab+cd', function (req, res) {
    
    
  res.end('ab+cd')
})

app.get('/ab*cd', function (req, res) {
    
    
  res.end('ab*cd')
})

app.get('/ab(cd)?e', function (req, res) {
    
    
  res.end('ab(cd)?e')
})

app.get(/a/, function (req, res) {
    
    
  res.end('/a/')
})

app.get(/.*fly$/, function (req, res) {
    
    
  res.end('/.*fly$/')
})

app.get('/users/:userId/books/:bookId', function (req, res) {
    
    
  res.end(req.params)
})

추신: 메소드 response.send()nodejs 객체를 기반으로 Express http.ServerResponse에서 확장한 메소드 입니다 .http.ServerResponse.send()res.end()

익스프레스 소스 코드

Express에서 내부적으로 사용하는 모듈은 path-to-regexp경로 일치를 구현하고 동적 매개변수를 확인할 수 있습니다.

// node_modules\express\lib\router\layer.js
var pathRegexp = require('path-to-regexp');

this.regexp = pathRegexp(path, this.keys = [], opts);

시뮬레이션 구현

종속성 설치

# 在 /express 目录下执行
# 注意:path-to-regexp 最新版已经不支持 Express 4 了,要安装老版本,版本号参考 Express 包下的 package.json
npm i [email protected]

처리 경로

// router\index.js
const url = require('url')
const methods = require('methods')
const pathRegexp = require('path-to-regexp')

function Router() {
    
    
  // 路由记录栈
  this.stack = []
}

// 为每个请求方法添加处理函数
methods.forEach(method => {
    
    
  Router.prototype[method] = function (path, handler) {
    
    
    this.stack.push({
    
    
      path,
      method,
      handler,
    })
  }
})

Router.prototype.handle = function (req, res) {
    
    
  const {
    
     pathname } = url.parse(req.url)
  const method = req.method.toLowerCase()
  const route = this.stack.find(route => {
    
    
    const keys = []
    // 基于配置的路由匹配规则,生成一个正则表达式
    const regexp = pathRegexp(route.path, keys, {
    
    })
    // 匹配实际请求路径的结果
    const match = regexp.exec(pathname)
    return match && route.method === method
  })

  if (route) {
    
    
    return route.handler(req, res)
  }

  res.end('404 Not Found.')
}

module.exports = Router

동적 경로 매개변수 처리

'/users/:userId/books/:bookId'라우팅 주소 에 대한 액세스 가정 : http://localhost:3000/users/tom/books/123.

path-to-regexp동적 매개변수가 구문 분석되고 구문 분석된 매개변수 이름이 들어오는 배열에 채워집니다 keys. 매개변수 값은 일반 일치 결과를 통해 볼 수 있습니다.

const keys = []
const regexp = pathRegexp(route.path, keys, {
    
    })
const match = regexp.exec(pathname)
console.log('keys =>',keys)
console.log('match =>',match)

인쇄 결과:

keys => [
  {
    
     name: 'userId', optional: false, offset: 8 },
  {
    
     name: 'bookId', optional: false, offset: 30 }
]
match => [
  '/users/tom/books/123',
  'tom',
  '123',
  index: 0,
  input: '/users/tom/books/123',
  groups: undefined
]

따라서 매개변수 이름과 매개변수 값을 함께 매핑하고 마운트할 수 있습니다 req.params.

// router\index.js
const route = this.stack.find(route => {
    
    
  const keys = []
  // 基于配置的路由匹配规则,生成一个正则表达式
  const regexp = pathRegexp(route.path, keys, {
    
    })
  // 匹配实际请求路径的结果
  const match = regexp.exec(pathname)

  // 解析动态参数
  if (match) {
    
    
    req.params = req.params || {
    
    }
    keys.forEach((key, index) => {
    
    
      req.params[key.name] = match[index + 1]
    })
  }

  return match && route.method === method
})

레이어 처리 모듈 추출

익스프레스 소스 코드

소스 코드:node_modules\express\lib\router\layer.js

Express에서 라우팅 경로 일치는 관리를 위해 별도로 모듈에 캡슐화됩니다.

// node_modules\express\lib\router\index.js
var layer = new Layer(path, {
    
    
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
}, route.dispatch.bind(route));

layer.route = route;

// 路由栈里存储的 Layer 实例
this.stack.push(layer);

// 调用的 layer 的 match 方法进行匹配
layer = stack[idx++];
match = matchLayer(layer, path);
function matchLayer(layer, path) {
    
    
  try {
    
    
    return layer.match(path);
  } catch (err) {
    
    
    return err;
  }
}
// node_modules\express\lib\router\layer.js
module.exports = Layer;

function Layer(path, options, fn) {
    
    
  if (!(this instanceof Layer)) {
    
    
    return new Layer(path, options, fn);
  }

  debug('new %o', path)
  var opts = options || {
    
    };

  this.handle = fn;
  this.name = fn.name || '<anonymous>';
  this.params = undefined;
  this.path = undefined;
  // 实例化时就生成了路由的正则表达式
  this.regexp = pathRegexp(path, this.keys = [], opts);

  // set fast path flags
  this.regexp.fast_star = path === '*'
  this.regexp.fast_slash = path === '/' && opts.end === false
}

Layer.prototype.match = function match(path) {
    
    ...}

시뮬레이션 구현

// express\lib\router\layer.js
const pathRegexp = require('path-to-regexp')

function Layer(path, handler) {
    
    
  this.path = path
  this.handler = handler
  this.keys = []
  this.regexp = pathRegexp(path, this.keys, {
    
    })
  this.params = {
    
    }
}

Layer.prototype.match = function (pathname) {
    
    
  const match = this.regexp.exec(pathname)

  if (match) {
    
    
    this.keys.forEach((key, index) => {
    
    
      this.params[key.name] = match[index + 1]
    })
    return true
  }

  return false
}

module.exports = Layer

// router\index.js
const url = require('url')
const methods = require('methods')
const Layer = require('./layer')

function Router() {
    
    
  // 路由记录栈
  this.stack = []
}

// 为每个请求方法添加处理函数
methods.forEach(method => {
    
    
  Router.prototype[method] = function (path, handler) {
    
    
    const layer = new Layer(path, handler)
    layer.method = method
    this.stack.push(layer)
  }
})

Router.prototype.handle = function (req, res) {
    
    
  const {
    
     pathname } = url.parse(req.url)
  const method = req.method.toLowerCase()
  
  const layer = this.stack.find(layer => {
    
    
    const match = layer.match(pathname)

    if (match) {
    
    
      req.params = req.params || {
    
    }
      Object.assign(req.params, layer.params)
    }

    return match && layer.method === method
  })

  if (layer) {
    
    
    return layer.handler(req, res)
  }

  res.end('404 Not Found.')
}

module.exports = Router

단일 핸들러 기능을 구현하는 미들웨어 기능

라우팅 예시

app.get('/', (req, res, next) => {
    
    
  console.log(1)
  next()
}, (req, res, next) => {
    
    
  console.log(2)
  next()
}, (req, res, next) => {
    
    
  console.log(3)
  next()
})

app.get('/', (req, res) => {
    
    
  // 响应
  res.end('get /')
})

app.get('/foo', (req, res, next) => {
    
    
  console.log('foo 1')
  next()
})

app.get('/foo', (req, res, next) => {
    
    
  console.log('foo 2')
  next()
})

app.get('/foo', (req, res) => {
    
    
  // 响应
  res.end('get /foo')
})

시뮬레이션 구현

일치하는 각 경로를 직접 순회하는 것은 불가능하며 handler처리 기능에 비동기 작업이 있을 수 있음을 고려하여 메서드를 사용자 지정 next(다음 경로의 내부 처리)하고 매개 변수로 전달하여 다음 경로를 처리하도록 결정할 수 있습니다. handler스스로 (전화 ) 타이밍.handlernext

// router\index.js
const url = require('url')
const methods = require('methods')
const Layer = require('./layer')

function Router() {
    
    
  // 路由记录栈
  this.stack = []
}

// 为每个请求方法添加处理函数
methods.forEach(method => {
    
    
  Router.prototype[method] = function (path, handler) {
    
    
    const layer = new Layer(path, handler)
    layer.method = method
    this.stack.push(layer)
  }
})

Router.prototype.handle = function (req, res) {
    
    
  const {
    
     pathname } = url.parse(req.url)
  const method = req.method.toLowerCase()

  let index = 0
  const next = () => {
    
    
    if (index >= this.stack.length) {
    
    
      return res.end(`Cannot GET ${
      
      pathname}`)
    }

    const layer = this.stack[index++]
    const match = layer.match(pathname)

    if (match) {
    
    
      req.params = req.params || {
    
    }
      Object.assign(req.params, layer.params)
    }

    if (match && layer.method === method) {
    
    
      return layer.handler(req, res, next)
    }

    // 如果当前请求没有匹配到路由,继续遍历下一个路由
    next()
  }

  // 调用执行
  next()
}

module.exports = Router

// 访问 `/foo` 打印结果:
foo 1
foo 2

// 访问 `/ 打印结果(只执行了每个路由的第一个处理函数):
1

여러 핸들러 기능을 구현하는 미들웨어

아이디어 분석

현재 라우팅 테이블 스택에 저장된 각 항목은 각각 처리 기능 핸들러를 포함하는 별도의 계층입니다.

실제로 Express는 라우팅 테이블(Router)에 라우팅 테이블(Route)도 저장하는데, 각 처리 기능에 대해 생성된 레이어를 저장하므로 각 경로의 여러 처리 기능을 통과할 수 있습니다.

데이터 구조는 다음과 유사합니다.

Router
  stack [
    Layer 1 {
    
    
      path: 'xx', // 请求路径
      dispatch, // 处理方法,内部调用 Route 下的 dispatch
      Route {
    
    
        stack [
          Layer {
    
     path: 'xx', method: 'xxx', handler: 'xxx' },
          Layer {
    
     path: 'xx', method: 'xxx', handler: 'xxx' },
          Layer {
    
     path: 'xx', method: 'xxx', handler: 'xxx' }
        ],
        dispatch // 遍历调用 stack 中的 handler
      }
    },
    Layer 2 {
    
    
      path: 'xx', // 请求路径
      dispatch, // 处理方法
      Route {
    
    
        stack [
          Layer {
    
     path: 'xx', method: 'xxx', handler: 'xxx' },
          Layer {
    
     path: 'xx', method: 'xxx', handler: 'xxx' },
          Layer {
    
     path: 'xx', method: 'xxx', handler: 'xxx' }
        ],
        dispatch // 遍历调用 stack 中的 handler
      }
    },
  ]

조직 데이터 구조

// express\lib\router\route.js
const methods = require('methods')
const Layer = require('./layer')

function Route() {
    
    
  this.stack = [
    // { path, method, handler }
  ]
}

// 遍历执行当前路由对象中所有的处理函数
Route.prototype.dispatch = function () {
    
     }

methods.forEach(method => {
    
    
  Route.prototype[method] = function (path, handlers) {
    
    
    handlers.forEach(handler => {
    
    
      const layer = new Layer(path, handler)
      layer.method = method
      this.stack.push(layer)
    })
  }
})

module.exports = Route

// router\index.js
const url = require('url')
const methods = require('methods')
const Layer = require('./layer')
const Route = require('./route')

function Router() {
    
    
  // 路由记录栈
  this.stack = []
}

// 为每个请求方法添加处理函数
methods.forEach(method => {
    
    
  Router.prototype[method] = function (path, handlers) {
    
    
    const route = new Route()
    const layer = new Layer(path, route.dispatch.bind(route))
    layer.route = route
    this.stack.push(layer)
    route[method](path, handlers)
  }
})

Router.prototype.handle = function (req, res) {
    
    ...}

module.exports = Router

// express\lib\application.js
...

methods.forEach(method => {
    
    
  // 接受全部处理函数
  App.prototype[method] = function (path, ...handlers) {
    
    
    this._router[method](path, handlers)
  }
})

...

경로를 인쇄하여 결과를 볼 수 있습니다.

// app.js
app.get('/foo', (req, res) => {
    
    
  // 响应
  res.end('get /foo')
})

console.log(app._router)

처리 기능 실행

Express는 다음 API를 통해 경로를 구성할 수 있습니다.

app.route('/foo')
  .get((req, res) => {
    
    })
  .post((req, res) => {
    
    })

처리 기능을 실행할 때 외부 경로는 경로와 일치하고 내부 경로는 요청 메서드와 일치하므로 여기에서 일치하는 메서드가 제거됩니다.

// router\index.js
Router.prototype.handle = function (req, res) {
    
    
  ...
  const next = () => {
    
    
	...
    // 顶层只匹配请求路径,内层匹配请求方法
    // if (match && layer.method === method) {
    
    
    if (match) {
    
    
      // 顶层调用的 handler 就是 route.dispatch 函数
      return layer.handler(req, res, next)
    }
    next()
  }
  next()
}
// express\lib\router\route.js
const methods = require('methods')
const Layer = require('./layer')

...

// 遍历执行当前路由对象中所有的处理函数
// 遍历内层的 stack,内层遍历结束就要返回到外层,所以第三个参数名取名 out 而不是 next
Route.prototype.dispatch = function (req, res, out) {
    
    
  const method = req.method.toLowerCase()

  let index = 0
  const next = () => {
    
    
    if (index >= this.stack.length) {
    
    
      return out()
    }

    const layer = this.stack[index++]

    if (layer.method === method) {
    
    
      return layer.handler(req, res, next)
    }

    next()
  }

  next()
}

...

module.exports = Route

/결과를 인쇄하려면 다시 방문하십시오 .

1
2
3

사용 방법 구현

사용 규칙 검토

// 不验证请求方法和请求路径,任何请求都会处理
// app.use((req, res, next) => {
    
    
//   res.end('hello')
// })

// 匹配的不是 `/foo`,而是以 `/foo` 作为前置路径的路由
// app.use('/foo', (req, res, next) => {
    
    
//   res.end('foo')
// })

// 与不传第一个路径参数等效
// app.use('/', (req, res, next) => {
    
    
//   res.end('hello')
// })

// 可以挂载多个请求处理函数
app.use('/', (req, res, next) => {
    
    
  console.log(1)
  next()
}, (req, res, next) => {
    
    
  console.log(2)
  next()
}, (req, res, next) => {
    
    
  res.end('hello')
})

시뮬레이션 구현

// express\lib\application.js
App.prototype.use = function (path, ...handlers) {
    
    
  this._router.use(path, handlers)
}

// router\index.js
Router.prototype.use = function (path, handlers) {
    
    
  // 没有传路径的时候
  if (typeof path === 'function') {
    
    
    handlers.unshift(path)
    path = '/' // 任何路径都以它开头
  }

  handlers.forEach(handler => {
    
    
    const layer = new Layer(path, handler)
    // 标记是否是 use 类型的中间件
    layer.isUseMiddleware = true
    this.stack.push(layer)
  })
}
// express\lib\router\layer.js
const pathRegexp = require('path-to-regexp')

function Layer(path, handler) {
    
    
  this.path = path
  this.handler = handler
  this.keys = []
  this.regexp = pathRegexp(path, this.keys, {
    
    })
  this.params = {
    
    }
}

Layer.prototype.match = function (pathname) {
    
    
  const match = this.regexp.exec(pathname)

  if (match) {
    
    
    this.keys.forEach((key, index) => {
    
    
      this.params[key.name] = match[index + 1]
    })
    return true
  }

  // 匹配 use 中间件的路径
  if (this.isUseMiddleware) {
    
    
    if (this.path === '/') {
    
    
      return true
    }

    if (pathname.startsWith(`${
      
      this.path}/`)) {
    
    
      return true
    }
  }

  return false
}

module.exports = Layer

추천

출처blog.csdn.net/u012961419/article/details/123889206