带你深入解析express源码(一)

带你深入解析express源码(一)

目录结构

在这里插入图片描述
本人将express源码简化,如下所示

const mixin = require('merge-descriptors')
let proto = require('./application')
const http = require('http')
const EventEmitter = require('events').EventEmitter
const Router = require('./router')

exports = module.exports = createApplication
/**
 * 创建app
 */
function createApplication() {
  
  let app = function (req, res, next) { // createServer的回调函数
    app.handle(req, res, next)
  }
  mixin(app, proto, false)

  mixin(app, EventEmitter.prototype, false)
  let req = Object.create(http.IncomingMessage.prototype)
  let res = Object.create(http.ServerResponse.prototype)
  // 4:新增 讲app和新创建的req相互关联
  //给request绑定req和app
  app.request = Object.create(req, {
    app: {
      configurable: true,
      enumerable: true,
      writable: true,
      value: app
    }
  })
  // 4:新增 讲app和新创建的res相互关联
  app.response = Object.create(res, {
    app: {
      configurable: true,
      enumerable: true,
      writable: true,
      value: app
    }
  })
  app.init()
  
  return app
}

// 4:新增 将router暴露出来
exports.Router = Router

  1. 首先可以看到new express()时执行了createApplication,定义了 app函数,并调用了mixin,mixin方法主要是合并对象,首先合并了application,随后混合了app和事件处理函数,并将node的res和req绑定到了app对象,最终执行了application的init方法。
  2. 我们来看看init方法,可以看出该方法主要调用了defaultConfiguration方法,该方法设置了环境变量,并设置了一些对象。
  3. 在application.js中我们可以看到有个遍历methods的方法,该方法在被引入时就会执行,这个是application的核心方法,我们来看看干了些什么。
/**
 * 采用的是设计模式中的模块模式,定义app对象,为其挂载方法
 */
const http = require('http')
let app = exports = module.exports = {}
let methods = http.METHODS // 获取http的所有请求方式
const slice = Array.prototype.slice;
const setPrototypeOf = require('setprototypeof')
const Router = require('./router')
const finalhandler = require('finalhandler') // http request 最后的函数处理,主要包括错误处理 https://www.npmjs.com/package/finalhandler
const flatten = require('array-flatten').flatten
const trustProxyDefaultSymbol = '@@symbol:trust_proxy_default'

const compileQueryParser = require('./utils').compileQueryParser
// 3:新增 req.query 中间件的处理
const query = require('./middleware/query')
// 4:新增 初始中间件,对req,res,app等进行相互关联
const middleware = require('./middleware/init')
// console.dir(new Router().toString())
/**
 * 初始化app对象需要的一些基础设置
 * paths: 存放所有使用get方法注册的请求,单体对象的格式为:
 * {
*     pathURL  请求的地址
      cb  请求对应的回调函数
 * }
 */
app.init = function init() {
  this.setting = {}
  this.paths = []
  this.defaultConfiguration()
}
/**
 * 设置环境变量env,后期迭代预留
 */
app.defaultConfiguration = function defaultConfiguration() {
  let env = process.env.NODE_ENV || 'development'
  this.set('env', env)
  this.set('jsonp callback name', 'callback')
  console.log(this)
}
/**
 * 对app中setting对象的操作,为后期迭代预留
 */
app.set = function set(key, val) {
  if (arguments.length === 1) {
    this.setting[key]
  }
  this.setting[key] = val
}


//用户的请求
app.handle = function handle(req, res) {
  let router = this._router
  let done = finalhandler(req, res, {
    env: this.get('env'),
    onerror: logerror.bind(this)
  })
  //厂家有没有,没有则报错
  if (!router) {
    done()
  }
  //执行handle函数
  router.handle(req, res, done)
}

function logerror(err) {
  if (this.get('env') !== 'test') 
    console.error(err.stack || err.toString());
  }
/**
 * 启动http服务
 */
app.listen = function listen() {
  
  let server = http.createServer(this)
  //自执行解构伪数组,还是执行linsten方法,并传入arguments
  return server
    .listen
    .apply(server, arguments)
}
// /**
//  * 实现app的get接口,主要是对所有的get请求进行注册,方便handle中实现精准回调
//  */
app.get = function get(path, cb) {
  let pathObj = {
    pathURL: path,
    cb: cb
  }
  this.paths.push(pathObj)
}

/**
 * 对路由实现装载,实例化
 */
app.lazyrouter = function () {
  if (!this._router) {
    this._router = new Router()
    // 3:新增 注册处理query的中间件
    this
      ._router
      .use(query(this.get('query parser fn')))
    // 4:新增 初始中间件,对req,res,app等进行相互关联
    this
      ._router
      .use(middleware.init(this))
  }
}

/**
 * 实现post,get等http.METHODS 对应的方法
 */

methods.forEach((method) => {
  method = method.toLowerCase()
  app[method] = function (path) {
    if (method === 'get' && arguments.length === 1) { // 当为一个参数时app的get方法,返回settings中的属性值
      
      return this.set(path)
    }
    this.lazyrouter()
  
    let route = this._router.route(path) // 调用_router的route方法,对path和route注册 slice.call(arguments, 1)是取出url后面的参数
    console.log(method)
    // this._router.route(path).get(()=>{})
    //slice.call(arguments, 1) 就是将伪数组切割,得到的是回调函数
    //apply相当于执行该函数,第二个参数为该函数执行所需要的参数为[]
    route[method].apply(route, slice.call(arguments, 1)) // 调用route的method方法,对method和callbacks注册
  }
})


/**
 * 3:新增 实现app的param接口
 * @param {*} name 参数名称 可以是数组 或者 字符串
 * @param {*} fn 需要处理的中间件
 */
app.param = function param(name, fn) {
  this.lazyrouter()
  // 如果name是数组时,分割调用自身
  if (Array.isArray(name)) {
    for (let i = 0; i < name.length; i++) {
      this.param(name[i], fn)
    }
    return this
  }
  this
    ._router
    .param(name, fn)
  return this
}
/**
 * 3:新增 暴露给用户注册中间件的结构,主要调用router的use方法
 * @param {*} fn
 */
app.use = function use(fn) {
  let offset = 0
  let path = '/'

  if (typeof fn !== 'function') {
    let arg = fn
    while (Array.isArray(arg) && arg.length !== 0) {
      arg = arg[0]
    }

    if (typeof arg !== 'function') {
      offset = 1
      path = fn
    }
  }
    // 4:新增 对传入的参数进行处理,是参数可以传入数组
    let fns = flatten(slice.call(arguments, offset))
    if (fns.length === 0) {
      throw new TypeError('app.use() require a middlewaare function')
    }
  
    //给res,req,request挂载一些参数
  this.lazyrouter()
  let router = this._router
  // fns.forEach(function (fn) {
  //   router.use(path, fn)
  // })

  fns.forEach(function (fn) {
    // 4:修改 通常use里面是一个express对象,或者router对象时会包含handle和set,不包含为普通中间件
    if (!fn || !fn.handle || !fn.set) {
      return router.use(path, fn)
    }
    // 4:新增 此时的fn为express或router对象,将当前express对象关联到fn
    fn.parent = this

    router.use(path, function mounted_app(req, res, next) {
      let orig = req.app
      // 4:新增 在中间件的回调函数中调用fn的handle
      fn.handle(req, res, function (err) {
        setPrototypeOf(req, orig.request)
        setPrototypeOf(res, orig.response)
        next(err)
      })
    })
    // 4:新增 触发fn挂载完成事件
    fn.emit('mount', this)
  }, this)
}

3.1 首先将获取到的methods转化小写,并给每个method设置为函数,该函数仅仅当该method获取一个参数时的方法,返回settings中的属性值。
随后调用了lazyrouter方法,该方法实例化了router文件夹下的index.js方法,我们来看看实例化该方法主要获得什么。
实例化对象如下所示,该对象最重要的是stack数组,该数组放置了Layer,这个Layer是什么呢?我贴出代码来分析下。

this._router=[Function] {
        route: [Function: route],
        handle: [Function: handle],
        params: {},
        stack: [
          Layer {
            handle: [Function: bound dispatch],
            params: undefined,
            path: undefined,
            keys: [],
            regexp: /^\/index[\/#\?]?$/i,
            route: [Route]
          },
          Layer {
            handle: [Function: bound dispatch],
            params: undefined,
            path: undefined,
            keys: [],
            regexp: /^\/index[\/#\?]?$/i,
            route: [Route]
          },
          Layer {
            handle: [Function: bound dispatch],
            params: undefined,
            path: undefined,
            keys: [],
            regexp: /^\/index[\/#\?]?$/i,
            route: [Route]
          }
        ]
  }
index.js代码如下所示,我们可以看出,主要实例化了Route和layer,实例化又干了些什么呢?我们先留着疑问下篇文章分析,下章主要分析new Route和new Layer时发生了什么。
'use strict'

const debug = require('debug')('router')
const Route = require('./route')
const Layer = require('./layer')
let methods = require('http').METHODS
const parseUrl = require('parseurl')
const mixin = require('merge-descriptors')
const flatten = require('array-flatten').flatten
// 3:新增,用于获取对象类型
const objectRegExp = /^\[object (\S+)\]$/
const slice = Array.prototype.slice
const toString = Object.prototype.toString


/**
 * @description: 
 * @param {type} 
 * @return {type} 
 */
let proto = module.exports = function (options) {
  let ops = options || {}
  function router(req, res, next) {
    router.handle(req, res, next)
  }

  mixin(router, proto)
  router.params = {}
  router._params = []
  router.stack = []

  return router
}


/**
 * 将path和route对应起来,并放进stack中,对象实例为layer
 */
proto.route = function route(path) {
  //拿着这个path去route.js中生成route对象,Route { path: '/', stack: [], methods: {} }+dispatch
  let route = new Route(path)
  
  //route.dispatch.bind(route) 得到dispatch函数,不会执行
  let layer = new Layer(path, {
    end: true
  }, route.dispatch.bind(route))//从Route { path: '/', stack: [], methods: {} }里面去取值 [route.dispatch.bind(route): bound dispatch]
  /**console.log(layer)
  
              {
                handle: [Function: bound dispatch],
                params: undefined,
                path: undefined,
                keys: [],
                regexp: /^\/index[\/#\?]?$/i
              }  
  
  **/

  layer.route = route
  /**
   * 
   * layer=
   *  {
        handle: [Function: bound dispatch],
        params: undefined,
        path: undefined,
        keys: [],
        regexp: /^\/index[\/#\?]?$/i,
        route:  { path: '/index', stack: [], methods: {} }
      }
   * 
   * **/
  
  this
    .stack
    .push(layer)
     
  return route
}

/**
 * 遍历stack数组,并处理函数, 将res req 传给route
 */

proto.handle = function handle(req, res, out) {
  let self = this
  debug('dispatching %s %s', req.method, req.url)
  let idx = 0
  //商家首先拿到stack和用户请求的url
  let stack = self.stack
  let url = req.url
  let done = restore(out, req, 'baseUrl', 'next', 'params')
  let paramcalled = {}
  next() //第一次调用next
  
  function next(err) {
    let layerError = err === 'route'? null: err
    if (layerError === 'router') { //如果错误存在,再当前任务结束前调用最终处理函数
      setImmediate(done, null)
      return
    }

    if (idx >= stack.length) { // 遍历完成之后调用最终处理函数
      setImmediate(done, layerError)
      return
    }
    //
    let layer
    let match
    let route
    while (match !== true && idx < stack.length) { //从数组中找到匹配的路由
      //拿到第一个layer
      layer = stack[idx++]
      //正则匹配,匹配到了就是true
      match = matchLayer(layer, url)
      //拿到route类
      route = layer.route
      if (typeof match !== 'boolean') {
        layerError = layerError || match
      }

      //没有匹配则跳过
      if (match !== true) {
        continue
      }
      if (layerError) {
        match = false
        continue
      }
      //拿到用户请求方式
      let method = req.method
      let has_method = route._handles_method(method)
      if (!has_method) {
        match = false
        continue
      }
    }
    //找了一圈都没找到
    if (match !== true) { // 循环完成没有匹配的路由,调用最终处理函数
      return done(layerError)
    }
    res.params = Object.assign({}, layer.params) // 将解析的‘/get/:id’ 中的id剥离出来
    // 3:新增,主要是处理app.param
    self.process_params(layer, paramcalled, req, res, function (err) {
      if (err) {
        return next(layerError || err)
      }
      if (route) {
        //调用route的dispatch方法,dispatch完成之后在此调用next,进行下一次循环
        return layer.handle_request(req, res, next)
      }
      // 3:新增,加入handle_error处理
      trim_prefix(layer, layerError, '', path)
    })

    function trim_prefix(layer, layerError, layerPath, path) {
      if (layerPath.length !== 0) {
        let c = path[layerPath.length]
        if (c && c !== '/' && c !== '.') 
          return next(layerError)
      }
      if (layerError) {
        layer.handle_error(layerError, req, res, next)
      } else {
        layer.handle_request(req, res, next)
      }
    }

  }
}


/**
 * 3:新增 获取除query部分的路径 例如: /get?id=12 --> /get
 */

function getPathname(req) {
  try {
    return parseUrl(req).pathname
  } catch (e) {
    return undefined
  }
}

/**
 * 判断url是否符合layer.path的规则
 */
function matchLayer(layer, path) {
  try {
    return layer.match(path);
  } catch (err) {
    return err;
  }
}
/**
 * 3:新增 对传过来的参数进行拦截,将参数拦截相关存入到params中,在handle中进行分解执行
 */
proto.param = function param(name, fn) {
  if (typeof name === 'function') {
    this
      ._params
      .push(name)
    return
  }
  if (name[0] === ':') {
    name = name.substr(1)
  }
  let params = this._params
  let len = this._params.length
  let ret
  for (let i = 0; i < len; i++) {
    if (ret = params[i](name, fn)) {
      fn = ret
    }
  }
  (this.params[name] = this.params[name] || []).push(fn)
}
/**
 * 3:新增 遍历params,匹配到合适的param,执行拦截
 * @param {*} layer 当前layer
 * @param {*} called 已经执行过的param
 * @param {*} req
 * @param {*} res
 * @param {*} done
 */
proto.process_params = function process_params(layer, called, req, res, done) {
  //包含参数和回调函数
  let params = this.params
  let keys = layer.keys
  let keysIndex = 0
  let key
  let name
  let paramcbIndex = 0
  let paramcallbacks
  let paramVal
  let paramCalled
  if (keys.length === 0) {
    return done()
  }
  /**
   * 3:新增 遍历当前url所对应的param,存于layer.keys中
   * @param {} err
   */
  function param(err) {
    if (err) {
      return done(err)
    }
    // 临界值判断,跳出循环
    if (keysIndex >= keys.length) {
      return done()
    }
    key = keys[keysIndex++]
    name = key.name
    paramcbIndex = 0
    paramVal = req.params[name]
    //拿到回调函数
    paramcallbacks = params[name]
    paramCalled = called[name]
    if (paramVal === undefined || !paramcallbacks) {
      return param()
    }

    if (paramCalled && (paramCalled.match === paramVal || (paramCalled.error && paramCalled.error !== 'route'))) {
      req.params[name] = paramCalled.value
      return param()
    }

    called[name] = paramCalled = {
      error: null,
      match: paramVal,
      value: paramVal
    }
    paramCallback()
  }

  /**
   * 3:新增 遍历当前param-->key 对应的functions --> callbacks
   * @param {} err
   */
  function paramCallback(err) {
    //中间件回调函数为[fn]
    let fn = paramcallbacks[paramcbIndex++]
    paramCalled.value = req.params[key.name]
    if (err) {
      paramCalled.error = err
      return param(err)
    }
    // 临界值,跳出循环
    if (!fn) {
      return param(err)
    }
    try {
      // 执行中间件
      fn(req, res, paramCallback, paramVal, name)
    } catch (e) {
      paramCallback(e)
    }
  }
  param()
}

/**
 * 3:新增 主要用于注册路由相关的中间件,此迭代中,在注册query中间件中使用到
 * @param {*} fn
 */
proto.use = function use(fn) {
  let path = '/'
  let offset = 0
  // 为app.use 接口准备,第一个参数可能时路径的正则表达式
  if (typeof fn !== 'function') {
    let arg = fn
    while (Array.isArray(arg) && arg.length != 0) {
      arg = arg[0]
    }
    if (typeof arg !== 'function') {
      offset = 1
      path = arg
    }
  }

  let callbacks = slice.call(arguments, offset)
  if (callbacks.length === 0) {
    throw new TypeError('Router.use() requires a middleware function')
  }

  // 将中间件加入到stack栈中,方便handle函数遍历中执行
  for (let i = 0; i < callbacks.length; i++) {
    let fn = callbacks[i]
    if (typeof fn !== 'function') {
      throw new TypeError('Router.use() requires a middleware function but not a ' + gettype(fn))
    }
    let layer = new Layer(path, {
      strict: false,
      end: false
    }, fn)
    layer.route = undefined
    this
      .stack
      .push(layer)
  }
}

/**
 * 3: 新增 获取对象的类型
 * @param {} obj
 */
function gettype(obj) {
  let type = typeof obj

  if (type !== 'object') {
    return type
  }

  return toString
    .call(obj)
    .replace(objectRegExp, '$1')
}

/**
 * 3:新增 对obj对象的一些属性进行恢复出厂设置
 * @param {*} fn 恢复值之后需要调用的函数
 * @param {*} obj 需要恢复值的对象
 * @param {*}  augments[i+2] obj需要恢复的属性
 */
function restore(fn, obj) {
  let props = new Array(arguments.length - 2)
  let vals = new Array(arguments.length - 2)

  // 保存函数调用时,obj对应属性的值
  for (let i = 0; i < props.length; i++) {
    props[i] = arguments[i + 2]
    vals[i] = obj[props[i]]
  }

  return function () {
    // 调用函数时,对obj属性值进行恢复
    for (let i = 0; i < props.length; i++) {
      obj[props[i]] = vals[i]
    }
    fn.apply(this, arguments);
  }

}

// 4:新增 增加router.methods对外接口
//本质是调用route的get方法,并放到route的stack中,相当于app.get方法
methods
  .forEach(function (method) {
    method = method.toLowerCase()
    proto[method] = function (path) {
      let route = this.route(path)
      route[method].apply(route, slice.call(arguments, 1))
      return this
    }
  })
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Vue Express MySQL项目源码是一个使用Vue.js、Express和MySQL构建的全栈应用程序的代码库。该应用程序提供了一个用户管理系统,允许用户注册、登录和管理他们的个人资料。 项目的文件结构通常分为前端和后端两部分。前端使用Vue.js来构建用户界面,包括注册和登录表单、个人资料页面以及其他相关页面。前端文件夹中包含各种Vue组件,用于显示数据、处理用户输入以及与后端API进行通信。 后端使用Express框架来处理路由和请求,与MySQL数据库进行交互。后端文件夹中包含各种路由文件,用于处理用户注册、登录和个人资料的请求。此外,还有一个名为"db.js"的文件,用于建立与MySQL数据库的连接,并执行相关的数据库操作。 在前端和后端之间进行通信时,通常使用axios库来发送异步请求。前端组件会将用户提交的注册和登录数据发送到后端,后端将验证这些数据,并根据情况返回成功或失败的响应。用户登录成功后,后端将生成一个JSON Web Token(JWT),以便在后续的请求中进行身份验证。 此外,该项目还可以包括密码加密、表单验证、拦截器、错误处理等功能,以提高安全性和用户体验。 总之,Vue Express MySQL项目源码提供了一个完整的全栈应用程序的实现,具有用户注册、登录和管理功能,通过使用Vue.js、Express和MySQL等技术,能够实现前后端的数据交互和业务逻辑处理。 ### 回答2: Vue+Express+MySQL是一种常见的全栈开发技术栈,用于构建基于Web的应用程序。该技术栈的项目源码包括了前端vue框架、后端express框架以及数据库MySQL的代码。 Vue是一种流行的JavaScript框架,用于构建交互式的前端界面。Vue具有简单易学的API和灵活的组件化架构,使得开发者可以更快速地构建响应式的用户界面。 Express是一个基于Node.js的后端框架,用于构建和管理服务器端应用。Express具有轻量级、灵活和可扩展的特点,支持开发REST API和处理HTTP请求。 MySQL是一种关系型数据库管理系统,用于存储和管理数据。它具有开源、高性能和可靠的特点,广泛应用于各种规模的应用程序。 在Vue+Express+MySQL项目源码中,前端代码主要是使用Vue框架编写的组件和页面,用于实现用户界面和与后端交互。例如,可以编写Vue组件来呈现数据、处理用户输入和发起HTTP请求。 后端代码主要是使用Express框架编写的路由和逻辑处理部分,用于处理前端发起的HTTP请求并返回数据。例如,可以编写Express路由来处理用户登录请求、查询数据库并返回结果。 数据库部分主要是使用MySQL数据库来存储和管理数据。例如,可以创建表格来存储用户信息、商品信息等,并通过使用MySQL查询语言来实现数据的增删改查等操作。 综上所述,Vue+Express+MySQL项目源码包括了前端Vue代码、后端Express代码和数据库MySQL的代码,通过这些代码可以实现一个完整的全栈应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Young soul2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值