面试题 - 前端 - Node.js

nodejs 创建静态服务器

要使用 HTTP 服务器和客户端,则必须 require('http')

1
2
3
4
5
6
7
8
9
10
11
12
13
const http = require('http');

// 创建本地服务器来从其接收数据
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(
JSON.stringify({
data: 'Hello World!',
}),
);
});

server.listen(8000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const http = require('http');

// 创建本地服务器来从其接收数据
const server = http.createServer();

// 监听请求事件
server.on('request', (request, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(
JSON.stringify({
data: 'Hello World!',
}),
);
});

server.listen(8000);

commonJS 规范 vs ES6 模块化规范

1
2
import { 某个几个接口 } from 'antd'; //webpack (tree shaking 摇树优化)
var myview = require('antd'); //导入整个接口

socket 通信 聊天

WebSocket 并不是全新的协议,而是利用了 HTTP 协议来建立连接。我们来看看 WebSocket 连接是如何创建的。

首先,WebSocket 连接必须由浏览器发起,因为请求协议是一个标准的 HTTP 请求,格式如下:

1
2
3
4
5
6
7
GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13

该请求和普通的 HTTP 请求有几点不同:

  1. GET 请求的地址不是类似/path/,而是以ws://开头的地址;
  2. 请求头Upgrade: websocketConnection: Upgrade表示这个连接将要被转换为 WebSocket 连接;
  3. Sec-WebSocket-Key是用于标识这个连接,并非用于加密数据;
  4. Sec-WebSocket-Version指定了 WebSocket 的协议版本。

随后,服务器如果接受该请求,就会返回如下响应:

1
2
3
4
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

该响应代码101表示本次连接的 HTTP 协议即将被更改,更改后的协议就是Upgrade: websocket指定的 WebSocket 协议。

版本号和子协议规定了双方能理解的数据格式,以及是否支持压缩等等。如果仅使用 WebSocket 的 API,就不需要关心这些。

现在,一个 WebSocket 连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。消息有两种,一种是文本,一种是二进制数据。通常,我们可以发送 JSON 格式的文本,这样,在浏览器处理起来就十分容易。

为什么 WebSocket 连接可以实现全双工通信而 HTTP 连接不行呢?实际上 HTTP 协议是建立在 TCP 协议之上的,TCP 协议本身就实现了全双工通信,但是 HTTP 协议的请求-应答机制限制了全双工通信。WebSocket 连接建立以后,其实只是简单规定了一下:接下来,咱们通信就不使用 HTTP 协议了,直接互相发数据吧。

安全的 WebSocket 连接机制和 HTTPS 类似。首先,浏览器用wss://xxx创建 WebSocket 连接时,会先通过 HTTPS 创建安全的连接,然后,该 HTTPS 连接升级为 WebSocket 连接,底层通信走的仍然是安全的 SSL/TLS 协议。

websocket全双工, 双向通信 onopen onmessage onclose onerror

服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const WebSocket = require('ws');
WebSocketServer = WebSocket.WebSocketServer;
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.on('message', function message(data, isBinary) {
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data, { binary: isBinary });
}
});
});

ws.send('欢迎加入聊天室');
});

客户端:

1
2
3
4
5
6
7
var ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('open');
};
ws.onmessage = (evt) => {
console.log(evt.data);
};

JSON Web Token (JWT)

我为什么要保存这可恶的 session 呢, 只让每个客户端去保存该多好?

当然, 如果一个人的 token 被别人偷走了, 那我也没办法, 我也会认为小偷就是合法用户, 这其实和一个人的 session id 被别人偷走是一样的。

这样一来, 我就不保存 session id 了, 我只是生成 token , 然后验证 token , 我用我的 CPU 计算时间获取了我的 session 存储空间 !

解除了 session id 这个负担, 可以说是无事一身轻, 我的机器集群现在可以轻松地做水平扩展, 用户访问量增大, 直接加机器就行。 这种无状态的感觉实在是太好了!

缺点:

  1. 占带宽,正常情况下要比 session_id 更大,需要消耗更多流量,挤占更多带宽,假如你的网站每月有 10 万次的浏览器,就意味着要多开销几十兆的流量。听起来并不多,但日积月累也是不小一笔开销。实际上,许多人会在 JWT 中存储的信息会更多;
  2. 无法在服务端注销,那么久很难解决劫持问题;
  3. 性能问题,JWT 的卖点之一就是加密签名,由于这个特性,接收方得以验证 JWT 是否有效且被信任。对于有着严格性能要求的 Web 应用,这并不理想,尤其对于单线程环境。

注意:

CSRF 攻击的原因是浏览器会自动带上 cookie,而不会带上 token;
以 CSRF 攻击为例:
cookie:用户点击了链接,cookie 未失效,导致发起请求后后端以为是用户正常操作,于是进行扣款操作;
token:用户点击链接,由于浏览器不会自动带上 token,所以即使发了请求,后端的 token 验证不会通过,所以不会进行扣款操作;

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//jsonwebtoken 封装
const jsonwebtoken = require('jsonwebtoken');
const secret = 'kerwin';
const JWT = {
generate(value, exprires) {
return jsonwebtoken.sign(value, secret, { expiresIn: exprires });
},
verify(token) {
try {
return jsonwebtoken.verify(token, secret);
} catch (e) {
return false;
}
},
};

module.exports = JWT;

token 存储在 localStorage 里,当过期时过期的 token 怎么处理?

当前端进行页面跳转或者需要鉴权的操作时,会发送请求到后台,而 token 会跟随请求头一起发送,后台通过请求头接收到 token 时会进行判断,若是过期了,应该返回一个 401 的状态码给前端,前端接收到以后,应该重定向到登录页要求用户重新登陆。axios 拦截器。

如何使用原生 Node.js 操做 cookie?

  • 获取 cookie: req.headers.cookie
  • 设置 cookie: res.writeHead(200, { ‘Set-Cookie’: ‘myCookie=test’, ‘Content-Type’: ‘text/plain’ })

nextTick 和 setImmediate 的区别是什么?

nextTick 和 setImmediate 都是延迟加载。但是 nextTick 是放在当前队列的最后一个执行,setImmediate 是在下一个队列的队首执行

koa 和 express 的区别

  • 最大的区别在于语法,experss 的异步采用的是回调函数的形式,而 koa1 支持 generator + yeild,koa2 支持 await/async,无疑更加优雅。
  • 中间件的区别,koa 采用洋葱模型,进去顺序执行,出去反向执行,支持 context 传递数据 express 本身无洋葱模型,需要引入插件,不支持 context express 的中间件中执行异步函数,执行顺序不会按照洋葱模型,异步的执行结果有可能被放到最后,response 之前。 这是由于,其中间件执行机制,递归回调中没有等待中间件中的异步函数执行完毕,就是没有 await 中间件异步函数
  • 集成度区别 express 内置了很多中间件,集成度高,使用省心, koa 轻量简洁,容易定制

koa 中间件的实现原理

  1. 每个中间件默认接受两个参数,第一个参数是 Context 对象,第二个参数是 next 函数。只要调用 next 函数,就可以把执行权转交给下一个中间件。
  2. 如果中间件内部没有调用 next 函数,那么执行权就不会传递下去。
  3. 多个中间件会形成一个栈结构,以“先进后出”的顺序执行。整个过程就像,先是入栈,然后出栈的操作。

图片上传到服务器的过程

Multer 是一个 node.js 中间件,用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件。

注意: Multer 不会处理任何非 multipart/form-data 类型的表单数据。

npm install --save multer

1
2
3
4
5
6
7
8
9
10
11
12
13
//前后端分离-前端

const params = new FormData();
params.append('kerwinfile', file.file);
params.append('username', this.username);
const config = {
headers: {
'Content-Type': 'multipart/form-data',
},
};
http.post('/api/upload', params, config).then((res) => {
this.imgpath = 'http://localhost:3000' + res.data;
});

Multer 会添加一个 body 对象 以及 filefiles 对象 到 express 的 request 对象中。 body 对象包含表单的文本域信息,filefiles 对象包含对象表单上传的文件信息。

1
2
3
4
//前后端分离-后端
router.post('/upload', upload.single('kerwinfile'), function (req, res, next) {
console.log(req.file);
});

什么是服务端渲染,服务端渲染的优点?

  • 服务端渲染:页面渲染过程是在服务端完成,最终的 HTML 字符串,直接通过请求发送给客户端。
  • 服务器端渲染的优势就是利于 SEO 优化,首屏加载快,因为客户端接收到的是完整的 HTML 页面。

Node.js 优缺点以及适用场景

优点:

  • Node.js 采用事件驱动、异步编程,为网络服务而设计。简单易学,可以很快上手做后端设计。
  • Node.js 非阻塞模式的 IO 处理给 Node.js 带来在相对低系统资源耗用下的高性能与出众的负载能力,非常适合用作依赖其它 IO 资源的中间层服务。
  • Node.js 轻量高效,可以认为是数据密集型分布式部署环境下的实时应用系统的完美解决方案。

缺点:

  • 单线程,可靠性低,一旦这个进程崩掉,那么整个 web 服务就崩掉了。
  • 开源组件库质量参差不齐,更新快,向下不兼容
  • 不适合做企业级应用开发,特别是复杂业务逻辑的,代码不好维护,事务支持不是很好。

适用场景:

  • 大量 Ajax 请求的应用,例如个性化应用,每个用户看到的页面都不一样,需要在页面加载的时候发起 Ajax 请求,NodeJS 能响应大量的并发请求。
  • 实时 :如在线聊天,实时通知推送等等
  • 工具类应用:海量的工具,小到前端压缩部署,大到桌面图形界面应用程序
  • 总而言之,NodeJS 适合运用在高并发、I/O 密集、少量业务逻辑的场景。