1. nodejs 有哪些特点?
是单线程的,但是有很高的可扩展性,使用 JavaScript 作为主流编程语言。使用的是非阻塞IO,异步处理机制和事件驱动
。处理高效。
擅长 IO 密集型业务业务处理,不擅长 CPU 密集型业务;
2. 什么是事件循环 Event Loop
在进程启动动时,Node 会创建一个类似于 while(true)的循环,每执行一次循环的过程我们称之为 Tick。每个 Tick 的过程就是查看是否有事件等待处理,如果有,就取出时间相关的回调函数。如果存在相关联的回调函数,就执行它们。然后进入下一个循环;如果不再有事件待处理,就退出进程。
1 | ┌───────────────────────┐ |
- timers: 这个阶段执行 setTimeout()和 setInterval()设定的回调。
- I/O callbacks: 执行几乎所有的回调,除了 close 回调,timer 的回调,和 setImmediate()的回调。
- idle, prepare: 仅内部使用。
- poll: 获取新的 I/O 事件;node 会在适当条件下阻塞在这里。
- check: 执行 setImmediate()设定的回调。
- close callbacks: 执行比如 socket.on(‘close’, …)的回调。
在底层, Node 是通过 libuv 来实现多线程的。Libuv
库负责 Node API 的执行。它将不同的任务分配给不同的线程,形成一个事件循环, 以异步的方式将任务的执行结果返回给 V8 引擎。
另:浏览器下的 Event Loop 如下:
- 所有任务都在js执行线程上执行,形成一个执行栈(Execution Context Stack)
- 在js执行线程之外还存在一个任务队列(Task Queen),系统把异步任务放到任务队列中,然后主线程继续执行后续的任务
- 一旦执行栈中所有的任务执行完毕,系统就会读取任务队列。如果这时异步任务已结束等待状态,就会从任务队列进入执行栈,恢复执行
- js执行线程不断重复上面的第三步
3. 在每个 tick 的过程中,如何判断是否有事件需要处理呢?
- 每个事件循环中有一个或者多个观察者,而判断是否有事件需要处理的过程就是向这些观察者询问是否有要处理的事件。
- 在 Node 中,事件主要来源于网络请求、文件的 I/O 等,这些事件对应的观察者有文件 I/O 观察者,网络 I/O 的观察者。
- 事件循环是一个典型的生产者/消费者模型。异步 I/O,网络请求等则是事件的生产者,源源不断为 Node 提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环则从观察者那里取出事件并处理。
4. 什么是回调函数?
回调函数是指用一个函数作为参数传入另一个函数,这个函数会被在某个时机调用。
5. node.js 计时器 promise.then, process.nextTick, setTimeout,setImmediate 执行顺序
首先了解 2 个概念
- macro-task(宏任务): script ,setTimeout, setInterval, setImmediate, I/O, UI rendering.Event Loop 在每个阶段执行的任务;
- micro-task (微任务): process.nextTick, Promise,Object. fvEvent Loop 在每个阶段之间执行的任务 observe,MutationObserver
对于 node.js, micro-task 的任务优先级高于 macro-task 的任务优先级,所以微任务普遍优先于宏任务的执行。
另一种优先级顺序,是“观察者优先级”, 在每次轮训检查中,各观察者的优先级分别是:idle观察者 > I/O观察者 > check观察者。
- idle 观察者:process.nextTick
- I/O 观察者:一般性的 I/O 回调,如网络,文件,数据库 I/O 等
- check 观察者:setTimeout > setImmediate (setTimeout(()=>{},0)优先于 setImmediate 执行);
- 总结
- 同步代码执行顺序优先级高于异步代码执行顺序优先级;
- new Promise(fn)中的 fn 是同步执行;
- process.nextTick()>Promise.then()>setTimeout>setImmediate。
6. next tick 和 setImmediate 的区别是什么?
NextTick 会等待当前的 event 执行完成或者下一轮儿事件循环到达再执行。
SetImmediate, 会在下一轮的事件循环中,执行回调并且返回当前的循环来做读写操作.
7. node.js 模块加载机制
Nodejs 遵循 commonjs 规范的模块加载机制,使用 require 加载文件,使用 exports 或 module.exports 导出文件
8. 什么是 globals?
有三个 global 的关键字。
- Global 代表的是最上层的命名空间,用来管理所有其他的全局对象。
- Process 是一个全局对象,可以把异步函数转化成异步回调, 它可以在任何地方被访问,它主要是用来返回系统的应用信息和环境信息.
- Buffer, 是用来处理二进制数据的类.
9. 什么是 EventEmitter?
EventEmitter 是 node 中一个实现观察者模式的类,主要功能是监听和发射消息,用于处理多模块交互问题.
主要用于: 1) 模块间传递消息 2) 回调函数内外传递消息 3) 处理流数据,因为流是在 EventEmitter 基础上实现的. 4) 观察者模式发射触发机制相关应用
10. process 有哪些常用方法?
process.stdin, process.stdout, process.stderr, process.on, process.env, process.argv, process.arch, process.platform, process.exit
11. 解释一下什么是 reactor pattern。
reactor 设计模式是 event-driven architecture 的一种实现方式,处理多个客户端并发的向服务端请求服务的场景。每种服务在服务端可能由多个方法组成。reactor 会解耦并发请求的服务并分发给对应的事件处理器来处理。
Reactor pattern 主要是非阻滞的 i/o 操作。提供一个回调函数来关联 io 操作。io 请求完成以后会不会提交给 demultiplexer, 这是一个通知接口用来处理并发性的非阻滞的 io 操作,这个功能是通过查询一个 event loop 来实现的.
12. 什么是错误优先的回调函数
node.js 广泛使用异步编程,而异步中的异常很难在主程序中捕获。为此,我们将异步中的异常、错误,通过回调函数传递给主程序。
错误优先的回调函数用于传递错误和数据。第一个参数始终应该是一个错误对象, 用于检查程序是否发生了错误。其余的参数用于传递数据。
13. 如何避免回调地狱
- 模块化:将回调函数分割为独立的函数
- 使用 Promise/ansyc (ES6)
- 使用 yield 来计算生成器或 Promise
14. 什么是 Promise?
Promise 可以帮助我们更好地处理异步操作。采用类似同步结构的代码,实现异步处理,同时避免回调地狱。
15. 使用 NPM 有哪些好处?
通过 NPM,你可以安装和管理项目的依赖,并且能够指明依赖项的具体版本号。 对于 Node 应用开发而言,你可以通过 package.json 文件来管理项目信息,配置脚本, 以及指明项目依赖的具体版本。
16. 什么是 Stub?
Stub 是用于模拟一个组件或模块的函数或程序,在测试用例中很常用。简单的说,你可以用 Stub 去模拟一个方法,从而避免调用真实的方法, 使用 Stub 你还可以返回虚构的结果。你可以配合断言使用 Stub。
17. 什么是测试金字塔?
测试金字塔指的是: 测试用例从下到上,包括单元测试、集成测试、端到端测试;当我们在编写测试用例时,底层的单元测试应该远比上层的端到端测试要多。
18. Node 模块机制
Node 中,每个文件模块都是一个对象,所有的模块都是 Module 的实例。
19. Node.js require 的模块加载机制
1、先计算模块路径
2、如果模块在缓存里面,取出缓存;如果不存在生成模块实例,存入缓存
3、加载模块
4、输出模块的 exports 属性
20. V8 的垃圾回收机制
在 V8 中,主要将内存分为新生代和老生代两代。新生代中的对象存活时间较短的对象,老生代中的对象存活时间较长,或常驻内存的对象。
新生代中的对象主要通过 Scavenge 算法进行垃圾回收。这是一种采用复制的方式实现的垃圾回收算法。它将堆内存一份为二,分为 2 个 semispace。在这两个 semispace 空间中,只有一个处于使用中,另一个处于闲置状态。处于使用状态的 semispace 空间称为 From 空间,处于闲置状态的空间称为 To 空间。
- 当开始垃圾回收的时候,会检查 From 空间中的存活对象,这些存活对象将被复制到 To 空间中,而非存活对象占用的空间将会被释放。完成复制后,From 空间和 To 空间发生角色对换。
- 应为新生代中对象的生命周期比较短,就比较适合这个算法。
- 当一个对象经过多次复制依然存活,它将会被认为是生命周期较长的对象。这种新生代中生命周期较长的对象随后会被移到老生代中。
老生代主要采取的是标记清除的垃圾回收算法。与 Scavenge 复制活着的对象不同,标记清除算法在标记阶段遍历堆中的所有对象,并标记活着的对象,只清理死亡对象。活对象在新生代中只占叫小部分,死对象在老生代中只占较小部分,这是为什么采用标记清除算法的原因。
标记清楚算法的问题:
主要问题是每一次进行标记清除回收后,内存空间会出现不连续的状态
- 这种内存碎片会对后续内存分配造成问题,很可能出现需要分配一个大对象的情况,这时所有的碎片空间都无法完成此次分配,就会提前触发垃圾回收,而这次回收是不必要的。
- 为了解决碎片问题,标记整理被提出来。就是在对象被标记死亡后,在整理的过程中,将活着的对象往一端移动,移动完成后,直接清理掉边界外的内存。
21. child-process
node 是异步非阻塞的,这对高并发非常有效.可是我们还有其它一些常用需求,比如和操作系统 shell 命令交互,调用可执行文件,创建子进程进行阻塞式访问或高 CPU 计算等,child-process 就是为满足这些需求而生的.child-process 顾名思义,就是把 node 阻塞的工作交给子进程去做.
22. 创建子进程的方法有哪些,简单说一下它们的区别
创建子进程的方法大致有:
- spawn(): 启动一个子进程来执行命令
- exec(): 启动一个子进程来执行命令,与 spawn()不同的是其接口不同,它有一个回调函数获知子进程的状况
- execFlie(): 启动一个子进程来执行可执行文件
- fork(): 与 spawn()类似,不同电在于它创建 Node 子进程需要执行 js 文件
- spawn()与 exec()、execFile()不同的是,后两者创建时可以指定 timeout 属性设置超时时间,一旦创建的进程超过设定的时间就会被杀死
- exec()与 execFile()不同的是,exec()适合执行已有命令,execFile()适合执行文件。
(exec 可以用操作系统原生的方式执行各种命令,如管道 cat ab.txt | grep hello; execFile 是执行一个文件; spawn 是流式和操作系统进行交互; fork 是两个 node 程序(javascript)之间时行交互.)
23. 怎样充分利用多个 CPU?
一个 CPU 运行一个 node 实例。 (Child_Process)
24. 有哪些方法可以让 node 程序遇到错误后自动重启?
Linxu: 1) runit 2) forever 3) nohup npm start &
Winwow: Windows service(auto restart)
25. 关于 Express 框架
25.1. express 生成器的作用是什么?
通过应用生成器工具 express-generator 可以快速创建一个应用的框架, 包含一整套配置好的服务器配置, 文件和文件夹等, 包括静态资源的暴露等, 包括路由的配置, 和模板引擎配置, 以及 404 的处理,异常处理等;
25.2. express 优点是什么?
Express 的优点是线性逻辑:路由和中间件完美融合,通过中间件形式把业务逻辑细分,简化,一个请求进来经过一系列中间件处理后再响应给用户,再复杂的业务也是线性了,清晰明了。
25.3. 什么是中间件
- 中间件是可以访问请求对象,响应对象以及 next 应用程序请求-响应周期中的函数,使用 app.use()来使用/定义中间件;
- Express 是一个自身功能极简,完全是路由和中间件构成一个 web 开发框架;
- 从本质上来说,一个 Express 应用就是在调用各种中间件。封装了一些或许复杂但肯定是通用的功能, 非内置的中间件需要通过安装后,require 到文件就可以运行。
25.4. express 缺点是什么?
Express 是基于 callback 来组合业务逻辑。Callback 有两大硬伤,一是不可组合
,二是异常不可捕获
。
25.5. ejs 作用是什么?
EJS 是一个 Javascript 模板库, 用来从 JSON 数据中生成 HTML 文件。其他模板库(模板引擎如 jade/pug);
25.6. 什么是后端渲染项目?
浏览器请求静态网页资源, 服务器端会在后端把数据渲染到 HTML 页面上, 再把 html 文件内的字符串一起返回给浏览器进行展示的一种手段, Nodejs 中体现为 ejs 和 pug 模板引擎;
25.7. session 和 cookie 的作用和区别?
session 是区别于数据库存在的一种服务器临时存储技术, 它主要存储一些无需持久化的数据, 比如临时的登录状态信息等
cookie 是存在于浏览器上的一种浏览器本地存储的方式, 同域名下的 cookie 不同标签页可以共享, 默认过期时间是浏览器关闭时, 而且在进行 http 请求时, 会自动带上浏览器全部的 cookie 发给后台, 后台也可以获取 cookie, 设置可以在响应时, 想浏览器中设置 cookie。
25.8. 跨域(CORS)是什么, 如何解决跨域?
当 ajax 请求所在域名或接口和请求目标 url 的域名或接口, 有一个不同, 即发生了跨域请求, 浏览器会阻止这次 ajax 请求.
解决跨域请求的方法:
- 让后台(API)开启跨域支持;
- 通过iframe
- 使用 jsonp 方式处理跨域(需要服务器端支持);
- 反向代理(API Gateway),UI/API 配置到同一个API Gateway,浏览器端看到的结果就是在同一个域了;
- UI有编程能力(node.js/ asp.net)的话,可以代理其他站点的API
25.9. express4 中 app 和 router 的区别
app 可以认为是全局的,方便注入中间件;router 主要针对具体的 routing,除了注入中间件,具体处理 routing 对应的核心业务逻辑;
app 级路由不方便模块化封装,而且 app.js 文件没有路由代码,路由代码在 router 文件中,而且 router 方便模块化封装
25.10. Cookies 如何防范 XSS 攻击?
XSS(Cross-Site Scripting,跨站脚本攻击)是指攻击者在返回的 HTML 中插入 JavaScript 脚本。为了减轻这些攻击,需要在 HTTP 头部配置 set-cookie:
HttpOnly - 这个属性可以防止 cross-site scripting,因为它会禁止 Javascript 脚本访问 cookie。
secure - 这个属性告诉浏览器仅在请求为 HTTPS 时发送 cookie。
结果应该是这样的: Set-Cookie: sid=; HttpOnly. 使用 Express 的话,cookie-session 默认配置好了。
25.11. Koa 和 Express 的对比
- Express 是一个完整的 nodejs 应用框架。Koa 是由 Express 团队开发的,但是它有不同的关注点。Koa 致力于核心中间件功能。nodejs 中间件是访问请求对象(req)和响应对象(res)的例程。 Express 包含一个完整的应用程序框架,具有路由和模板等功能。
- Koa 对 Express 进行了扩展,并充分利用了 ES7 新的语法。Koa 的 Context 对象是对 Express 核心请求和应答对象的扩展,另外利用 async/await 来消除回调(callback)陷阱。
- Koa 的中间件的设计是洋葱模型。
- Koa 不是立即响应,是整个中间件处理完成在最外层进行了响应,而 Express 则是立即响应
26. KOA
26.1. KOA 洋葱模型
中间件执行就像洋葱一样,最早 use 的中间件,就放在最外层。处理顺序从左到右,左边接收一个 request,右边输出返回 response。
一般的中间件都会执行两次,调用 next 之前为第一次,调用 next 时把控制传递给下游的下一个中间件。当下游不再有中间件或者没有执行 next 函数时,就将依次恢复上游中间件的行为,让上游中间件执行 next 之后的代码。
—————- END —————-