node js, so cool

459 阅读18分钟

为什么要写这篇博客呢, 因为自己学习node的道路也蛮坎坷的, 因为官方文档虽然比较全, 但是在某些情况下真实的不是给人看的, 而且巨繁琐, 所以我结合自己在企业级的开发(当然在企业中一般会使用node框架express或者koa或者使用egg来定制更加强大的上层框架)的一些经验, 来总结了在node的基础中你必须要知道的一些东西, 希望能够帮助你省掉一些学习node的时间成本

本栏博文大致会在这段时间和大家讨论如下几个问题:

  1. node是什么? node能给我们带来什么?
  2. node中的global全局对象(新能力)
  3. node中的模块化细节(新能力)
  4. node中的基本内置模块(新能力)
  5. node中的生命周期(新能力)
  6. node框架express, koa初探(框架)

node是什么? node能够给我们带来什么?

在回答这个问题之前, 首先我们先来举一个我们日常中可以看到的例子哈, 不知道大家有没有看过丧尸系列的电影(比如生化危机之类的?)

其实我们会发现一点: 因为宿主天赋系统的不一样, 病毒在不同的宿主身上表现的效果不一致(病毒到了人体, 依靠人体我们可以理解为病毒变得可以直立行走, 而在狗身上 则只能四肢行走), 不同的宿主也给病毒提供了不同的能力, 比如人作为宿主, 就没法跑得快, 但是狗因为天生就跑得快, 所以当跟病毒结合以后, 依然跑的快

说了这个, 我们再回到这个问题, node是什么?node能够给我们带来什么?

node就是JS语言的一个宿主环境, 而在这之前我们知道浏览器已经是JS语言的宿主环境之一了, 浏览器给JS提供了一些新的能力(比如JS操作dom), 我们称之为WebAPI, 而node也给JS提供了一些新的能力(比如读取文件, 关闭计算机), 我们称之为NodeAPI, NodeAPI提供了完整的计算机控制能力, 在node环境的JS几乎可以通过node提供的api, 实现对整个操作系统的控制

说了这么多, 我们过去在浏览器运行JS的时候, 首先就是下载一个浏览器, 然后用vscode在浏览器中打开, 同理, 如果我们想在node环境执行JS, 我们也需要下载node

node的下载与安装

node官网: nodejs.org/zh-cn/

下载好了以后傻瓜式安装这里就不多说了, node环境下载完毕以后跟浏览器不同, node环境只能在命令行中打开, 不像浏览器可以直接用视图形式打开

安装完毕以后, 直接敲node命令应该就可以打开node环境了, 在node环境中我们可以直接敲js代码(类似于浏览器的console的平台), 他会执行js代码, 并且我们可以直接使用node给我们提供的api进行一些骚操作, 但是大多数情况下, 我们并不会在命令行写代码(就像我们并不会在Chromeconsole写代码一样), 过去我们会直接书写好相应的js文件, 在浏览器中打开, 而现在, 我们也会书写相应的js文件, 但是是在node中打开, 我们可以来初步体验一下

// 建立一个test文件夹, 在其中新建一个index.js, 随便书写一点js代码
let username = "Leronardo";
console.log(username);

然后我们进入到test目录, 使用node index.js命令, 可以直接将该js文件置于node环境中执行(可以立即在命令行看到结果), 就好像我们使用open in browser就可以将js文件置于浏览器环境执行一样

我们上面说到, 作为和浏览器截然不同的宿主环境, node给JS赋予了一些新的能力, 在下面我们会将node给JS赋予的常用新能力一一拿出来跟大伙聊一聊

我觉得有些点还是有必要提一下, 我们上面说到T病毒到了人和狗身上, 因为人和狗体能的不一样, 所以导致T病毒拥有了新的能力, 但是T病毒本身的能力并没有变化过(让宿主智商为0), 同理无论是在浏览器还是NODE中, JS的基本语法是不会变的, 例如你写循环依旧是for, 声明变量该怎么声明还是怎么声明, 而真正变化的, 是宿主重新赋予给JS的不同新能力, 所以我们没有必要再去了解JS的基本能力, 因为你都看到node了还不会js基础的话那你这不扯淡嘛, 所以我们只需要去学习node带给他的新能力就好了

node中的global全局对象

我们在做浏览器端js开发的时候, 我们都知道有个全局window对象, 而这个window对象是浏览器宿主环境赋予给JS的能力 而在node中,node不再提供window对象, 但是赋予了JS一个新的能力, 操作global对象

// test/index.js
console.log("global", global);

随后node index.js执行该文件, 我们会看到输出如下:

我们可以来唠一唠这个global对象里大概都有些啥玩意

  • global

    其实我们为什么能够直接在编辑器或者控制台访问global, 或者浏览器端我们为什么可以直接访问window, 是因为他都做了下面这步操作

    global.global = global; // 这样做就是方便你后续对他进行访问
    
  • clearInterval / clearTimeout /setInterval / setTimeout

    作用同浏览器环境中的clearInterval / clearTimeout / setInterval / setTimeout

    // 但是我们需要注意一点: 在node环境中, setInterval和setTimeout的返回值都是一个对象
    // 而绝非跟浏览器一样的是个数字
    console.log(setTimeout(() => {},0); 
    // 输出Timeout {
       // _idleTimeout: 1,
       // _idlePrev: [TimersList],
       // _idleNext: [TimersList],
       // _idleStart: 42,
       // _onTimeout: [Function (anonymous)],
       // _timerArgs: undefined,
       // _repeat: null,
       // _destroyed: false,
       // [Symbol(refed)]: true,
       // [Symbol(kHasPrimitive)]: false,
       // [Symbol(asyncId)]: 5,
       // [Symbol(triggerId)]: 1
       // } 
    // 这是一个时间对象, 平时我们用到的概率也比较低, 所以这里就提一嘴
    
  • queueMicrotask

    node生命周期有关, 后面聊

  • setImmediate / clearImmediate

    setImmediate你可以暂时把他理解为浏览器中的setTimeout(fn, 0), 但是由于生命周期不一致, node中对于setImmediate有特殊的处理, 导致他的表现形式在某些情况下可能和浏览器有所差异, 后续会讲解

这就是node赋予给jsnode环境中的全局对象, 有兴趣的同学可以自己在文件中尝试使用一下这些函数

setTimeout(() => {
    console.log("setTimeout");
}, 0)

// setInterval(() => {
//     console.log("setInterval");
// }, 0)

setImmediate(() => {
    console.log("setImmediate");
})

// 如果你要说setTimeout 0 和setImmediate谁更快的话,  这个答案是不固定的 
// 谁都有可能先输出, 但是他们两个在生命周期里进的队列是不一样的, 稍后我就会聊到

其他的非global属性的全局常用方法

上面我们看到的是global全局对象下存在的属性和方法, node在此之外还给我们赋予了一些其他的全局能力, 只是他们不在于global全局对象下

  • Buffer

    Buffer可以帮助我们将字符串转换为一个buffer, buffer是什么我就不多说了哈

    const buffer = Buffer.from("helloWorld");
    console.log("buffer", buffer, buffer.toString()); // 输出: buffer <Buffer 68 65 6c 6c 6f 57 6f 72 6c 64> helloWorld
    
  • process

    process能够给我们提供一些关于操作系统进程方面的api

    // 1. process.cwd: 输出node的工作目录, 绝对路径
    // 这哥们的作用就是你在哪里敲的node index这个命令, 他能给你找出来
    // 假设我在test中执行node src/index.js, 他就会输出test
    // 如果我在test/src中执行node index.js, 他就会输出test/src
    console.log(process.cwd());
    
    
    // 2. process.exit: 退出当前node进程
    // 这哥们是干啥的呢, 就是你敲了node index.js以后, 你的node任务实际上就在跑了, 
    // 中间就在执行代码了, 这哥们就是帮你在你想要的时候直接结束任务
    // 就好像你在打游戏, 你妈不想让你打了, 直接给你把电源给你扯了
    console.log("i am starting");
    setTimeout(() => {
        console.log("我执行啦");
    }, 5000)
    // 我们知道按照正常来说5s以后会输出”我执行啦“这句话, 但是不好意思因为有
    // 下面这行代码(下面这行代码作为同步代码会先执行这我就不解释了)
    // 因为有下面这行代码他就会立即把这整个node进程给你退掉, 你进程都没了
    // 你还指望能输出么? 不可能, 所以作用就在这, 比如黑客来了, 
    
    process.exit();  
    
    // 很多计算机会自动关机以防止被进攻, 这个就大概是这么个意思
    // if( 黑客攻击 ) {
    //    process.exit();
    // }
    
    // 3. process.argv: 这个是用来获取命令行所有参数的
    // 我们知道我们之前使用webpack的时候, 经常会传递很多参数, 比如npm install --save
    // 这个--save就是传递的参数, 我们可以通过process.argv来获取这些参数
    
    // 假设我命令行敲了 node index -a -b -c
    console.log(process.argv); // 输出一个数组 数组内容: 
    // [ 你的nodebin所在目录, 该js文件所在目录, '-a', '-b', '-c' ]
    // 数组前两项是固定的, 你的nodebin所在目录和当前js文件所在目录, 后续的就是我们传递的参数了
    
    // 4. process.kill(processid): 用来杀死一个进程
    // 比如我们知道qq就是一个进程, 如果你想你的这个node程序一启动就把qq给杀死, 那你就可以找到qq的进程id
    // 然后直接调用该方法, 你就会发现你一执行你这个文件, 你的qq就被退出了
    process.kill(78488); // 我电脑下qq的进程id是78488, 所以当我敲下node index.js且代码执行到这一行
    // 的时候 qq就退了
    
    // 5. process.env: 获取环境变量
    // 这个就是我们通常在其他程序里给process.env设置了值的话这里就可以捕捉到
    // 然后通过process.env就可以获取到
    console.log(process.env); // 会输出一个对象
    

node中的模块化细节(新能力)

在这一块我要着重写两个东西, 一个就是node(commonjs)的模块化原理, 另一个就是node的模块化查找细节

node(commonjs)的模块化原理

其实在全局中, 我们还有几个常用的属性和方法, 我没有说到, 为什么我在之前不说呢, 因为这几个哥们是比较特殊的, 特涉及到我要说的模块化原理, 所以拎到这块来说了

  1. module
  2. exports
  3. require
  4. __dirname
  5. __filename
  • module

    记录了当前模块的一些信息

    console.log("module", module);
    
    // 输出结果如下
    Module {
    id: '.', // 当前模块id
    path: '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/src', // 当前模块的绝对路径
    exports: {},
    parent: null, // 当前模块的父模块
    filename: '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/src/index.js', // 输出这行代码的文件
    loaded: false, // 是否加载完毕
    children: [ // 他引用的子模块
        Module {
        id: '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/node_modules/ab/main.js',
        path: '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/node_modules/ab',
        exports: {}, // 帮助我们导出模块的对象
        parent: [Circular *1],
        filename: '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/node_modules/ab/main.js',
        loaded: true,
        children: [],
        paths: [Array]
        }
    ],
    paths: [ // 查找子模块的查找顺序
        '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/src/node_modules',
        '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/node_modules',
        '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/node_modules',
        '/Users/goldpower/Desktop/PERSONAL/LearnNotes/node_modules',
        '/Users/goldpower/Desktop/PERSONAL/node_modules',
        '/Users/goldpower/Desktop/node_modules',
        '/Users/goldpower/node_modules',
        '/Users/node_modules',
        '/node_modules'
    ]
    }
    
  • exports

    其实就是module.exports

  • require

    我们用来导入一个模块所使用的函数, 类似于浏览器端的import

    console.log("reqiure", require);
    
    // 输出结果如下
    require [Function: require] {
    resolve: [Function: resolve] { paths: [Function: paths] }, // require提供的resolve函数, 可以用来生成绝对路径
    main: Module {
        id: '.',
        path: '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/src',
        exports: {},
        parent: null,
        filename: '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/src/index.js',
        loaded: false,
        children: [],
        paths: [
        '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/src/node_modules',
        '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/node_modules',
        '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/node_modules',
        '/Users/goldpower/Desktop/PERSONAL/LearnNotes/node_modules',
        '/Users/goldpower/Desktop/PERSONAL/node_modules',
        '/Users/goldpower/Desktop/node_modules',
        '/Users/goldpower/node_modules',
        '/Users/node_modules',
        '/node_modules'
        ]
    },
    extensions: [Object: null prototype] { // require自动拼接路径的规则
        '.js': [Function (anonymous)],
        '.json': [Function (anonymous)],
        '.node': [Function (anonymous)]
    },
    cache: [Object: null prototype] {  // 已经加载过得模块缓存
        '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/src/index.js': Module {
        id: '.',
        path: '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/src',
        exports: {},
        parent: null,
        filename: '/Users/goldpower/Desktop/PERSONAL/LearnNotes/nodejs/3. node的模块化细节/src/index.js',
        loaded: false,
        children: [],
        paths: [Array]
        }
    }
    }
    
  • __dirname

    获取当前模块所在的目录, 而不是node执行目录

    // 假设我是在src目录下, 那么下面会输出绝对路径的src目录 盘符/xxx/xxx/xxx/src
    console.log("__dirname", __dirname); // 始终会返回当前模块(js文件)所在的目录
    
  • ** __filename**

    获取当前模块所在的文件路径

    // 假设我是在src目录下, 那么下面会输出绝对路径的当前文件路径 盘符/xxx/xxx/xxx/src/index.js
    console.log("__filename", __filename); 
    

    OK, 上面的只是帮助我们暂时了解一下这几个属性, 接下来我要讲的就是这几个属性是怎么来的

仔细看我接下来写的话

当我们执行一个模块, 或者使用require函数导入一个模块的时候, node会把这个模块放入一个函数环境中

我们来看看当我们调用require函数的时候, require函数都做了哪些事情

function require( moduleId ) {
    // 1. 将moduleId转换为绝对路径
    // 2. 检查require.cache中有没有存过该绝对路径, 如果有的话直接返回, 没有的话走下面的流程
    // 3. 通过绝对路径读取该文件内容, 并将内容放置到一个函数中, 该函数接受几个参数
    function temp( module, exports, require, __dirname, __filename ) {
        // 文件内容
        ...
        // 
    }

    // 4. 创建调用temp函数所需要的参数
    const module = {}; // new 一个内部的Module对象
    module.exports = {};
    // 5. 直接调用temp函数
    temp.call(module.exports, module, module.exports, require, 绝对路径的文件目录, 绝对路径的文件名);

    // 返回 module.exports
    return module.exports;
}

通过上面我们可以明白的一点就是, 上面说到的5个属性全是node自己生成然后作为函数的参数传递给我们的, 那么有的同学可能就会说了, 那你空口白牙说node会将模块放入到函数环境中, 你怎么验证?

当然我这里跟你说源码不太可能, 但是我可以给你反向验证一下

// 我们在index.js文件里啥都不干直接打印arguments
// arguments是不是只有函数环境才有的东西
console.log("arguments", arguments); // 你可以执行node index.js看看你自己的输出

输出应该如下

我这里可能讲的比较草率, 是因为我很久之前已经写过这块的博客了, 没看明白或者有需要的同学可以去我下面这篇博客里详细查看:

node模块化细节博客: blog.csdn.net/weixin_4423…

node中的基本内置模块(新能力)

上面把全局对象, 模块化细节都聊过了, 我相信同学也感觉到了node确实好像有点东西, 拥有很多超过浏览器宿主的能力, 接下来我打算来说说node给我们提供的一些常用内置模块

啥叫内置模块, 我们知道模块就是我们写好的一个又一个的js文件, 可以供其他地方引入(node中使用require引入模块, module.exports或者exports导出模块), 而内置模块其实就是node在内部已经给我们写好的模块, 这些模块可以帮助我们干很多事情, 这里我打算聊的模块大概如下:

  • os: 跟系统操作有关的模块
  • path: 跟路径操作有关的模块
  • url: 跟解析url相关的模块
  • file: 跟文件处理相关的模块
  • net: 跟网络相关的模块
  • http: 跟网络请求相关的模块

os

  • EOL: end of line, 获得当前系统下的换行符, windows是\r\n, linux mac是\n
  • arch: 函数, 获取cpu的架构名, 目前cpu的架构名主要集中在x32和x64
  • cpus: 函数, 获取cpu的内核信息
  • freeman: 函数, 得到当前还有多少空闲的内存
  • homedir: 函数, 获取用户目录
  • hostname: 函数, 获取主机名
  • tmpdir: 函数, 获取内存临时目录
const os = require("os"); // 导入os模块


// 1. os.EOL: 我们做服务端开发, 比较重要的就是容错和兼容,
//  有的时候我们需要用到换行符, 但是win下面的换行符mac下的不一致, 
// 这就类似于一个变量, 我相信你可以理解
// 这个时候我们就会用到os.EOL, 他会获取所在平台下的换行符然后
console.log(os.EOL); // 会直接输出一个换行符

// 2. os.arch(): 因为我们知道x32和x64的电脑始终是有点区别的
// 所以我们可以通过这个os.arch来判断所在主机的架构名, 然后分别
// 做相应的处理
console.log(os.arch()); // 因为我的电脑是x64的, 所以输出x64

// 3. os.cpus: 获取cpu的内核信息,假设你是8核的电脑, 那这就是给你输出一个
// 8个对象的数组, 数组的每一项都是每一核的信息
console.log(os.cpus(), os.cpus().length);

// 4. 这个就是用来获取你还有多少的闲置内存了, 比如你16G的内存, 用了9G, 那他这里就输出5G
console.log(os.freemem());

// 5. 这个是获取你的用户目录, Windows就是那个什么C://UserS什么的不太记得请了
// 因为有时候你读文件可能要从用户目录下去找更方便一点
console.log(os.homedir()); // /Users/goldpower

// 6. 这个就是输出你的用户名, 比如你是Aminstrator, 那就是输出这个
console.log(os.hostname()); // fuhongdeMBP

// 7. 获取临时内存目录, 这个其实也不常用, 但是就是比如你声明了变量
// 他的临时存储目录就在这, 根据需求去获取就好了
console.log(os.tmpdir()); 

path

提供一些跟路径相关的操作, 注意:path这个模块说白了就是帮你进行字符串组装和进行字符串处理提取的, 他跟你真正文件路径关系不大, 好多同学总觉得path的有些方法可以传递不存在的路径为什么不报错, 实际上他压根不在乎你这个路径有没有, 他就是单纯的字符串处理, 就这么简单

  • basename: 给他一个路径, 获取文件的basename
    console.log(path.basename("/src/index.js")); // 输出index.js
    // 第二个参数是帮你将filename裁掉一些尾部值
    console.log(path.basename("./src/index.html", ".html"); // 会帮你把.html切掉, 直接输出index
    
  • sep: 获取当前操作系统下的分隔符
    console.log(path.sep); // 获取当前系统的单路径分隔符
    
  • delimiter: 获取当前操作系统的多路径分隔符
    // 比如在windows下面, 我们配置环境变量的时候, 多个环境变量在一块我们通常用;分割, 而在mac下则是:
    console.log(path.delimiter);
    
  • dirname: 给他一个路径, 他会解析出这个路径的目录
    console.log(path.dirname("./src/assets/index.css")); // 输出./src/assets
    
  • extname: 获取一个路径的后缀名
    console.log(path.dirname("./src/assets/index.css")); // 输出.css
    
  • join: 将多段路径进行组合
    console.log(path.join("../src", "assets", "index.css")); // 输出../src/assets/index.css
    // 它还可以支持我们返回上级目录一些骚操作
    console.log(path.join("src", "../public", "index.html")); // 输出public/index.html
    
  • normalize: 帮你将一些不规则的路径规范化
    console.log(path.normalize("..//src/assets/../index.html")); // ../src/index.html
    
  • relative: 拿到参数b路径相对于参数a的相对路径
    console.log(path.relative("./src/index.html", "./src/assets/index.css")); // ./assets/index.css
    
  • resolve: 生成一个绝对路径
    // 如果我们参数填写了相对路径, 且并没有填写绝对路径, 则resolve会自动使用process.cwd()作为绝对路径
    console.log(path.resolve("./src/index.css")); 
    // 但是如果我们参数中只要出现了绝对路径, 那么resolve就会使用参数的绝对路径而放弃使用process.cwd()
    console.log(path.resolve("./src/assets", "/abc")); // 直接输出/abc, 他会以第一个遇到的绝对路径开始拼后面的东西, 如果一直没有遇到, 那就将所有的路径拼起来使用process.cwd()作为绝对路径开头
    // 所以我们就可以使用__dirname来拼接路径了
    

url

给他一个url, 他帮你分析出url的各个部分组成, 跟path一样, url他也是当做字符串处理的, 同学一定要搞明白这个

const url = require("url");

const urlIns = new url.URL("https://blog.csdn.net/qq_29055201/article/details/90690338?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control");

console.log(urlIns); // 输出对象如下

URL {
  href: 'https://blog.csdn.net/qq_29055201/article/details/90690338?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control', // full地址
  origin: 'https://blog.csdn.net', // 源
  protocol: 'https:', // 协议
  username: '', // 用户名
  password: '', // 密码
  host: 'blog.csdn.net', // 主机
  hostname: 'blog.csdn.net', // 主机名
  port: '', // 端口
  pathname: '/qq_29055201/article/details/90690338', // 路径
  search: '?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control',
  searchParams: URLSearchParams {
    'utm_medium' => 'distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control',
    'depth_1-utm_source' => 'distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control' },
  hash: ''
}

console.log(urlIns.searchParams.has("utm_medium")); // true

// 如果你不想调用构造函数, 可以使用URL.parse(url)帮你实现一样的功能, 不过该功能要被移除了, 不建议使用

// 同时如果我们需要将一个路径对象转换成路径的话, 可以调用url.format方法
const obj = {
  href: 'https://blog.csdn.net/qq_29055201/article/details/90690338?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control', // full地址
  origin: 'https://blog.csdn.net', // 源
  protocol: 'https:', // 协议
  username: '', // 用户名
  password: '', // 密码
  host: 'blog.csdn.net', // 主机
  hostname: 'blog.csdn.net', // 主机名
  port: '', // 端口
  pathname: '/qq_29055201/article/details/90690338', // 路径
  search: '?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control',
  hash: ''
}

console.log(url.format(obj)); // 输出https://blog.csdn.net/qq_29055201/article/details/90690338?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control

fs

loading

net

loading

http

loading

node中的生命周期(新能力)

loading

node框架express, koa初探(框架)

loading