1.说说你对Node.js 的理解?优缺点?应用场景?
一、是什么
Node.js
是一个开源与跨平台的 JavaScript
运行时环境
在浏览器外运行 V8 JavaScript 引擎(Google Chrome 的内核),利用事件驱动、非阻塞和异步输入输出模型等技术提高性能
可以理解为 Node.js
就是一个服务器端的、非阻塞式I/O的、事件驱动的JavaScript
运行环境
非阻塞异步
Nodejs
采用了非阻塞型I/O
机制,在做I/O
操作的时候不会造成任何的阻塞,当完成之后,以时间的形式通知执行操作
例如在执行了访问数据库的代码之后,将立即转而执行其后面的代码,把数据库返回结果的处理代码放在回调函数中,从而提高了程序的执行效率
事件驱动
事件驱动就是当进来一个新的请求的时,请求将会被压入一个事件队列中,然后通过一个循环来检测队列中的事件状态变化,如果检测到有状态变化的事件,那么就执行该事件对应的处理代码,一般都是回调函数
比如读取一个文件,文件读取完毕后,就会触发对应的状态,然后通过对应的回调函数来进行处理
二、优缺点
优点:
- 处理高并发场景性能更佳
- 适合I/O密集型应用,值的是应用在运行极限时,CPU占用率仍然比较低,大部分时间是在做 I/O硬盘内存读写操作
因为Nodejs
是单线程,带来的缺点有:
- 不适合CPU密集型应用
- 只支持单核CPU,不能充分利用CPU
- 可靠性低,一旦代码某个环节崩溃,整个系统都崩溃
三、应用场景
借助Nodejs
的特点和弊端,其应用场景分类如下:
- 善于
I/O
,不善于计算。因为Nodejs是一个单线程,如果计算(同步)太多,则会阻塞这个线程 - 大量并发的I/O,应用程序内部并不需要进行非常复杂的处理
- 与 websocket 配合,开发长连接的实时交互应用程序
具体场景可以表现为如下:
- 第一大类:用户表单收集系统、后台管理系统、实时交互系统、考试系统、联网软件、高并发量的web应用程序
- 第二大类:基于web、canvas等多人联网游戏
- 第三大类:基于web的多人实时聊天客户端、聊天室、图文直播
- 第四大类:单页面浏览器应用程序
- 第五大类:操作数据库、为前端和移动端提供基于
json
的API
其实,Nodejs
能实现几乎一切的应用,只考虑适不适合使用它
2. 说说 Node. js 有哪些全局对象?
一、是什么
在浏览器 JavaScript
中,通常window
是全局对象, 而 Nodejs
中的全局对象是 global
在NodeJS
里,是不可能在最外层定义一个变量,因为所有的用户代码都是当前模块的,只在当前模块里可用,但可以通过exports
对象的使用将其传递给模块外部
所以,在NodeJS
中,用var
声明的变量并不属于全局的变量,只在当前模块生效
像上述的global
全局对象则在全局作用域中,任何全局变量、函数、对象都是该对象的一个属性值
二、有哪些
将全局对象分成两类:
- 真正的全局对象
- 模块级别的全局变量
真正的全局对象
下面给出一些常见的全局对象:
- Class:Buffer
- process
- console
- clearInterval、setInterval
- clearTimeout、setTimeout
- global
Class:Buffer
可以处理二进制以及非Unicode
编码的数据
在Buffer
类实例化中存储了原始数据。Buffer
类似于一个整数数组,在V8堆原始存储空间给它分配了内存
一旦创建了Buffer
实例,则无法改变大小
process
进程对象,提供有关当前进程的信息和控制
包括在执行node
程序进程时,如果需要传递参数,我们想要获取这个参数需要在process
内置对象中
启动进程:
node index.js 参数1 参数2 参数3
index.js文件如下:
process.argv.forEach((val, index) => { console.log(`${index}: ${val}`); });
输出如下:
/usr/local/bin/node /Users/mjr/work/node/process-args.js 参数1 参数2 参数3
除此之外,还包括一些其他信息如版本、操作系统等
console
用来打印stdout
和stderr
最常用的输入内容的方式:console.log
console.log("hello");
清空控制台:console.clear
console.clear
打印函数的调用栈:console.trace
function test() { demo(); } function demo() { foo(); } function foo() { console.trace(); } test();
clearInterval、setInterval
设置定时器与清除定时器
setInterval(callback, delay[, ...args])
callback
每delay
毫秒重复执行一次
clearInterval
则为对应发取消定时器的方法
clearTimeout、setTimeout
设置延时器与清除延时器
setTimeout(callback,delay[,...args])
callback
在delay
毫秒后执行一次
clearTimeout
则为对应取消延时器的方法
global
全局命名空间对象,墙面讲到的process
、console
、setTimeout
等都有放到global
中
console.log(process === global.process) // true
模块级别的全局对象
这些全局对象是模块中的变量,只是每个模块都有,看起来就像全局变量,像在命令交互中是不可以使用,包括:
- __dirname
- __filename
- exports
- module
- require
__dirname
获取当前文件所在的路径,不包括后面的文件名
从 /Users/mjr
运行 node example.js
:
console.log(__dirname); // 打印: /Users/mjr
__filename
获取当前文件所在的路径和文件名称,包括后面的文件名称
从 /Users/mjr
运行 node example.js
:
console.log(__filename); // 打印: /Users/mjr/example.js
exports
module.exports
用于指定一个模块所导出的内容,即可以通过 require()
访问的内容
exports.name = name; exports.age = age; exports.sayHello = sayHello;
module
对当前模块的引用,通过module.exports
用于指定一个模块所导出的内容,即可以通过 require()
访问的内容
require
用于引入模块、 JSON
、或本地文件。 可以从 node_modules
引入模块。
可以使用相对路径引入本地模块或JSON
文件,路径会根据__dirname
定义的目录名或当前工作目录进行处理
3. 说说对 Node 中的 process 的理解?有哪些常用方法?
一、是什么
process
对象是一个全局变量,提供了有关当前 Node.js
进程的信息并对其进行控制,作为一个全局变量
我们都知道,进程计算机系统进行资源分配和调度的基本单位,是操作系统结构的基础,是线程的容器
当我们启动一个js
文件,实际就是开启了一个服务进程,每个进程都拥有自己的独立空间地址、数据栈,像另一个进程无法访问当前进程的变量、数据结构,只有数据通信后,进程之间才可以数据共享
由于JavaScript
是一个单线程语言,所以通过node xxx
启动一个文件后,只有一条主线程
二、属性与方法
关于process
常见的属性有如下:
- process.env:环境变量,例如通过 process.env.NODE_ENV 获取不同环境项目配置信息
- process.nextTick:这个在谈及
EventLoop
时经常为会提到 - process.pid:获取当前进程id
- process.ppid:当前进程对应的父进程
- process.cwd():获取当前进程工作目录,
- process.platform:获取当前进程运行的操作系统平台
- process.uptime():当前进程已运行时间,例如:pm2 守护进程的 uptime 值
- 进程事件: process.on('uncaughtException',cb) 捕获异常信息、 process.on('exit',cb)进程推出监听
- 三个标准流: process.stdout 标准输出、 process.stdin 标准输入、 process.stderr 标准错误输出
- process.title 指定进程名称,有的时候需要给进程指定一个名称
下面再稍微介绍下某些方法的使用:
process.cwd()
返回当前 Node
进程执行的目录
一个Node
模块 A
通过 NPM 发布,项目 B
中使用了模块 A
。在 A
中需要操作 B
项目下的文件时,就可以用 process.cwd()
来获取 B
项目的路径
process.argv
在终端通过 Node 执行命令的时候,通过 process.argv
可以获取传入的命令行参数,返回值是一个数组:
- 0: Node 路径(一般用不到,直接忽略)
- 1: 被执行的 JS 文件路径(一般用不到,直接忽略)
- 2~n: 真实传入命令的参数
所以,我们只要从 process.argv[2]
开始获取就好了
const args = process.argv.slice(2);
process.env
返回一个对象,存储当前环境相关的所有信息,一般很少直接用到。
一般我们会在 process.env
上挂载一些变量标识当前的环境。比如最常见的用 process.env.NODE_ENV
区分 development
和 production
在 vue-cli
的源码中也经常会看到 process.env.VUE_CLI_DEBUG
标识当前是不是 DEBUG
模式
process.nextTick()
process.nextTick() 是微任务
我们知道NodeJs
是基于事件轮询,在这个过程中,同一时间只会处理一件事情
在这种处理模式下,process.nextTick()
就是定义出一个动作,并且让这个动作在下一个事件轮询的时间点上执行
例如下面例子将一个foo
函数在下一个时间点调用
function foo() { console.error('foo'); } process.nextTick(foo); console.error('bar');
输出结果为bar
、foo
虽然下述方式也能实现同样效果:
setTimeout(foo, 0); console.log('bar');
两者区别在于:
- process.nextTick()会在这一次event loop的call stack清空后(下一次event loop开始前)再调用callback
- setTimeout()是并不知道什么时候call stack清空的,所以何时调用callback函数是不确定的
4. 说说对 Node 中的 fs模块的理解? 有哪些常用方法
一、是什么
fs(filesystem),该模块提供本地文件的读写能力,基本上是POSIX
文件操作命令的简单包装
可以说,所有与文件的操作都是通过fs
核心模块实现
导入模块如下:
const fs = require('fs');
这个模块对所有文件系统操作提供异步(不具有sync
后缀)和同步(具有 sync
后缀)两种操作方式,而供开发者选择
二、文件知识
在计算机中有关于文件的知识:
- 权限位 mode
- 标识位 flag
- 文件描述为 fd
权限位 mode
针对文件所有者、文件所属组、其他用户进行权限分配,其中类型又分成读、写和执行,具备权限位4、2、1,不具备权限为0
如在linux
查看文件权限位:
drwxr-xr-x 1 PandaShen 197121 0 Jun 28 14:41 core -rw-r--r-- 1 PandaShen 197121 293 Jun 23 17:44 index.md
在开头前十位中,d
为文件夹,-
为文件,后九位就代表当前用户、用户所属组和其他用户的权限位,按每三位划分,分别代表读(r)、写(w)和执行(x),- 代表没有当前位对应的权限
标识位
标识位代表着对文件的操作方式,如可读、可写、即可读又可写等等,如下表所示:
符号 | 含义 |
r | 读取文件,如果文件不存在则抛出异常。 |
r+ | 读取并写入文件,如果文件不存在则抛出异常。 |
rs | 读取并写入文件,指示操作系统绕开本地文件系统缓存。 |
w | 写入文件,文件不存在会被创建,存在则清空后写入。 |
wx | 写入文件,排它方式打开。 |
w+ | 读取并写入文件,文件不存在则创建文件,存在则清空后写入。 |
wx+ | 和 w+ 类似,排他方式打开。 |
a | 追加写入,文件不存在则创建文件。 |
ax | 与 a 类似,排他方式打开。 |
a+ | 读取并追加写入,不存在则创建。 |
ax+ | 与 a+ 类似,排他方式打开。 |
文件描述为 fd
操作系统会为每个打开的文件分配一个名为文件描述符的数值标识,文件操作使用这些文件描述符来识别与追踪每个特定的文件
Window
系统使用了一个不同但概念类似的机制来追踪资源,为方便用户,NodeJS
抽象了不同操作系统间的差异,为所有打开的文件分配了数值的文件描述符
在 NodeJS
中,每操作一个文件,文件描述符是递增的,文件描述符一般从 3
开始,因为前面有 0
、1
、2
三个比较特殊的描述符,分别代表 process.stdin
(标准输入)、process.stdout
(标准输出)和 process.stderr
(错误输出)
三、方法
下面针对fs
模块常用的方法进行展开:
- 文件读取
- 文件写入
- 文件追加写入
- 文件拷贝
- 创建目录
文件读取
fs.readFileSync
同步读取,参数如下:
- 第一个参数为读取文件的路径或文件描述符
- 第二个参数为 options,默认值为 null,其中有 encoding(编码,默认为 null)和 flag(标识位,默认为 r),也可直接传入 encoding
结果为返回文件的内容
const fs = require("fs"); let buf = fs.readFileSync("1.txt"); let data = fs.readFileSync("1.txt", "utf8"); console.log(buf); // <Buffer 48 65 6c 6c 6f> console.log(data); // Hello
fs.readFile
异步读取方法 readFile
与 readFileSync
的前两个参数相同,最后一个参数为回调函数,函数内有两个参数 err
(错误)和 data
(数据),该方法没有返回值,回调函数在读取文件成功后执行
const fs = require("fs"); fs.readFile("1.txt", "utf8", (err, data) => { if(!err){ console.log(data); // Hello } });
文件写入
writeFileSync
同步写入,有三个参数:
- 第一个参数为写入文件的路径或文件描述符
- 第二个参数为写入的数据,类型为 String 或 Buffer
- 第三个参数为 options,默认值为 null,其中有 encoding(编码,默认为 utf8)、 flag(标识位,默认为 w)和 mode(权限位,默认为 0o666),也可直接传入 encoding
const fs = require("fs"); fs.writeFileSync("2.txt", "Hello world"); let data = fs.readFileSync("2.txt", "utf8"); console.log(data); // Hello world
writeFile
异步写入,writeFile
与 writeFileSync
的前三个参数相同,最后一个参数为回调函数,函数内有一个参数 err
(错误),回调函数在文件写入数据成功后执行
const fs = require("fs"); fs.writeFile("2.txt", "Hello world", err => { if (!err) { fs.readFile("2.txt", "utf8", (err, data) => { console.log(data); // Hello world }); } });
文件追加写入
appendFileSync
参数如下:
- 第一个参数为写入文件的路径或文件描述符
- 第二个参数为写入的数据,类型为 String 或 Buffer
- 第三个参数为 options,默认值为 null,其中有 encoding(编码,默认为 utf8)、 flag(标识位,默认为 a)和 mode(权限位,默认为 0o666),也可直接传入 encoding
const fs = require("fs"); fs.appendFileSync("3.txt", " world"); let data = fs.readFileSync("3.txt", "utf8");
appendFile
异步追加写入方法 appendFile
与 appendFileSync
的前三个参数相同,最后一个参数为回调函数,函数内有一个参数 err
(错误),回调函数在文件追加写入数据成功后执行
const fs = require("fs"); fs.appendFile("3.txt", " world", err => { if (!err) { fs.readFile("3.txt", "utf8", (err, data) => { console.log(data); // Hello world }); } });
文件拷贝
copyFileSync
同步拷贝
const fs = require("fs"); fs.copyFileSync("3.txt", "4.txt"); let data = fs.readFileSync("4.txt", "utf8"); console.log(data); // Hello world
copyFile
异步拷贝
const fs = require("fs"); fs.copyFile("3.txt", "4.txt", () => { fs.readFile("4.txt", "utf8", (err, data) => { console.log(data); // Hello world }); });
创建目录
mkdirSync
同步创建,参数为一个目录的路径,没有返回值,在创建目录的过程中,必须保证传入的路径前面的文件目录都存在,否则会抛出异常
// 假设已经有了 a 文件夹和 a 下的 b 文件夹 fs.mkdirSync("a/b/c")
mkdir
异步创建,第二个参数为回调函数
fs.mkdir("a/b/c", err => { if (!err) console.log("创建成功"); });
5. 说说对 Node 中的 Buffer 的理解?应用场景?
一、是什么
在Node
应用中,需要处理网络协议、操作数据库、处理图片、接收上传文件等,在网络流和文件的操作中,要处理大量二进制数据,而Buffer
就是在内存中开辟一片区域(初次初始化为8KB),用来存放二进制数据
在上述操作中都会存在数据流动,每个数据流动的过程中,都会有一个最小或最大数据量
如果数据到达的速度比进程消耗的速度快,那么少数早到达的数据会处于等待区等候被处理。反之,如果数据到达的速度比进程消耗的数据慢,那么早先到达的数据需要等待一定量的数据到达之后才能被处理
这里的等待区就指的缓冲区(Buffer),它是计算机中的一个小物理单位,通常位于计算机的 RAM
中
简单来讲,Nodejs
不能控制数据传输的速度和到达时间,只能决定何时发送数据,如果还没到发送时间,则将数据放在Buffer
中,即在RAM
中,直至将它们发送完毕
上面讲到了Buffer
是用来存储二进制数据,其的形式可以理解成一个数组,数组中的每一项,都可以保存8位二进制:00000000
,也就是一个字节
例如:
const buffer = Buffer.from("why")
二、使用方法
Buffer
类在全局作用域中,无须require
导入
创建Buffer
的方法有很多种,我们讲讲下面的两种常见的形式:
- Buffer.from()
- 0 Buffer.alloc()
Buffer.from()
const b1 = Buffer.from('10'); const b2 = Buffer.from('10', 'utf8'); const b3 = Buffer.from([10]); const b4 = Buffer.from(b3); console.log(b1, b2, b3, b4); // <Buffer 31 30> <Buffer 31 30> <Buffer 0a> <Buffer 0a>
Buffer.alloc()
const bAlloc1 = Buffer.alloc(10); // 创建一个大小为 10 个字节的缓冲区 const bAlloc2 = Buffer.alloc(10, 1); // 建一个长度为 10 的 Buffer,其中全部填充了值为 `1` 的字节 console.log(bAlloc1); // <Buffer 00 00 00 00 00 00 00 00 00 00> console.log(bAlloc2); // <Buffer 01 01 01 01 01 01 01 01 01 01>
在上面创建buffer
后,则能够toString
的形式进行交互,默认情况下采取utf8
字符编码形式,如下
const buffer = Buffer.from("你好"); console.log(buffer); // <Buffer e4 bd a0 e5 a5 bd> const str = buffer.toString(); console.log(str); // 你好
如果编码与解码不是相同的格式则会出现乱码的情况,如下:
const buffer = Buffer.from("你好","utf-8 "); console.log(buffer); // <Buffer e4 bd a0 e5 a5 bd> const str = buffer.toString("ascii"); console.log(str); // d= e%=
当设定的范围导致字符串被截断的时候,也会存在乱码情况,如下:
const buf = Buffer.from('Node.js 技术栈', 'UTF-8'); console.log(buf) // <Buffer 4e 6f 64 65 2e 6a 73 20 e6 8a 80 e6 9c af e6 a0 88> console.log(buf.length) // 17 console.log(buf.toString('UTF-8', 0, 9)) // Node.js � console.log(buf.toString('UTF-8', 0, 11)) // Node.js 技
所支持的字符集有如下:
- ascii:仅支持 7 位 ASCII 数据,如果设置去掉高位的话,这种编码是非常快的
- utf8:多字节编码的 Unicode 字符,许多网页和其他文档格式都使用 UTF-8
- utf16le:2 或 4 个字节,小字节序编码的 Unicode 字符,支持代理对(U+10000至 U+10FFFF)
- ucs2,utf16le 的别名
- base64:Base64 编码
- latin:一种把 Buffer 编码成一字节编码的字符串的方式
- binary:latin1 的别名,
- hex:将每个字节编码为两个十六进制字符
三、应用场景
Buffer
的应用场景常常与流的概念联系在一起,例如有如下:
- I/O操作
- 加密解密
- zlib.js
I/O操作
通过流的形式,将一个文件的内容读取到另外一个文件
const fs = require('fs'); const inputStream = fs.createReadStream('input.txt'); // 创建可读流 const outputStream = fs.createWriteStream('output.txt'); // 创建可写流 inputStream.pipe(outputStream); // 管道读写
加解密
在一些加解密算法中会遇到使用 Buffer
,例如 crypto.createCipheriv
的第二个参数 key
为 string
或 Buffer
类型
zlib.js
zlib.js
为 Node.js
的核心库之一,其利用了缓冲区(Buffer
)的功能来操作二进制数据流,提供了压缩或解压功能