了解一些node的知识点

254 阅读8分钟

node

node是运行环境,采取commonJS规范,输出模块:moudle.export,引入模块:require()

  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用

  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口

优点:轻量可扩展、非阻塞IO、高并发访问、单线程多进程

node的运行机制

  1. V8引擎解析JavaScript脚本。
  2. 解析后的代码,调用Node API。
  3. libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
  4. V8引擎再将结果返回给用户。

node模块类型

  1. 内置模块:node原生提供的功能,比如fs、http等,这些模块在node进程起来时就加载了
  2. 文件模块:第三方模块,node_modules下面的文件都是文件模块

node模块加载顺序require(X)

  1. 优先加载内置模块,即使有同名文件,也会优先使用内置模块。
  2.  不是内置模块,先去缓存找。
  3.  缓存没有就去找对应路径的文件。
  4.  不存在对应的文件,就将这个路径作为文件夹加载。
  5.  对应的文件和文件夹都找不到就去node_modules下面找。
  6.  还找不到就报错了。

加载文件夹顺序

前面提到找不到文件就找文件夹,但是不可能将整个文件夹都加载进来,加载文件夹的时候也是有一个加载顺序的:

  1. 先看看这个文件夹下面有没有package.json,如果有就找里面的main字段,main字段有值就加载对应的文件。所以如果大家在看一些第三方库源码时找不到入口就看看他package.json里面的main字段吧,比如jquerymain字段就是这样:"main": "dist/jquery.js"
  2. 如果没有package.json或者package.json里面没有main就找index文件。
  3. 如果这两步都找不到就报错了。

require支持文件类型

  1. .js:.js文件是我们最常用的文件类型,加载的时候会先运行整个JS文件,然后将前面说的module.exports作为require的返回值。
  2.  .json:.json文件是一个普通的文本文件,直接用JSON.parse将其转化为对象返回就行。
  3.  .node:.node文件是C++编译后的二进制文件,纯前端一般很少接触这个类型。 

node事件循环

Node端事件循环中的异步队列也是这两种:macro(宏任务)队列和 micro(微任务)队列。

  1. 常见的 macro-task setTimeoutsetIntervalsetImmediatescript(整体代码)、 I/O 操作
  2. 常见的 micro-task  process.nextTicknew Promise().then(回调)

微任务和宏任务在Node的执行顺序

Node 10以前:

  1. 执行完一个阶段的所有宏任务
  2. 执行完nextTick队列里面的内容
  3. 然后执行完微任务队列的内容

Node 11以后:

  1. 和浏览器的行为统一了,都是每执行一个宏任务就执行完微任务队列。

node大体的task(宏任务)执行顺序是这样的:

  1. timers 定时器:本阶段执行已经安排的 setTimeout() 和 setInterval() 的回调函数,并且是由 poll 阶段控制的。
  2. pending callbacks 待定回调:执行延迟到下一个循环迭代的 I/O 回调。
  3. idle, prepare:仅系统内部使用。
  4. poll 轮询:执行 I/O 回调,回到 timer 阶段执行回调。
  5. check 检测:setImmediate() 回调函数在这里执行。
  6. close callbacks 关闭的回调函数:一些准备关闭的回调函数,如:socket.on('close', ...)

app.use和app.get区别

  1. app.use(path,callback)中的callback既可以是router(路由)对象又可以是函数
  2. app.get(path,callback)中的callback只能是函数

path模块的__dirname 和 __filename

__dirname 和 __filename 是模块中 的一个内置成员,他们分别是:

  • __dirname 是当前文件夹的绝对路径
  • __filename是当前文件的绝对路径

exports 和 module.exports 的区别 

  1. 每一个模块中都有一个 module 对象, module 对象中有一个 exports 对象
  2. 我们可以把需要导出的成员都放到 module.exports 这个接口对象中,也就是 module.exports.xxx = xxx 的方式
  3. module.exports.xxx = xxx 的方式等价于 exports.xxx = xxx
  4. 当一个模块需要导出单个成员的时候,必须要使用 module.exports. = xxx
  5. 因为每个模块最终向外 return 的是 module.exports,而 exports 只是 module.exports 的一个引用,所以exports.xxx = xxx 赋值,也不会影响 module.exports

请问创建子进程的方法有哪些,简单说一下它们的区别

创建子进程的方法大致有:

  • spawn(): 启动一个子进程来执行命令
  • exec(): 启动一个子进程来执行命令,与spawn()不同的是其接口不同,它有一个回调函数获知子进程的状况
  • execFlie(): 启动一个子进程来执行可执行文件
  • fork(): 与spawn()类似,不同电在于它创建Node子进程需要执行js文件
  • spawn()与exec()、execFile()不同的是,后两者创建时可以指定timeout属性设置超时时间,一旦创建的进程超过设定的时间就会被杀死
  • exec()与execFile()不同的是,exec()适合执行已有命令,execFile()适合执行文件。

为什么会有Event Loop

​ JavaScript的任务分为两种同步异步,它们的处理方式也各自不同,同步任务是直接放在主线程上排队依次执行,异步任务会放在任务队列中,若有多个异步任务则需要在任务队列中排队等待,任务队列类似于缓冲区,任务下一步会被移到调用栈然后主线程执行调用栈的任务。(处理ajax请求的方式是异步的)

// 例子
function test () {
   console.log('start')
    setTimeout(() => {
        console.log('children2')
        Promise.resolve().then(() => {console.log('children2-1')})
    }, 0)
    setTimeout(() => {
        console.log('children3')
        Promise.resolve().then(() => {console.log('children3-1')})
    }, 0)
    Promise.resolve().then(() => {console.log('children1')})
    console.log('end') 
}

test()

// 以上代码在node11以下版本的执行结果(先执行所有的宏任务,再执行微任务)
// start
// end
// children1
// children2
// children3
// children2-1
// children3-1

// 以上代码在node11及浏览器的执行结果(顺序执行宏任务和微任务)
// start
// end
// children1
// children2
// children2-1
// children3
// children3-1

说一下事件循环eventloop

  1)所有同步任务都在主线程上执行,形成一个执行栈

  2)当主线程中的执行栈为空时,检查事件队列是否为空,如果为空,则继续检查;如不为空,则执行3

  3)取出任务队列的首部,加入执行栈

  4)执行任务

  5)检查执行栈,如果执行栈为空,则跳回第 2 步;如不为空,则继续检查

node中间件(中间层)

node中间件指的是封装http请求细节处理的方法。引入中间件来简化和隔离基础设施与业务逻辑之间的细节,提升开发效率

ip过滤、查询字符串传递、请求体解析、cookie信息处理、权限验证、日志记录、会话管理中间件(session)、gzip压缩中间件(如compress) 、异常处理

  • 代理:在开发环境下,我们可以利用代理来,解决最常见的跨域问题;在线上环境下,我们可以利用代理,转发请求到多个服务端
  • 缓存:缓存其实是更靠近前端的需求,用户的动作触发数据的更新,node中间层可以直接处理一部分缓存需求
  • 限流:node中间层,可以针对接口或者路由做响应的限流
  • 日志:相比其他服务端语言,node中间层的日志记录,能更方便快捷的定位问题(是在浏览器端还是服务端)
  • 监控:擅长高并发的请求处理,做监控也是合适的选项
  • 鉴权:有一个中间层去鉴权,也是一种单一职责的实现
  • 路由:前端更需要掌握页面路由的权限和逻辑
  • 服务端渲染:node中间层的解决方案更灵活,以SSR来说,在服务端将页面渲染好,可以加快用户的首屏加载速度,避免请求时白屏,还有利于网站做SEO,他的好处是比较好理解的

中间层怎样做的请求合并转发

  1. 使用express中间件multifetch可以将请求批量合并
  2. 使用express+http-proxy-middleware实现接口代理转发
  3. 不使用用第三方模块手动实现一个nodejs代理服务器,实现请求合并转发

       ①搭建http服务器,使用Node的http模块的createServer方法
②接收客户端发送的请求,就是请求报文,请求报文中包括请求行、请求头、请求体
③将请求报文发送到目标服务器,使用http模块的request方法

使用NPM有哪些好处?

通过NPM,你可以安装和管理项目的依赖,并且能够指明依赖项的具体版本号。 对于Node应用开发而言,你可以通过package.json文件来管理项目信息,配置脚本, 以及指明项目依赖的具体版本。

封装node中间件

const http = require('http')
function compose(middlewareList) {
  return function (ctx) {
    function dispatch (i) {
      const fn = middlewareList[i]
      try {
        return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)))
      } catch (err) {
        Promise.reject(err)
      }
    } 
    return dispatch(0)
  }
}
class App {
  constructor(){
    this.middlewares = []
  }
  use(fn){
    this.middlewares.push(fn)
    return this
  }
  handleRequest(ctx, middleware) {
    return middleware(ctx)
  }
  createContext (req, res) {
    const ctx = {
      req,
      res
    }
    return ctx
  }
  callback () {
    const fn = compose(this.middlewares)
    return (req, res) => {
      const ctx = this.createContext(req, res)
      return this.handleRequest(ctx, fn)
    }
  }
  listen(...args) {
    const server = http.createServer(this.callback())
    return server.listen(...args)
  }
}
module.exports = App