Node.js 基础笔记

CC不爱吃辣 -
Node.js 基础笔记
Node.js 的应用场景

前端工程化

打包工具(bundle):webpack、vite、esbuild、parcel代码压缩(uglify):uglifyjs语法转换(transpile):bablejs、typescript其他语言加入竞争:esbuild(golang)、parcel(rust)、prismaNode.js 现状:在前端工程化领域仍然难以替代

Web 服务端应用

学习曲线平缓,开发效率较高运行效率接近常见的编译语言社区生态丰富以及工具链成熟(npm、V8 inspector)与前端结合的场景会有优势(服务端渲染ssr)现状:竞争激烈,Node.js 有自己独特的优势

Electron 跨端桌面应用

商业应用:vscode、slack、discord、zoom大型公司内的效率工具现状:大部分场景在选型时,都值得考虑

其他应用场景

BFF(Backends For Frontends)应用:作为中间件自由处理后端接口,使得前后端开发更加分离SSR(Server Side Render)应用:将组件或页面通过服务器生成 HTML 字符串,再发送到浏览器Node.js 运行时结构

Node.js 组成
image.png

V8:JavaScript Runtime,诊断调试工具(inspector)libuv:eventLoop(事件循环),syscall(系统调用)Node.js 运行示例(以 node-fetch 为例):
a2a0845368fb45dba6206411d978bf4d_tplv-k3u1fbpfcp-watermark.png

Node.js 运行时结构特点

异步 I/O

当 Node.js 执行 I/O 操作时,会在响应返回后恢复操作,而不是阻塞线程并占用额外内存等待
屏幕截图 2022-01-26 144554.png

单线程

JavaScript 是单线程的(只有一个主线程),但实际在 Node.js 环境中是:JavaScript 单线程 + libuv 线程池 + V8 任务线程池 + V8 inspector 线程比如读取文件(常见的I/O操作)时,将该任务交给 libuv 线程池去操作,JS 主线程便可以进行其他任务V8 inspector 单独作为一个线程的作用:比如写了一个死循环,常规情况无法再去调试,便可以利用V8 inspector 线程调试该死循环优点:不用考虑多线程状态同步问题,也就不需要锁,同时还能比较高效地利用系统资源缺点:阻塞会产生更多负面影响,解决办法:多进程或多线程

跨平台

Node.js 跨平台 + JS 无需编译环境 + Web 跨平台 + 诊断工具跨平台优点:开发成本低,整体学习成本低编写 HTTP Server

编写一个简单的 server 服务,使得浏览器打开主机的3000端口看到 hello!nodejs!

const http = require('http');
const port = 3000;
const server = http.createServer((req, res) => {
    res.end('hello!nodejs!');
})

server.listen(port, () => {
    console.log('success!', port);
});

把用户请求中的数据转换为 JSON 格式,并在响应中返回给浏览器

const http = require('http');
const port = 3000;

const server = http.createServer((req, res) => {
    const bufs = [];
    req.on('data', (buf) => {
        bufs.push(buf);
    });
    req.on('end', () => {
        const buf = Buffer.concat(bufs).toString('utf-8');
        let msg = 'hello!'
        try {
            const ret = JSON.parse(buf);
            msg = ret.msg;
        }
        catch (err) {
            //这里请求数据不合法暂时不做处理(因为无法发送合法数据),msg的值仍是hello
        }
        // 处理响应 
        const responseJson = {
            msg: `receive : ${msg}`,
        }
        // 响应头设置Content-Type
        res.setHeader('Content-Type', 'application/json');
        res.end(JSON.stringify(responseJson));
    });
});

server.listen(port, () => {
    console.log('success!', port);
});

搭建一个客户端,能给server发送post请求

const http = require('http');
const port = 3000;

const body = JSON.stringify({
    msg: 'Hello from my client',
})

const req = http.request('http://127.0.0.1:3000', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    }
}, (res) => {
    // 拿到响应以后,转换为JSON格式,输出在控制台
    const bufs = [];
    res.on('data', (buf) => {
        bufs.push(buf);
    })
    res.on('end', () => {
        const buf = Buffer.concat(bufs);
        const res = JSON.parse(buf);
        console.log('json.msg is : ', res);
    })
});
// 发送请求
req.end(body);

用 promise + async await 重写这两个例子

const http = require('http');
const port = 3000;

const server = http.createServer(async (req, res) => {
    // recieve body from client
    const msg = await new Promise((resolve, reject) => {
        const bufs = [];
        req.on('data', (buf) => {
            bufs.push(buf);
        });
        req.on('error', (err) => {
            reject(err);
        });
        req.on('end', () => {
            const buf = Buffer.concat(bufs).toString('utf-8');
            let msg = 'hello!nodejs!'
            try {
                const ret = JSON.parse(buf);
                msg = ret.msg;

            }
            catch (err) {
                //
            }
            resolve(msg);
        });
    })

    // response
    const responseJson = {
        msg: `receive : ${msg}`,
    }
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify(responseJson));
});

server.listen(port, () => {
    console.log('success!', port);
});

搭建一个可以读取服务器上静态资源的 server

const http = require('http');
const fs = require('fs');
const url = require('url');
const path = require('path');

// 文件路径
const folderPath = path.resolve(__dirname, './static_server');

const server = http.createServer((req, res) => {
    // expected http://127.0.0.1:3000/index.html?abc=10
    const info = url.parse(req.url);

    // static_server.html
    const filepath = path.resolve(folderPath, './' + info.path);

    // stream api
    const filestream = fs.createReadStream(filepath);

    filestream.pipe(res);
});

const port = 3000;

server.listen(port, () => {
    console.log('listening on : ', port);
})
与高性能、可靠的静态文件服务器相比,还需要缓存、加速、分布式缓存的能力

SSR(server side render)有什么特点

相比传统 HTML 模板引擎避免了重复编写代码相比SPA(single page application)首屏渲染更快,SEO(搜索引擎友好 Search Engine Optimization)友好缺点:通常 qps(每秒查询率 Queries-per-second) 较低,前端代码编写时需要考虑服务端渲染情况

编写 React-SSR HTTP Server

const React = require('react');
const ReactDOMServer = require('react-dom/server');
const http = require('http');

const App = (props) => {
    // 避免编译问题不使用 jsx
    return React.createElement('div', {}, props.children || 'Hello!')
}

const port = 3000;
const server = http.createServer((req, res) => {
    res.end(`
        <!DOCTYPE html>
        <html lang="en">

        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>my application</title>
        </head>

        <body>
            ${ReactDOMServer.renderToString(
              React.createElement(App, {}, 'my_content')
            )}
            <script>
              init......
            </script>
        </body >

        </html >
    `);
})

server.listen(port, () => {
    console.log('success!', port);
});

SSR 难点:

需要处理打包代码需要思考前端代码在服务端运行时的逻辑移除对服务端毫无意义的副作用或重置环境

HTTP Server -Debug

运行js文件时,在文件名之前加上 --inspector 指令,在浏览器输入对应的调试地址,查看 json 信息可以跳转到前端调试界面。以static_file_server为例,输入127.0.0.1:9229/json即可实际开发环境中打断点调试会比较危险,可以打 logpointMemory 面板:可以打一个 snapshot 查看 JavaScript 主线程中的内存占用信息Profile 面板:可以录制一个 profile 查看 CPU 一段时间内的运行情况,可以看到调用了哪些函数。比如应用发生了死循环,可以通过 profile 查看死循环时在运行什么代码也可以通过上述数据观察代码性能,判断函数运行时间是否符合预期

HTTP Server -部署

部署要解决的问题

守护进程:当进程退出时重新拉起多进程:cluster 便携地利用多进程记录进程状态用于诊断

容器环境

通常有健康检查的手段,只需要考虑多核 CPU 利用率即可
特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。

Tags 标签

前端node.js

扩展阅读

加个好友,技术交流

1628738909466805.jpg