学习笔记:NodeJS(尚硅谷)

介绍

  • 本文主要记录在学习尚硅谷的 Node.js 课程时的一些笔记
  • 尚硅谷前端学科全套课程请点击这里进行下载,提取码:afyt

一、基础内容

1.命令行

(1).命令

  • dir:列出当前目录下的所有文件
  • cd 目录名:进入指定目录
  • md 目录名:新建文件夹
  • rd 目录名:删除文件夹
  • a.txt:直接打开当前目录下的文件

(2).目录

  • .:当前目录
  • ..:上一级目录

(3).环境变量

  • 当我们在命令行窗口打开一个文件或调用一个程序时
  • 系统会首先在当前目录下寻找文件程序
    • 如果找到了则直接打开
    • 如果没有找到则会依次到环境变量 path 的路径中寻找
      • 如果找到了则会直接执行
      • 如果没有找到则报错
  • 可以将一些常用的文件或程序的路径添加到 path 下,这样我们就可以在任意位置来访问这些文件了

2.简介

  • 中文网站:点击这里
  • Node.js 是一个能够在服务器端运行 JavaScript 的开放源代码、跨平台 JavaScript 运行环境
  • Node 采用 Google 开发的 V8 引擎运行 js 代码,使用事件驱动、非阻塞和异步I/O模型等技术来提高性能,可优化应用程序的传输量和规模
  • Node 大部分基本模块都用 JavaScript 编写。在 Node 出现之前,JS 通常作为客户端程序设计语言使用,以 JS 写出的程序常在用户的浏览器上运行
  • Node 是事件驱动的,开发者可以在不使用线程的情况下开发出一个能够承载高并发的服务器。其他服务器端语言难以开发高并发应用,而且即使开发出来,性能也不尽人意
  • Node.js 允许通过 JS 和一系列模块来编写服务器端应用和网络相关的应用
    • 核心模块包括文件系统 I/O、网络(HTTP、TCP、UDP、DNS、YLS/SSL等)、二进制数据流、加密算法、数据流等等
    • Node 模块的 API 形式简单,降低了编程的复杂度
  • Node.js 之父:瑞安·达尔(Ryan Dahl)

3.图示

  • 一个网页需要经过如下几个步骤:
    • 用户访问网页并发送请求给网站服务器
    • 服务器分出线程来处理该条请求,如果需要进行I/O操作时,会自动分出一条I/O线程
    • 处理完I/O操作后,服务器将网页响应返回给用户
  • 在如上的步骤中,第一和第三项可以做到优化,唯独无法优化的就是I/O请求
  • 所以为了防止服务器多条进程造成阻塞现象,服务器端只能是单进程,这样才不会形成阻塞
  • 示例图如下:
    njs01.png

4.历史

  • Node.js 的历史发展如下图所示:
    njs02.png

5.用途

  • Web 服务 API,比如 REST
  • 实时多人游戏
  • 后端的 Web 服务,例如跨域、服务器端的请求
  • 基于 Web 的应用
  • 多客户端的通信,如即时通信

6.更新

(1).Windows下

  • 去官网下载最新版本的 .msi 文件并安装,就完成了 Node 版本更新
  • npm 是随 Node.js 一起发布的包管理工具,默认采用的并不一定是最新版本,如需升级在命令行使用以下命令:
    1
    2
    npm -g install npm ( 官方最新稳定版 )
    npm -g install npm@6.1.0 ( 自己需要的版本 )

(2).Linux下

  • 先查看本机 Node.js 版本:

    1
    node -v
  • 清除 Node.js 的 cache

    1
    sudo npm cache clean -f
  • 安装 Node 版本管理工具,工具的名字有点奇葩,叫做 n

    1
    sudo npm install -g n
  • 安装最新版本的 Node.js

    1
    sudo n stable
  • 再次查看本机的 Node.js 版本

    1
    node -v
  • 更新 npm 到最新版

    1
    sudo npm install -g npm
  • 验证版本是否升级

    1
    2
    node -v
    npm -v

7.执行js

  • 在命令行中进入js文件所在目录
  • 然后使用 node hello.js 命令来执行该js文件

二、CommonJS规范

  • 目的:弥补当前 JavaScript 没有标准的缺陷
  • 愿景:希望JS能够在任何地方运行
  • 定义:模块引用、模块定义、模块标识

1.模块标识

  • 引入外部模块时,使用即为模块标识,可以通过模块标识来找到指定的模块
  • 模块分为两大类:
    • 核心模块:由Node引擎提供的模块,标识即为模块的名字
    • 文件模块:由用户自己创建的模块,标识即为文件的路径(绝对、相对路径)

2.模块化

  • 在Node中,一个JS文件就是一个模块
  • 在Node中,每一个JS文件中的JS代码都是独立运行在一个函数中的,而不是全局作用域

(1).引入模块

  • 在Node中,通过 require() 函数来引入外部的模块

    1
    2
    3
    let md = require("./module");

    let md = require("./module.js");
  • 路径如果使用相对路径,必须以 ./../ 来开头

  • 使用 require() 引入模块以后,该函数会返回一个对象,这个对象代表的是引入的模块

(2).暴露变量或方法

  • 在Node中,通过 exports 来向外部暴露变量和方法,只需要将需要暴露给外部的变量或方法设置为 exports 的属性即可
    1
    exports.x = "暴露的x"

(3).调用变量或方法

  • 通过引入模块时定义的变量来调用暴露的变量或方法
    1
    md.x;

3.全局对象

  • 在 Node 中有一个全局对象 global,它的作用和网页中 Window 类似

    • 在全局中创建的变量都会作为 global 的属性保存
    • 在全局中创建的函数都会作为 global 的方法保存
  • 当 Node 在执行模块中的代码时,它会在代码的外部添加如下代码:

    1
    2
    3
    4
    function (exports, require, module, __filename, __dirname) {
    模块中的代码
    console.log(arguments.callee + "");
    }
  • 实际上模块中的代码都是包装在一个函数中执行的并且在函数执行时,同时传递进了如下5个实参:

    • exports:用来将变量或函数暴露到外部
    • require:函数,用来引入外部的模块
    • module:代表的是当前模块本身,exports就是它的属性
    • __filename:当前模块的完 整路径
    • __dirname:当前模块所在文件夹的完整路径

exports与module.exports的区别

  • 前者只能通过 exports.xxx 的方式来向外暴露内部变量,如:

    1
    exports.xxx = xxx;
  • 后者既可以通过 module.exports.xxx 的方式,也可以通过直接赋值来向外暴露内部变量,如:

    1
    2
    module.exports.xxx = xxx;
    module.exports = {};
  • 赋值的区分方法:前者是直接修改了变量,而后者是修改了变量的属性(通过画引用数据类型的内存空间图来理解)

4.包

  • 规范允许我们将一组相关的模块组合到一起,形成一组完整的工具
  • 包规范包结构包描述文件两个部分组成
    • 包结构:用于组织包中的各种文件
    • 包描述文件:描述包的相关信息,以供外部读取分析

(1).包结构

  • 包实际上就是一个压缩文件,解压以后还原为目录,包含如下:
    • package.json:描述文件(必须的)
    • bin:目录,存放可执行二进制文件
    • lib:目录,存放js代码
    • doc:目录,存放文档
    • test:目录,存放单元测试文件

(2).包描述文件

  • 用于表达非代码相关的信息,是一个JSON格式的文件,位于包的根目录下,是包的重要组成部分
  • package.json 主要含有:name、description、version、keywords、maintainers、contributors、bugs、licenses、repositories、dependencies 等
  • 注意:任何JSON文件中都不可以写注释

(3).NPM

  • npm 可实现第三方模块的发布、安装和依赖等
  • npm 命令:
    • npm -v:查看 npm 的版本
    • npm version:查看所有模块的版本
    • npm init -y:初始化 npm 且跳过手动设置(如需手动设置需去除-y)
    • npm search 包名:搜索包
    • npm install/i 包名:安装包
    • npm remove/r 包名:删除包
    • npm install 包名 --save:安装包并添加到依赖中
    • npm install:下载当前项目所依赖的包
    • npm install 包名 -g:全局安装包(一般都是一些工具)
  • 在安装包的时候,可能发现文件夹中并没有新增文件夹,可能是因为缺少了 package.json 文件,可通过初始化命令添加

(4).CNPM

  • 即为淘宝提供的 npm 镜像网站
  • 作用:解决 npm 下载缓慢问题

(5).寻找包流程

  • node 在使用模块名字来引入模块时,会首先在当前目录的 node_modules 中寻找是否含有该模块
    • 如果有则直接使用,如果没有则去上一层目录的 node_modules 中寻找
    • 如果有则直接使用,如果没有则再去上一层目录的 node_modules 中寻找,直到找到为止
    • 如果找到磁盘的根目录依然没有,则报错

三、Buffer(缓冲区)

  • 简介:从结构上看 Buffer 非常像一个数组,其元素为16进制的两位数,每个元素表示内存中的一个字节,因此可以直接通过 Buffer 来创建内存中的空间
    • 每个元素的范围是从 00 - ff 的
  • 作用:专门用来存储二进制数据(区别于数组)

1.length的区别

  • buf.length:计算的是占用内存的大小
  • str.length:计算的是字符串的长度
  • 区别:两个有时会相同有时不同,不同是因为一个汉字占内存空间中的3个字节,而长度只有1

2.相关方法

  • Buffer.from("字符串"):将一个字符串转换为 buffer

  • Buffer.alloc(size):创建一个指定大小的 buffer

  • Buffer.allocUnsafe(size):创建一个指定大小的 buffer,但是 buffer 中可能含有敏感数据(会残留当前内存区之前的数据)

  • buf[2]:操作buf对象中的第3个元素,若数字在控制台或页面中输出一定是十进制

    • 可使用 toString() 方法来转换显示的进制
      1
      buf2[1].toString(2);	// 转换为二进制显示
  • buf.toString():将缓冲区中的数据转换为字符串

四、fs(文件系统)

1.简介

  • 文件系统(File System)是通过 Node 中的 fs 模块来操作系统中的文件
  • 该模块提供了一些标准文件访问 API 来打开、读取、写入文件,以及与其交互
  • 该模块中的所有的操作都有同步异步两种形式
    • 同步:会阻塞程序的执行(带Sync)
    • 异步:不会阻塞程序的执行,通过回调函数将结果返回(不带Sync)
  • 该模块是核心模块,直接引入无需下载:
    1
    let fs = require("fs");

2.文件写入

(1).普通写入

a).同步使用

  • 使用 fs.openSync()打开文件(该方法会返回一个文件的描述符作为结果,可以通过该描述符来对文件进行各种操作),参数为:

    • path:文件路径
    • flags:操作的类型(w,r)
      1
      let fd = fs.openSync("./file/test1.txt", "w");
  • 使用 fs.writeSync()写入文件,参数为:

    • fd:文件描述符
    • string:要写入的内容
    • position:写入的起始位置(可选)
    • encoding:写入的编码,默认为 utf-8(可选)
      1
      fs.writeSync(fd, "测试文件的第一行文字");
  • 使用 fs.closeSync()关闭文件,参数为:

    • fd:文件描述符
      1
      fs.closeSync(fd);

b).异步使用

  • 使用异步 API 时,只需要在同步的基础上增加回调函数即可,回调函数需要通过参数来返回相应的值,参数通常有:
    • err:错误对象,若没有错误即为 null
    • fd:文件描述符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 打开文件
fs.open("./file/test2.txt", "w", function (err, fd){
if(!err){
// 写入内容
fs.write(fd, "异步操作的第一行文字", function (err){
if(!err){
console.log("成功添加内容");
}
// 关闭文件
fs.close(fd, function (err){
console.log(err);
})
})
}
})

(2).简单写入

a).同步使用

  • 使用 fs.writeFileSync() 来写入,参数为:

    • path:文件路径

    • data:要写入的内容

    • options:可选,可以对写入进行一些设置

      1
      fs.writeFileSync("./file/test4.txt", "通过简单文件同步写入的内容");

b).异步使用

  • 使用 fs.writeFile() 来写入,参数比同步多一个回调函数
    1
    2
    3
    4
    5
    6
    fs.writeFile("./file/test3.txt", "通过简单文件异步写入的内容", function (err){
    console.log(err);
    if(!err){
    console.log("写入成功");
    }
    })

c).flag状态

  • 打开文件的状态如下图:
    njs03.png

(3).流式写入

以上两种写入方法都不适合大文件的写入,性能较差,容易导致内存溢出,因此推荐使用流式写入方法

  • 使用 fs.createWriteStream() 来创建一个可写流,参数为:

    • path:文件路径
    • options:配置的参数,可选
      1
      let ws = fs.createWriteStream("./file/test5.txt");
  • 使用 ws.write() 来向文件中输入内容:

    1
    2
    ws.write("第一次写入");
    ws.write("第二次写入");
  • 使用 ws.close()/ws.end() 来关闭该可写流(前者在低版本Node中会出现一些错误):

    1
    2
    ws.close();
    ws.end();
  • 使用 ws.once() 可以为对象绑定一个一次性的事件来监听可写流的关闭与否:

    1
    2
    3
    4
    5
    6
    ws.once("open", function (){
    console.log("可写流打开了~~");
    })
    ws.once("close", function (){
    console.log("可写流关闭了~~");
    })

3.文件读取

(1).普通读取

(2).简单读取

a).同步使用

b).异步使用

  • 使用 fs.readFile() 来读取,参数比同步多一个回调函数

    1
    2
    3
    4
    5
    fs.readFile("./file/test1.txt", function (err, data){
    if(!err){
    console.log(data.toString());
    }
    })
  • 若读取与写入同时使用时,可以达到复制的效果,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fs.readFile("./file/1.jpg", function (err, data){
    if(!err){
    console.log(data);
    fs.writeFile("./file/1_copy.jpg", data, function (err){
    if (!err){
    console.log("写入成功~~~");
    }
    })
    }
    })

(3).流式读取

a).常规读取+写入

  • 读取可读流中的数据,需要为可读流绑定一个 data 事件,事件绑定完毕会自动开始读取数据(读取到的数据都在回调函数的参数中)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 创建一个可读流
    let rs = fs.createReadStream("./file/test1.txt");
    // 创建一个可写流
    let ws = fs.createWriteStream("./file/test1_copy.txt");
    // 监听是否开始关闭
    rs.once("open", function (){
    console.log("可读流打开了");
    })
    rs.once("close", function (){
    console.log("可读流关闭了");
    ws.end();
    })
    // 读取可读流的数据
    rs.on("data", function (data){
    console.log(data);
    // 写入可写流中
    ws.write(data);
    })

b).简便读取+写入

  • 无需绑定 data 事件,只需使用可写流的 rs.pipe() 方法即可将可读流中的内容直接输出到可写流中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 创建一个可写流
    let rs = fs.createReadStream("./file/那些花儿.mp3");
    // 创建一个可写流
    let ws = fs.createWriteStream("./file/那些花儿_copy.mp3");
    // 监听是否开始关闭
    rs.once("open", function (){
    console.log("可读流打开了");
    })
    rs.once("close", function (){
    console.log("可读流关闭了");
    })
    rs.pipe(ws);

4.其他方法

  • fs.existsSync(path):检查一个文件是否存在
  • fs.stat(path,callback)/fs.statSync(path):获取文件的状态
  • fs.unlink(path,callback)/fs.unlinkSync(path):删除文件
  • fs.readdir(path[,options],callback)/fs.readdirSync(path[,options]):读取一个目录的目录结构
  • fs.truncate(path,len,callback) / fs.truncateSync(path,len):截断文件,将文件修改为指定的大小
  • fs.mkdir(path,[options],callback) / fs.mkdirSync(path,[options]):创建一个目录
  • fs.rmdir(path,callback) / fs.rmdirSync(path):删除一个目录
  • fs.rename(oldPath,newPath,callback) / fs.renameSync(oldPath,newPath):对文件进行重命名,同时可以实现移动的效果
  • fs.watchFire(filename[,options],listener):监视文件的修改