前端知识体系10.Node/服务端

本文目录:

  • 1.讲一下你对node的了解
  • 2.koa2 和 express 主要区别
  • 3.讲讲node的流
  • 4.node中加载相同模块,会重复打印吗
  • 5.说说你对koa2中间件的理解
  • 6.什么是洋葱模型
  • 7.介绍下浏览器和node的事件循环
  • 8.说一说koa中的ctx
  • 9.说一下npm包管理机制
  • 10.手动修改 package.json 中的包版本号后运行 npm install 命令会下载新包吗
  • 11.node单线程容易崩溃,怎么维护服务的
  • 12.有没有考虑过高并发场景,如何解决
  • 13.node内存泄漏的检查方法,大型项目在运行时发现内存占用率越来越高,怎么去检查
  • 14.了解node多进程吗?node多进程怎么通信?node可以开启多线程吗

1.讲一下你对node的了解

从定义+特点+作用来说对node的理解
定义:node是基于Chrmo v8引擎的JavaScript运行环境;
特点:具有事件驱动,非阻塞I/O模型,高并发和轻量级,单线程,单进程特点;
作用:执行js并不受浏览器安全级别的限制(跨域),可以操作系统级别的api:如文件读写,进程管理,网络通信等

2.koa2 和 express 主要区别

  • express 是大而全有路由等,koa2 小而精通过中间件
  • koa2 能使用 async await,express 不能
  • koa2 有洋葱模型和 ctx 上下文,express 没有

3.讲讲node的流

流(stream)是一种在 Node.js 中处理流式数据的抽象接口。
使用node存取大数据的时候,因为存取的数据都是放在内存里的,大段的数据会消耗大量的内存,通过使用stream工具将资源数据分成一块一块的进行操作,可以有效的提升程序的运行效率。
node中stream有四种流类型:
Readable - 可读操作
Writable - 可写操作。
Duplex - 可读可写操作.
Transform - 操作被写入数据,然后读出结果。
所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:
data - 当有数据可读时触发。
end - 没有更多的数据可读时触发。
error - 在接收和写入过程中发生错误时触发。
finish - 所有数据已被写入到底层系统时触发。

4.node中加载相同模块,会重复打印吗

//a.js 
function foo() {
    console.log('foo'); // 
 }
foo();
//b.js
require('./a.js');
require('./a.js');
//node b.js

不会,因为commonjs当加载到相同模块的时候,会首先检查是否有缓存,有缓存的则不会再去加载代码。

5.说说你对koa2中间件的理解

基本上,Koa 所有的功能都是通过中间件实现的。
每个中间件默认接受两个参数,第一个参数是 Context 对象,第二个参数是 next 函数。只要调用 next 函数,就可以把执行权转交给下一个中间件。
如果中间件内部没有调用 next 函数,那么执行权就不会传递下去。
多个中间件会形成一个栈结构(middle stack),以“先进后出”(first-in-last-out)的顺序执行。整个过程就像,先是入栈,然后出栈的操作。

6.什么是洋葱模型

洋葱模型其实就是中间件处理的流程,中间件生命周期大致有:
前期处理,交给并等待其它中间件处理,后期处理
多个中间件处理,就形成了所谓的洋葱模型,它是 AOP 面向切面编程的一种应用。

7.介绍下浏览器和node的事件循环

浏览器事件循环:
代码分为同步代码和异步代码,同步代码压入执行栈直接执行,异步代码放到任务队列,任务队列分为多种不同的任务,先执行微任务,再执行宏任务,宏任务中如果又有微任务,则将其放到微任务队列中排队等待,以此循环。
Node事件循环:
外部输入数据–>轮询阶段(poll)–>检查阶段(check)–>关闭事件回调阶段(close callback)–>定时器检测阶段(timer)–>I/O 事件回调阶段(I/O callbacks)–>闲置阶段(idle, prepare)–>轮询阶段(按照该顺序反复运行)…
timers 阶段:这个阶段执行 timer(setTimeout、setInterval)的回调
I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
idle, prepare 阶段:仅 node 内部使用
poll 阶段:获取新的 I/O 事件, 适当的条件下 node 将阻塞在这里
check 阶段:执行 setImmediate() 的回调
close callbacks 阶段:执行 socket 的 close 事件回调
注意:上面六个阶段都不包括 process.nextTick(),这个函数其实是独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行。
来看个例子

console.log('start')
setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(function() {
    console.log('promise1')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
  Promise.resolve().then(function() {
    console.log('promise2')
  })
}, 0)
Promise.resolve().then(function() {
  console.log('promise3')
})
console.log('end')
//start=>end=>promise3=>timer1=>timer2=>promise1=>promise2

一开始执行栈的同步任务(这属于宏任务)执行完毕后(依次打印出 start end,并将 2 个 timer 依次放入 timer 队列),会先去执行微任务(这点跟浏览器端的一样),所以打印出 promise3
然后进入 timers 阶段,执行 timer1 的回调函数,打印 timer1,并将 promise.then 回调放入 microtask 队列,同样的步骤执行 timer2,打印 timer2;这点跟浏览器端相差比较大,timers 阶段有几个 setTimeout/setInterval 都会依次执行,并不像浏览器端,每执行一个宏任务后就去执行一个微任务。
Node 与浏览器的 Event Loop 差异
浏览器环境下,microtask 的任务队列是每个 macrotask 执行完之后执行。而在 Node.js 中,microtask 会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask 队列的任务。
通过一个例子来说明两者区别:

setTimeout(()=>{
    console.log('timer1')
    Promise.resolve().then(function() {
        console.log('promise1')
    })
}, 0)
setTimeout(()=>{
    console.log('timer2')
    Promise.resolve().then(function() {
        console.log('promise2')
    })
}, 0)

浏览器端运行结果:timer1=>promise1=>timer2=>promise2
Node 端运行结果:timer1=>timer2=>promise1=>promise2

8.说一说koa中的ctx

ctx是context的缩写中文一般叫成上下文,这个在所有语言里都有的名词,可以理解为上(request)下(response)沟通的环境,所以koa把它们都封装进了ctx对象。
ctx.request koa的request对象
ctx.response koa的response对象
ctx.throw() 抛异常
ctx.redirect() 重定向(ctx.status = 301;永久重定向)
ctx.headers 获取客户端头部,格式是JSON
console.log(ctx.headers['host']); // 指定获取host地址
ctx.set() 给客户端设置头部
ctx.set('set-cookie', 'type=12');

9.说一下npm包管理机制

Node.js 项目遵循模块化的架构,当我们创建了一个 Node.js 项目,意味着创建了一个模块,这个模块必须有一个描述文件,即 package.json。
package.json 中有非常多的属性,其中必须填写的只有两个:name 和 version ,这两个属性组成一个 npm 模块的唯一标识。
dependencies 指定了项目运行所依赖的模块,开发环境和生产环境的依赖模块都可以配置到这里。
有一些包有可能你只是在开发环境中用到,例如你用于检测代码规范的 eslint ,用于进行测试的 jest ,用户使用你的包时即使不安装这些依赖也可以正常运行,反而安装他们会耗费更多的时间和资源,所以你可以把这些依赖添加到 devDependencies 中,这些依赖照样会在你本地进行 npm install 时被安装和管理,但是不会被安装到生产环境。
我们经常看到,在 package.json 中各种依赖的不同写法:

"dependencies": {
  "signale": "1.4.0",
  "figlet": "*",
  "react": "16.x",
  "table": "~5.4.6",
  "yargs": "^14.0.0"
}

前面三个很容易理解:
"signale": "1.4.0": 固定版本号
"figlet": "*": 任意版本(>=0.0.0)
"react": "16.x": 匹配主要版本(>=16.0.0 <17.0.0)
"react": "16.3.x": 匹配主要版本和次要版本(>=16.3.0 <16.4.0)
再来看看后面两个,版本号中引用了 ~ 和 ^ 符号:
~: 当安装依赖时获取到有新版本时,安装到 x.y.z 中 z 的最新的版本。即保持主版本号、次版本号不变的情况下,保持修订号的最新版本。
^: 当安装依赖时获取到有新版本时,安装到 x.y.z 中 y 和 z 都为最新版本。 即保持主版本号不变的情况下,保持次版本号、修订版本号为最新版本。
scripts 用于配置一些脚本命令的缩写,各个脚本可以互相组合使用,这些脚本可以覆盖整个项目的生命周期,配置后可使用 npm run command 进行调用。
自 npm 5.0后,项目中如果没有 package-lock.json 文件的时候,npm 会自动帮我们生成。该文件的主要作用是记录依赖包之间的具体版本号,对包版本有一个锁定的意义,项目开发中应该将此文件上传到git等版本控制工具

10.手动修改 package.json 中的包版本号后运行 npm install 命令会下载新包吗

是否更新要看特定情况,取决于 package.json中版本号的标记和 package-lock.json 是否一致。

11.node单线程容易崩溃,怎么维护服务的

可以uncaughtException来全局捕获未捕获的Error,同时还可以将此函数的调用栈打印出来,捕获之后可以有效防止node进程退出,如:

process.on('uncaughtException', function (err) {
  //打印出错误
  console.log(err);
  //打印出错误的调用栈方便调试
  console.log(err.stack);
});

另外在回调中通过try/catch,同样可以确保线程的安全,有一些插件也可以帮助我们实现node进程的守护。

12.有没有考虑过高并发场景,如何解决

通过pm2多开进程
通过nginx实现http缓存和gzip资源压缩
通过nginx实现负载均衡,拓展node服务器集群

13.node内存泄漏的检查方法,大型项目在运行时发现内存占用率越来越高,怎么去检查

process.memoryUsage() 方法会返回描述 Node.js 进程的内存使用情况(以字节为单位)的对象。
例如:

console.log(process.memoryUsage());

会返回:

{
  rss: 4935680,
  heapTotal: 1826816,
  heapUsed: 650472,
  external: 49879,
  arrayBuffers: 9386
}

heapTotal 和 heapUsed 代表 V8 的内存使用情况。
rss,常驻集大小, 是为进程分配的物理内存(总分配内存的子集)的大小,包括所有的 C++ 和 JavaScript 对象与代码。
当使用Worker线程时, rss 会是对整个进程都有效的值,而其他字段只代表当前线程。

14.了解node多进程吗?node多进程怎么通信?node可以开启多线程吗

开启多进程使用child_process模块或cluster模块,开启多线程使用worker_threads模块。
进程创建有四个方法spawn、exec、execFile、fork。
进程通信方式有stdin/stdout传递json、node原生IPC、sockets、message queue。
线程通信方式共享内存、parentPort、MessageChannel。

你可能感兴趣的:(前端知识体系10.Node/服务端)