node前置知识点
阅读大佬笔记有感:node技术栈,总结了一些笔记
正文
javascript 引擎在编译阶段会进行性能优化,很多优化依赖于能够根据代码词法进行静态分析,预先确定了变量和函数的定义位置,才能快速找到标识符,但是在词法分析阶段遇到了 with 或 eval 无法明确知道它们会接收什么代码,也就无法判断标识符的位置,最简单的做法就是遇到 with 或 eval 不做任何优化,使用其中一个都会导致代码运行变慢,因此,请不要使用with 或 eval。(eval函数接受字符串并将其转换成代码执行,with通常被当作重复引用同一个对象中的多个属性的快捷方式)
typeof
用来检测基本类型,instanceof
用来检测数组、对象、正则数组去重的三种方式
Set数组去重,ES6新的数据结构,类似数组,但元素唯一
reduce 数组对象去重,对数组中的每一个元素依次执行回调函数,不含数组中未赋值、被删除的元素,回调函数接收四个参数
callback
- previousValue:上一次调用回调返回的值,或者是提供的初始值(initialValue)
- currentValue:数组中当前被处理的元素
- index:当前元素在数组中的索引
- array:调用reduce的数组
initialValue:可选,作为第一次调用callback的第一个参数
_.uniqBy(参考lodash:一个现代的 JavaScript 实用程序库,提供模块化,性能和附加功能)
深拷贝:
function copy(elments){ //根据传入的元素判断是数组还是对象 let newElments = elments instanceof Array ? [] : {}; for (let key in elments) { //注意数组也是对象类型,如果遍历的元素是对象,进行深度拷贝 newElments[key] = typeof elments[key] === 'object' ? copy(elments[key]) : elments[key]; } return newElments; }
- es6 的 扩展运算符(…) 也是可以进行深拷贝的,但好像只能拷贝一维,多维好像浅拷贝了
由于计算机底层存储都是基于二进制的,需要事先由十进制转换为二进制存储与运算,这整个转换过程中,类似于 0.1、0.2 这样的数是无穷尽的,无法用二进制数精确表示。JavaScript 采用的是 IEEE 754 双精确度标准,能够有效存储的位数为 52 位,所以就需要做舍入操作,这无可避免的会引起精度丢失。另外我们在 0.1 与 0.2 相加做对阶、求和、舍入过程中也会产生精度的丢失。
js有大数处理精度丢失问题,(超过2的53次方)处理方法可以用字符串代替,如果涉及到json,可能需要使用第三方库json-bigint
如果服务端的某个数据状态定期的去变化,那么前端需要定时的去服务端取这个状态,因为 http 是无状态的链接,如果要实时的去取服务端的这种变化有两种方法,一个是长轮询,一个是通过 websocket,websocket 浏览器兼容性不好,因此长轮询还是一个普遍的用法 一种做法是通过定时器,不断的去访问接口 第二种是使用 Generator
为什么使用TS
- 类型安全,可以类比java
- TS面向对象理念,支持面向对象的封装,继承,多态三大特性
- 类似babel,es6,es7新语法都可以写
- 生产力工具的提升,vs code + TS 使IDE更容易理解你的代码
TS const声明的变量必须有默认值,let声明的则不是必须的
nodejs适用于什么?
I/O 密集型场景
- Node.js 的优势主要在于事件循环,非阻塞异步 I/O,只开一个线程,不会每个请求过来我都去创建一个线程,从而产生资源开销。
RESTful API
- 通常我们可以使用 Node.js 来做为中间层,负责组装数据提供 API 接口给到前端调用,这些数据源可能来自第三方接口或者数据库,例如,以前可能我们通过后端 Java、PHP 等其它语言来做,现在我们前端工程师通过 Node.js 即可完成,后端则可以更专注于业务开发。既然提到了 RESTful API,顺便推荐一个去哪儿开源的 API 管理工具 YAPI:https://github.com/YMFE/yapi 使用的 Node.js 进行开发的(声明下这里不是打广告,只是这个用起来真的很赞!忍不住向给大家推荐!)。
RPC 服务
- RPC(Remote Procedure Call)中文名「远程过程调用」,也许你对它很陌生,但是在当今微服务模式下,我们可能是针对功能或者具体的业务形态进行服务化,那么服务之间的通信一种常见的模式我们都知道通过 HTTP 来实现,了解网络模型的同学可能知道,如果我们现在通过 TCP 的方式是不是会更高效呢?当然是的,HTTP 属于应用层协议,在这之下就是传输层,显然以 TCP 形式是很有优势的,RPC 服务也就是采用的 TCP,现在出名的 RPC 服务例如,Google 的 gRPC、阿里的 Dubble。
基础工具
- 可以做为基础工具,前端领域中的编译器、构建工具、搭建脚手架等。比较出名的例如 Webpack、Gulp 都是很成功的。
论坛社区
- Nodeclub 是使用 Node.js 和 MongoDB 开发的社区系统,界面优雅,功能丰富,小巧迅速,可以用它搭建自己的社区。Cnode 社区就是一个成功的例子,Cnode 地址:https://cnodejs.org/ https://github.com/cnodejs/nodeclub
Backend For Frontend
- Backend For Frontend,简称 BFF,服务于前端的后端,并非是一种新技术只是一种逻辑上的分层,在这一层我们可以做一些资源的整合,例如:原先前端需要从三个不同的地方来获取资源,那么,有了这一层之后,我们是不是可以做个聚合,统一处理之后返回给前端,同时也不授后端系统的变迁,导致也要去更改。
Serverless
- 这将是未来经常会听到的一个词,ServerLess 是一种 “无服务器架构”,它不需要开发者去关心运维、流量处理这些工作,开发者则可以更关注于业务本身。函数即服务,那么写一个函数就可以实现一个 API 接口给到前端,显然对开发工作是减轻了很多,在 JavaScript 中函数则是一等一的公民,在 ServerLess 这一场景下 Node.js 本身也很轻量级,还是拥有着很大的优势
Microservices
- 微服务也是近两年一个很火热的词,这里提几个微服务主要的特点:小型服务、以独立进程运行、可以使用不同语言。那么这里则可以根据业务形态来选择不同的语言实现,Node.js 本身也是很轻量级的,实现起来也很快,在一些 I/O 密集场景还是很适用的。
本文从 Handler 处理方式、中间件执行机制的实现、响应机制三个维度来对 Express、Koa 做了比较,通常都会说 Koa 是洋葱模型,这重点在于中间件的设计。但是按照上面的分析,会发现 Express 也是类似的,不同的是 Express 中间件机制使用了 Callback 实现,这样如果出现异步则可能会使你在执行顺序上感到困惑,因此如果我们想做接口耗时统计、错误处理 Koa 的这种中间件模式处理起来更方便些。最后一点响应机制也很重要,Koa 不是立即响应,是整个中间件处理完成在最外层进行了响应,而 Express 则是立即响应。
在模块加载机制中,Node.js 采用了延迟加载的策略,只有在用到的情况下,系统模块才会被加载
在 Node.js 中模块加载一般会经历 3 个步骤,路径分析、文件定位、编译执行。
module.exports 与 exports 的区别?
- exports 相当于 module.exports 的快捷方式
- 但是要注意不能改变 exports 的指向,我们可以通过 exports.test = ‘a’ 这样来导出一个对象,但是不能向下面示例直接赋值,这样会改变 exports 的指向
- // 错误的写法 将会得到 undefined exports = { ‘a’: 1, ‘b’: 2 } // 正确的写法 modules.exports = { ‘a’: 1, ‘b’: 2 }
实现继承的方法
ES6方法:class和extends关键词
原生JS:Object.setProtitypeOf().例如针对server函数和EventEmitter函数
- Object.setPrototypeOf(Server.prototype, EventEmitter.prototype); Object.setPrototypeOf(Server, EventEmitter);
缓存雪崩:对于需要查询 DB 的数据,我们一般称之为热点数据,这类数据通常是要在 DB 之上增加一层缓存,但是在高并发场景下,如果这个缓存正好失效,此时就会有大量的请求直接涌入数据库,对数据库造成一定的压力
setImmediate() 与 setTimeout(() => {}, 0)(传入 0 毫秒的超时)、process.nextTick() 有何不同?
- 传给 process.nextTick() 的函数会在事件循环的当前迭代中(当前操作结束之后)被执行。 这意味着它会始终在 setTimeout 和 setImmediate 之前执行。延迟 0 毫秒的 setTimeout() 回调与 setImmediate() 非常相似。 执行顺序取决于各种因素,但是它们都会在事件循环的下一个迭代中运行。
- process.nextTick (),效率最高,消费资源小,但会阻塞 CPU 的后续调用;
- setTimeout (),精确度不高,可能有延迟执行的情况发生,且因为动用了红黑树,所以消耗资源大;
- setImmediate (),消耗的资源小,也不会造成阻塞,但效率也是最低的。
Node.js 是单线程模型,但是其基于事件驱动、异步非阻塞模式,可以应用于高并发场景,避免了线程创建、线程之间上下文切换所产生的资源开销。如果你有需要大量计算,CPU 耗时的操作,开发时候要注意。
多线程的代价还在于创建新的线程和执行期上下文线程的切换开销,由于每创建一个线程就会占用一定的内存,当应用程序并发大了之后,内存将会很快耗尽。类似于上面单线程模型中例举的例子,需要一定的计算会造成当前线程阻塞的,还是推荐使用多线程来处理。线程间资源是共享的,关注的是安全问题。
关于node进程的几点总结
- Javascript 是单线程,但是做为宿主环境的 Node.js 并非是单线程的。
- 由于单线程原故,一些复杂的、消耗 CPU 资源的任务建议不要交给 Node.js 来处理,当你的业务需要一些大量计算、视频编码解码等 CPU 密集型的任务,可以采用 C 语言。
- Node.js 和 Nginx 均采用事件驱动方式,避免了多线程的线程创建、线程上下文切换的开销。如果你的业务大多是基于 I/O 操作,那么你可以选择 Node.js 来开发。
Nodejs 多进程架构模型解决了单进程、单线程无法充分利用系统多核 CPU 的问题
守护进程:
- index.js 文件里的处理逻辑使用 spawn 创建子进程完成了上面的第一步操作。
- 设置 options.detached 为 true 可以使子进程在父进程退出后继续运行(系统层会调用 setsid 方法),参考 options_detached,这是第二步操作。
- options.cwd 指定当前子进程工作目录若不做设置默认继承当前工作目录,这是第三步操作。
- 运行 daemon.unref () 退出父进程,参考 options.stdio,这是第四步操作。
进程间是独立的关注的是通信问题,线程间资源是共享的关注的是安全问题
孤儿进程:父进程创建子进程之后,父进程退出了,但是父进程对应的一个或多个子进程还在运行,这些子进程会被系统的 init 进程收养,对应的进程 ppid 为 1,这就是孤儿进程。
IPC (Inter-process communication) ,即进程间通信技术,由于每个进程创建之后都有自己的独立地址空间,实现 IPC 的目的就是为了进程之间资源共享访问,实现 IPC 的方式有多种:管道、消息队列、信号量、Domain Socket,Node.js 通过 pipe 来实现。
Node.js 是单线程还是多线程?进一步会提问为什么是单线程?
- 第一个问题,Node.js 是单线程还是多线程?这个问题是个基本的问题,在以往面试中偶尔提到还是有不知道的,Javascript 是单线程的,但是做为其在服务端运行环境的 Node.js 并非是单线程的。
- 第二个问题,Javascript 为什么是单线程?这个问题需要从浏览器说起,在浏览器环境中对于 DOM 的操作,试想如果多个线程来对同一个 DOM 操作是不是就乱了呢,那也就意味着对于 DOM 的操作只能是单线程,避免 DOM 渲染冲突。在浏览器环境中 UI 渲染线程和 JS 执行引擎是互斥的,一方在执行时都会导致另一方被挂起,这是由 JS 引擎所决定的。
多进程或多个 Web 服务之间的状态共享问题?
- 多进程模式下各个进程之间是相互独立的,例如用户登陆之后 session 的保存,如果保存在服务进程里,那么如果我有 4 个工作进程,每个进程都要保存一份这是没必要的,假设服务重启了数据也会丢失。多个 Web 服务也是一样的,还会出现我在 A 机器上创建了 Session,当负载均衡分发到 B 机器上之后还需要在创建一份。一般的做法是通过 Redis 或者 数据库来做数据共享。
Net 与 Dgram 是基于网络模型的传输层来实现的,分别对应于 TCP、UDP 协议
TCP 粘包是什么?该怎么解决?
客户端(发送的一端)在发送之前会将短时间有多个发送的数据块缓冲到一起(发送端缓冲区),形成一个大的数据块一并发送,同样接收端也有一个接收端缓冲区,收到的数据先存放接收端缓冲区,然后程序从这里读取部分数据进行消费,这样做也是为了减少 I/O 消耗达到性能优化。
TCP 粘包解决方案?
- 方案一:延迟发送
- 方案二:关闭 Nagle 算法
- 方案三:封包 / 拆包 : 通信双方约定好格式,将消息分为定长的消息头(Header)和不定长的消息体(Body),在解析时读取消息头获取到内容占用的长度,之后读取到的消息体内容字节数等于字节头的字节数时,我们认为它是一个完整的包。
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 (CC BY-NC-ND 4.0) 进行许可。