nodejs入门知识总结

262 阅读18分钟

1、nodejs的基本原理介绍

参考《深入浏览器工作原理和JS引擎》:juejin.cn/post/705000…

2、node 版本管理

主要有n和nvm两种管理工具。

nvm安装教程:t.csdn.cn/vRmEe

3、nodejs的基本使用

1、运行文件

在终端写入:  node index.js
传递参数:  node index.js coderwhy
运行index.js文件,获取参数的方法
    console.log(process.argv)

2、node中的全局对象

(1)特殊的全局变量,在模块中使用,每个模块都有,但不是全局的量。在命令行交互REPL中不可使用。

例如:process对象、setTimeout()延迟函数、global对象

global对象中都有什么内容?

global对象和window对象的区别?

虽然两者同属全局变量。但是浏览器在执行js代码时,会在顶级范围内使用var

定义一个属性,默认添加到window对象上。Node在执行代码时,var定义的属性只在当前模块下吗,不会放在全局中。如下图所示:

image-20221008102119771

image-20221008144326065

——Buffer

Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。

Buffer 库为 Node.js 带来了一种存储原始数据的方法,可以让 Node.js 处理二进制数据,每当需要在Node.js 中处理I/O操作中移动的数据时,就有可能使用 Buffer 库。

Buffer 提供了以下 API 来创建 Buffer 类:

  • Buffer.alloc(size[, fill[, encoding]]): 返回一个指定大小的 Buffer 实例,如果没有设置 fill,则默认填满 0
  • Buffer.allocUnsafe(size): 返回一个指定大小的 Buffer 实例,但是它不会被初始化,所以它可能包含敏感的数据
  • Buffer.allocUnsafeSlow(size)
  • Buffer.from(array): 返回一个被 array 的值初始化的新的 Buffer 实例(传入的 array 的元素只能是数字,不然就会自动被 0 覆盖)
  • Buffer.from(arrayBuffer[, byteOffset[, length]]): 返回一个新建的与给定的 ArrayBuffer 共享同一内存的 Buffer。
  • Buffer.from(buffer): 复制传入的 Buffer 实例的数据,并返回一个新的 Buffer 实例
  • Buffer.from(string[, encoding]): 返回一个被 string 的值初始化的新的 Buffer 实例

——Stream流

image-20221110170551161

Stream 对象常用的事件有:

  • data - 当有数据可读时触发。
  • end - 没有更多的数据可读时触发。
  • error - 在接收和写入过程中发生错误时触发。
  • finish - 所有数据已被写入到底层系统时触发。

——回调函数

回调函数就是一个被作为参数传递的函数。

参考:t.csdn.cn/P7DUJ

一般用于截获消息、获取系统信息或处理异步事件。比如:箭头函数,原生JS方法,事件绑定,异步。

nodejs的异步编程的直接体现:异步程序依托于回调函数。node中所有的API都支持回调函数。

回调函数是如何实现异步原理的呢?

回调函数和直接在主函数体中调用函数的区别在于:主函数中调用其他函数需要程序员已知具体用到那个函数,而回调函数则是预留函数调用的位置,在需要的时候这里可以放需要的函数。更加灵活,适用于各种api中。

回调地狱:

在ES6中新引入了Promise和Generator、await/async来解决回调地狱。

4、事件循环原理

(1)Node.js的运行机制

(1)V8引擎解析JavaScript脚本。

(2)解析后的代码,调用Node API。

(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。

(4)V8引擎再将结果返回给用户。

(2)介绍

进程和线程t.csdn.cn/yU0Lg

Event Loop(事件循环):www.ruanyifeng.com/blog/2013/1…

事件循环是JavaScript 的执行机制, 实际上是我们编写js和浏览器或者Node之间的一个桥梁。桥梁之间通过回调函数进行沟通。浏览器中的EventLoop是根据HTML5定义来规范的,是js代码和浏览器API(如:setTImeout,AJAX,监听事件等)调用的一个桥梁。Node中是由libuv实现的,是js代码和系统调用(如:file system,network等)的一个桥梁。

image-20221109094200902

image-20221109110516053

(3)执行机制

浏览器的事件循环机制:

任务队列又分为macro-task(宏任务)与micro-task(微任务)。注意:Promise属于同步任务。

宏任务包括:scrip整体代码、setTimeout、setInterval、setImmediate、I/O操作、UI 渲染 微任务包括:process.nextTick、Promise.then、Async/Await(语法糖,Await相当于Promise,Await之后的内容相当与Promise.then)、MutationObserver

总结:

  1. 首先执行宏任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。
  2. 当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务;如果没有,会读取宏任务中排在最前面的任务。
  3. 若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。
  4. 以此类推。

Node的事件循环机制:

image-20221110091412688

从一次事件循环的Tick来说,node的事件循环分为:

image-20221109144514840

5、模块化开发

早期的javaScript语言,没有模块化开发,会出现命名冲突等诸多问题。

后来出现了CommonJS规范,最初为ServerJS提出来是浏览器以外的地方使用后来得到了广泛应用。Node是CommonJS在服务端一个具有/代表性的实现。webpack具备对CommonJS的支持和转换。Node中主要的导出方式 module.exportsexports ,导入方式const bar = require('./bar.js')

module.exports和exports两者之间有什么区别?

moduleexportsNode.js给每个js文件内置的两个对象。可以通过console.log(module)console.log(exports)打印出来。实际上,这两个对象指向同一块内存。这也就是说module.exportsexports是等价的。module.exports = exports = {},此时两者导出值类型存储在栈上,这两种写法是一致的。

//console.log(module);
//输出:Module {..., exports:18 , ...} (注:...代表省略了其他一些属性)
info={
    name:'2233',
    arg:18
}
exports = info
module.exports = exports
info.name = 'syy'
​
console.log(exports);
console.log(module.exports);
// const bar = require('./global.js')
// console.log(bar)

image-20221009115300098

但是,require默认引入的对象本质上是module.exports。这就产生了一个问题,当 module.exportsexports,两者导出指向的不是同一块内存时,exports的内容就会失效。

exports = {name: '萤火虫老阿姨'}
module.exports = {name: '萤火虫叔叔'}
//exports 输出为 {name: '萤火虫老阿姨'}
//module.exports 输出为 {..., exports:{name: '萤火虫叔叔'}, ...} 
​
//此时bar引入输出为{name: '萤火虫叔叔'}

(1)模块引入的细节

require本质是一个函数,采用的是同步引入,在查找引入过程中的查找规则,比如 查找xyz。

  • 如果存在核心模块(path, url)等,可以直接引入即可。
  • 第一步,将xyz当作文件名,在对应目录下查找。按后缀名格式查找,无后缀名按默认文件格式查找顺序。
  • 第二步,没找到对应文件,将xyz当作目录查找目录的index文件,无后缀名按默认文件格式查找顺序。
  • 默认文件格式查找顺序依次为:js文件 、json文件 、node文件。

模块的加载过程:

(2)模块导出方法

其中AMD和CMD等社区规范在早期使用。

CommonJS加载模块是同步的对模块的浅拷贝,多在服务器中使用,所以浏览器中通常不使用CommonJS规范。但在webpack中仍然使用,因为webpack会将代码转换成浏览器可以直接执行的代码。目前,浏览器已经支持ES Modules,另一方面借助webpack对CommonJS的转换。

//导出方式
module.exports = createCommand
module.exports ={
  createProject
}
//导入方式
const createCommand = require('./lib/core/create.js')
const { createProject } = require('./actions.js')

ES Module模块是对模块的引用,采用 export 和import 关键字来实现模块化。ES Modules的规范如下:

//导出方式:直接导出、括号导出、起别名导出、默认导出
export let age = 3
export function sayHello() {
    console.log('say')
    return 'Hello'
}
export{ name,age,sex,sayHello }   // 这里的大括号,并不表示对象
export{
    name as name1,
    age as age1,
}    // 为name起别名为name1
export {name,age,sayHello as default}   //默认导出
export default function () {
    console.log('say')
    return 'Hello'
}   //默认导出引用时可以随意命名//导入方式
 import { name,age,sayHello } from './bar.js'
 import { name as name2,age as age2, } from './bar.js'
 import * as bar from './bar.js'//相结合:引用进来的同时,导出文件。
export {sum as barSum} from './bar.js'

(3)ES的模块加载过程

ES Module加载js文件的过程是编译(解析)时加载的,并且时异步的:

编译时加载,意味着import不能和运行时相关的内容放在一起,比如:当from后面的路径需要动态获取、import放在if、函数、for等语句的代码块中。

//错误示范
if(type == true){
    import { sum } from './bar.js'
}

ES Module是对模块的引用。

commonjs是对模块的浅拷贝。因此import的接口是read-only(只读状态),不能修改其变量值。即不能修改变量的指针指向,但是可以改变变量内部的指针指 向。esmodule赋值会编译报错,但可以对commonjs对重新赋值。

image-20221010162100488

在node中使用ESModule需要js文件改为.mjs的文件类型

一个js文件就是一个模块,但是在node 中默认支持CommonJS的规则。此时运用ES Modules规则时需要改为.mjs的文件类型。

//1、文件类型.mjs
//2、引入
import { foo, bar } from './index.mjs';
//3、终端运行语句
node index.mjs

(4)ES和CS的交互

  • 通常情况下,CommonJS不能加载ES Module。因为CommonJS事同步加载的,处于文件加载完成后代码编译过程,但是ES Module必须经过静态分析,在代码编译阶段就完成了导出导入。
  • 多数情况下,ES Module可以加载CommonJS。在加载过程中module.exports导出的内容作为default导出方式来使用。

6、node中常见的属性、内置模块 、方法

(1)属性

JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。Node.js 中的全局对象是 global。

常见全局变量有:

setTimeout(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。

clearTimeout( t ) 全局函数用于停止一个之前通过 setTimeout() 创建的定时器t。

console 用于提供控制台标准输出。

process 用于描述当前Node.js 进程状态的对象,提供了一个与操作系统的简单接口。

(2)内置模块

参考:www.cnblogs.com/wfblog/p/15…

nodejs内置模块有哪些?

nodejs内置模块指的是除默认提供的语法之外,提供的美容,无需下载,直接引入,引入只写名称即可。

  1. path模块,用于处理文件路径的各类API;
  2. until模块,用于弥补js功能不足,提供常用函数的集合;
  3. fs模块,文件操作系统的API;
  4. events模块,提供了一个“events.EventEmitter”对象;
  5. jade模块,可通过jade来编写html文件。
  6. OS 模块提供基本的系统操作函数。
  7. Net 模块用于底层的网络通信。提供了服务端和客户端的的操作。
  8. DNS 模块 用于解析域名。
  9. Domain 模块简化异步代码的异常处理,可以捕捉处理try catch无法捕捉的。
path模块:

path模块知识拓展:www.runoob.com/nodejs/node…

注意MAC 、Linux、window上的路径有着不一样的规范。为了屏蔽操作系统之间的差异,使用path内置模块对路径进行操作。

const path = require('path')
​
let basePath =  '/User/why'
let fileName = 'aer.txt'
​
const filePath = path.resolve( basePath, fileName)
console.log(filePath)
//打印结果C:\User\why\aer.txt
​

path模块的常用参数:

  • dirname:获取文件的父文件名
  • basename:获取文件名
  • extname:获取文件扩展名

path模块的常用方法:

  • path.resolve()将文件与文件夹拼接。
  • resolve()会判断拼接路径前面是否有/ 或 ../ 或 ./。如果有表示绝对路径,如果没有会和当前执行文件所在的文件夹进行路径的拼接。
util模块:
fs模块:

fs 模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。

异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。

7、npm包管理工具

(1)npm命令

npm常用命令:

npm install <packageName> 安装
npm install <packageName> --force 强制重新安装
npm update <packageName> 更新
npm init -y  用来初始化一个简单的 package.json 文件
npm link  一个包可以链接到另一个项目
npm ls 查看依赖树

npm install 原理:

关于依赖树的了解以及npm进行扁平结构的依赖树层级管理、package-lock.json文件锁定依赖安装结构

参考《2018 年了,你还是只会 npm install 吗?》:juejin.cn/post/684490…

参考《npm 模块安装机制简介》:www.ruanyifeng.com/blog/2016/0…

npm run xxx的时候,发生了什么?

  1. 运行 npm run xxx的时候,npm 会先在当前目录的 node_modules/.bin 查找要执行的程序,如果找到则运行;
  2. 没有找到则从全局的 node_modules/.bin 中查找,npm i -g xxx就是安装到到全局目录;
  3. 如果全局目录还是没找到,那么就从 path 环境变量中查找有没有其他同名的可执行程序。

npm run xxx原理说明:

1、首先会去项目的package.json文件里找scripts 里找对应的xxx,然后执行 xxx的命令。

例如启动vue项目 npm run serve的时候,实际上就是执行了vue-cli-service serve 这条命令。

2、npm install 在 安装这个依赖的时候,就会node_modules/.bin/ 目录中创建 好vue-cli-service 为名的几个可执行文件了。(操作系统实际上没有存在vue-cli-service这一条指令。)

3、npm install 在 安装这个依赖的时候,读取package-lock.json文件就会将一个个文件软链接到 ./node_modules/.bin 目录下,自动node_modules/.bin加入$PATH,这样就可以直接作为命令运行依赖程序和开发依赖程序,不用全局安装了。

4、 bin 目录,保存了依赖目录中所安装的可供调用的命令行包。打开目录文件可以看到文件顶部写着 #!/bin/sh ,表示一个个脚本,在这个脚本中代替执行了vue-cli-service这一条指令。

另外:npm install -g xxx 来安装,那么会将其中的 bin 文件加入到全局,比如 create-react-app 和 vue-cli ,在全局安装后,就可以直接使用如 vue-cli projectName 这样的命令来创建项目了。

yarn、npm 的区别:

参考《字节的一个小问题 npm 和 yarn不一样吗?》:juejin.cn/post/706084…

了解pnpm

(2)项目配置文件package.json

文件内的属性:

name:项目名称 (必填)

version:项目版本号 (必填)

description:项目的基本描述信息

author:作者相关信息 (发布时用到)

license:开源协议 (发布时用到)

private:记录项目是否私有,当为true时,npm不能发布此项目

main:设置程序的入口。1、在前端项目webpack会自动找到程序的入口,main属性和webpack打包的入口并不冲突。2、当作为一个模块打包开源时,将会通过main属性作为入口文件查找文件。比如使用axios模块,const axios= require('axios')

bin: 字段是命令名到本地文件名的映射

scripts:配置脚本命令,

"scripts": {
  "preserve": "npm install kylink_ui@latest",
  "serve": "node index.js "
},
//npm run serve 执行node index.js命令.
//在serve之前预执行preserve的命令语句

dependencies:指定开发环境、生产环境所需要的依赖包。通常是项目实际来发过程用到的模块

devDependencies:指定生成环境不需要的包,如webpack、balel。通过npm install webpack --save-dev 安装

8、脚手架工具开发

(1)设置node命令

    1、新建项目空目录,添加`index.js`文件,在当前目录下执行终端`npm init -y`生成package.json配置文件。在package.json中设置main属性,将index.js设置为入口文件。

image-20221018142707962

2、在index.js文件顶行顶格输入

#!/usr/bin/env nodeconsole.log('18772960133')

#!英文名叫“Sha-bang”,告诉Shell使用什么命令执行该脚本。后面接相对路径/user/bin/env以及脚本类型为node

3、在package.json配置bin属性,用键值对的方式,将相关命令语句绑定文件。然后,通过执行在终端执行npm link,将bin和环境变量进行连接即“iccry”配置在环境变量中。之后在终端运行命令,就可以取代node index.js

image-20221018155459892

image-20221018162115143

(2)命令语句设置

参考:zhuanlan.zhihu.com/p/354347260

使用commander插件来进行命令行工具的开发工作,首先在终端npm install commander完成安装。熟悉Commander的使用,在index.js中设置。( 将有关commander的部分,封装起来,引入index.js,提高代码整洁性。)

官方学习地址:

const program = require('commander')
// command:自定义执行的命令
// oprton:自定义选项,表示可选参数
// description:命令的描述
// action:命令执行之后执行的方法
// parse:解析命令行参数,放在最后
  • .version()方法定义版本,program.version(‘0.0.1’);也可以从packjson.json文件中引入,例如:program.version(require('./package.json').version,'-v,--version')
  • .option()方法来定义选项,同时可以附加选项的简介。每个选项可以定义一个短选项名称(-后面接单个字符)和一个长选项名称(–后面接一个或多个单词),使用逗号、空格或|分隔。例如:program.option('-d, --debug', 'options debug')

(3)仿vue-cli操作

详细操作过程,见iccry_cli文件夹实战记录。

两种执行git clone 的方法:

download-git-repo官网:gitlab.com/flippidippi…

创建子进程:child_process

参考:www.jianshu.com/p/692d9d2e7…

Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能。

每个子进程总是带有三个流对象:child.stdin, child.stdout 和 child.stderr。他们可能会共享父进程的 stdio 流,或者也可以是独立的被导流的流对象。

Node 提供了 child_process 模块来创建子进程,方法有:

  • exec - child_process.exec 使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。
  • spawn - child_process.spawn 使用指定的命令行参数创建新进程。
  • fork - child_process.fork 是 spawn()的特殊形式,用于在子进程中运行的模块,如 fork('./son.js') 相当于 spawn('node', ['./son.js']) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。

拓展:

学习了解VUE_cli脚手架中使用的各种插件。在这个位置中vue-cli/package/@vue/cli文件中的package.json,列举了vue-cli中用到的一些插件。 仿写vue-cli功能,对vue-cli源码进行学习。

设置文件模板EJS插件:

EJS 是一套简单的模板语言,帮你利用普通的 JavaScript 代码生成 HTML 页面。

??? program.dest

发布在npm:

  1. 注册npm账号
  2. 命令行:npm login,输入用户名称、密码、邮箱以及邮箱验证码,进行账号登录。
  3. 调整package.json文件,新增各项内容。命令行:npm publish,发布。

9、服务端——Http模块

(安装nodemon插件,启动服务器时,代码改变后自动更新。)

什么是客户端、什么是服务器?

http 模块Node.js 官方提供的、用来创建 web 服务器的模块。通过 http 模块提供的 http.createServer() 方法,就能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务。

了解http服务器常用方法 以及 request, response请求对象、响应对象常用属性

var http = require('http');
var fs = require('fs');
var url = require('url');
 
// 创建服务器
http.createServer( function (request, response) {  
    //(request, response)请求对象、响应对象
​
   // 解析请求,包括文件名
   var pathname = url.parse(request.url).pathname;
   
   // 输出请求的文件名
   console.log("Request for " + pathname + " received.");
   
   // 从文件系统中读取请求的文件内容
   fs.readFile(pathname.substr(1), function (err, data) {
      if (err) {
         console.log(err);
         response.writeHead(404, {'Content-Type': 'text/html'});
      }else{             
         response.writeHead(200, {'Content-Type': 'text/html'});    
         // 响应文件内容
         response.write(data.toString());        
      }
      //  发送响应数据
      response.end();
   });   
}).listen(8080);
 
// 控制台会输出以下信息
console.log('Server running at http://127.0.0.1:8080/');

浏览器header属性解析: response header:

Date 
表示服务器返回消息的时间,这个时间可用于评估缓存的新鲜度时要用到
​
Cache-Control
服务端告诉客户端对于响应内容的缓存如何控制
​
content-type
表示返回数据的类型
​
content-Encoding
服务端表明自己使用了什么压缩方法压缩响应的内容。
例如:Content-Encoding:gzip
​
access-control-allow-origin
所请求的服务器,允许来自哪些域名的访问
​
access-control-allow-methods
允许使用的访问方法
​