瞭解 express 原理以前,你須要先掌握 express 的基本用法。前端
關於 express 的介紹請看 express 官網。git
先回顧一下 express 使用的的過程,首先是把模塊倒入,而後當作方法執行,在返回值中調用 use
處理路由,調用 listen
監聽端口。github
const express = require('express') const app = express() app.use('/home', (req, res) => { res.end('home') }) app.listen(8080, () => { console.log('port created successfully') })
根據上面的使用,咱們開始構建代碼。咱們須要寫一個 express
方法,返回一個 app
對象,有 use
和 listen
方法。express
const http = require('http') const url = require('url') function express() { const app = {} const routes = []; app.use = function (path, action) { routes.push([path, action]) } function handle(req, res) { let pathname = url.parse(req.url).pathname; for (let i = 0; i < routes.length; i++) { var route = routes[i]; if (pathname === route[0]) { let action = route[1]; action(req, res); return; } } handle404(req, res); } function handle404(req, res) { res.end('404') } app.listen = function (...args) { const server = http.createServer((req, res) => { handle(req, res) }) server.listen(...args) } return app } module.exports = express
上面代碼中的 use
方法的做用是把請求路徑跟對應的處理函數存放在一個數組中,當請求到來的時候遍歷數組,根據路徑找到對應的方法執行。數組
動態路由是根據參數能夠動態匹配路徑。微信
const express = require('./express') const app = express() // /home/1 // /home/2 app.use('/home/:id', (req, res) => { res.end('home') }) app.listen(8080, () => { console.log('port created successfully') })
根據路由裏面的參數要匹配符合規則的路由咱們須要使用正則來處理,下面代碼是根據路徑來生成正則的一個方法。app
const pathRegexp = (path, paramNames=[], {end=false} ={}) => { path = path .concat(end ? '' : '/?') .replace(/\/\(/g, '(?:/') .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function (_, slash, format, key, capture, optional, star) { slash = slash || ''; paramNames.push(key); return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' + (optional || '') + (star ? '(/*)?' : ''); }) .replace(/([\/.])/g, '\\$1') .replace(/\*/g, '(.*)'); return new RegExp('^' + path + '$') } module.exports = pathRegexp
根據路徑生成正則也是有第三方模塊 path-to-regexp 模塊,核心原理你們值得參考。包括 Vue 和 React 的路由都使用到了這個模塊。函數
下面咱們須要開始動態映射路由。post
const http = require('http') const url = require('url') const pathRegexp = require('./pathRegexp') function express() { const app = {} const routes = { 'all': [] }; app.use = function (path, action) { const keys = [] const regexp = pathRegexp(path, keys,{end:true}) routes.all.push([ { regexp, keys }, action ]); }; ['get', 'put', 'delete', 'post'].forEach(function (method) { routes[method] = []; app[method] = function (path, action) { const keys = [] const regexp = pathRegexp(path, keys, {end:true}) routes[method].push([ { regexp, keys }, action ]); }; }); const match = function (pathname, routes, req, res) { for (var i = 0; i < routes.length; i++) { let route = routes[i]; let reg = route[0].regexp; let keys = route[0].keys; let matched = reg.exec(pathname); if (matched) { let params = {}; for (let i = 0, l = keys.length; i < l; i++) { let value = matched[i + 1]; if (value) { params[keys[i]] = value; } } req.params = params; let action = route[1]; action(req, res); return true; } } return false; }; function handle(req, res) { let {pathname, query} = url.parse(req.url, true); req.query = query let method = req.method.toLowerCase(); if (routes.hasOwnProperty(method)) { if (match(pathname, routes[method], req, res)) { return; } else { if (match(pathname, routes.all, req, res)) { return; } } } else { if (match(pathname, routes.all, req, res)) { return; } } handle404(req, res); } function handle404 (req, res) { res.end('404') } app.listen = function (...args) { const server = http.createServer((req, res) => { handle(req, res) }) server.listen(...args) } return app } module.exports = express
其中 express
會把請求的方法都代理到 app
中做爲屬性的方式來方便用戶使用。ui
const express = require('./express') const app = express() app.use('/home/:id', (req, res) => { console.log(req.params) res.end('home') }) // app.get // app.post app.get('/user', (req, res) => { console.log(req.query) res.end('user') }) app.listen(8080, () => { console.log('port created successfully') })
express 會在請求對象中加一些屬性,會把路徑參數做爲請求時的 params
屬性,會把查詢字符串做爲請求時的 query
屬性。大多數中間件也是這個原理,如 body-parser 模塊,給它加個 body
屬性便可。
經過GitHub查看代碼請點擊:傳送門