Node.js从入门到放弃系列
目录
前言
学习 Node.js 需要具备的技术:
1. 初识 Node.js
1.1 回顾与思考
-
浏览器中的 JavaScript 的组成部分
-
JavaScript 在浏览器中执行的原理
-
JavaScript 操作 DOM 和 BOM 原理
-
浏览器中的 JavaScript 运行环境
运行环境 是指 代码正常运行所需的必要环境。 -
JavaScript 能否做后端开发?
除了Java、python、PHP做后端开发外,JavaScript 也可以做后端程序开发(需要Node.js 这个运行环境)
1.2 Node.js 简介
1.2.1 什么是Node.js
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
在Node上运行的 JavaScript 相比其他后端开发语言有何优势?
最大的优势是借助 JavaScript 天生的事件驱动机制加 V8 高性能引擎,使编写高性能Web服务轻而易举。
其次,JavaScript 语言本身是完善的函数式语言,在前端开发时,开发人员往往写得比较随意,让人感觉 JavaScript就是个“玩具语言”。但是,在Node环境下,通过模块化的 JavaScript 代码,加上函数式编程,并且无需考虑浏览器兼容性问题,直接使用最新的ECMAScript 6标准,可以完全满足工程上的需求。
Node.js 官网地址: https://nodejs.org/zh-cn/
相关技术/论述:https://www.liaoxuefeng.com/wiki/1022910821149312/1023025235359040
1.2.2 Node.js 中的 JavaScript 运行环境
-
注意:
- 浏览器 是JavaScript 的 前端运行环境。
- Node.js 是 JavaScript 的 后端运行环境。
- Node.js 中 无法调用 DOM 和 BOM 等浏览器内置 API。
1.2.3 Node.js 可以做什么
Node.js 作为一个 JavaScript 的运行环境,仅仅提供了基础的功能和 API。然而,基于 Node.js 提供的这些基础能,很多强大的工具和框架如雨后春笋,层出不穷,所以学会了 Node.js ,可以让前端程序员胜任更多的工作和岗位:
- 基于 Express 框架http://www.expressjs.com.cn/,可以快速构建 Web 应用;
- 基于 Electron 框架 https://electronjs.org/,可以构建跨平台的桌面应用;
- 基于 restify 框架 http://restify.com/,可以快速构建 API 接口项目;
- 读写和操作数据库、创建实用的命令行工具辅助前端开发、
etc…
总之:Node.js 是 大前端时代 的“大宝剑”,有了 Node.js 这个超级 buff 的加持,前端程序员的 行业竞争力 会越来越强!
1.2.4 学习路径
-
浏览器中的 JavaScript 学习路径:
JavaScript 基础语法 + 浏览器内置 API(DOM + BOM) + 第三方库(jQuery、art-template 等) -
Node.js 的学习路径:
JavaScript 基础语法 + Node.js 内置 API 模块(fs、path、http等)+ 第三方 API 模块(express、mysql 等)
1.3 Node.js环境安装
由于 Node.js 平台是在后端运行 JavaScript 代码,所以,必须必须首先在计算机上安装 Node.js 环境。
安装包可以从 Node.js 的官网首页直接下载,进入到 Node.js 的官网首页(https://nodejs.org/en/),点击绿色的按钮,下载所需的版本后(建议稳定版)。
【安装】:双击直接安装,不建议更改默认安装位置(C:\),中途也保持默认设置,一路 “Next” 即可。
LTS :长期稳定版 — 对于追求稳定性的企业级项目来说,推荐安装 LTS 版本的 Node.js。
Current :新特性尝鲜版 — 对热衷于尝试新特性的用户来说,推荐安装 Current 版本的 Node.js。但是,Current 版本中可能存在隐藏的 Bug 或安全性漏洞,因此不推荐在企业级项目中使用 Current 版本的 Node.js。
1.3.1 查看已安装的Node.js版本号
- 打开 cmd 终端
Windows系统,打开 cmd 命令行终端,输入命令 node –v,按下回车键,即可查看已安装的 Node.js 的版本号(能看到版本号,则代表 Node.js 安装成功)。
注意命令行模式和交互模式,看到类似C:\>
是在Windows提供的命令行模式:
看到 >
是在Node交互式环境下:
1、命令提示符后输入node,就会进入 Node.js 的交互环境。在交互环境下,你可以输入任意 JavaScript 语句,例如100+200,回车后将得到输出结果。
2、要退出 Node.js 环境,连按两次Ctrl+C
。
此外,在命令行模式运行.js文件和在Node交互式环境下直接运行 JavaScript 代码有所不同。Node交互式环境会把每一行JavaScript代码的结果自动打印出来(直接看到结果),但是,直接运行JavaScript文件则不会。
快速打开 cmd 终端的方式:
使用快捷键( Windows徽标键 + R)打开运行面板,输入 cmd 后直接回车,即可打开终端。
- Powershell 终端
除了 cmd 终端外,还可以使用 Powershell终端。快速打开方式为,在当前文件所在目录下,shift
+ 右键
,在弹出的菜单中,选择Powershell,如图所示:
1.3.2 什么是终端
终端(英文:Terminal)是专门为开发人员设计的,用于实现人机交互 的一种方式。
作为一名合格的程序员,我们有必要识记一些 常用的终端命令,来辅助我们更好的操作与使用计算机
1.4 Node.js 环境中执行 JavaScript 代码
语法格式:node 要执行的js文件的路径
1.4.1 终端中的快捷键
在 Windows 的 powershell 或 cmd 终端中,我们可以通过如下快捷键,来提高终端的操作效率:
使用 ↑ 键,可以快速定位到上一次执行的命令;
使用 tab键,能够快速补全路径;
使用 ESC 键,能够快速清空当前已输入的命令;
输入 cls 命令,可以清空终端;
2. fs 文件系统模块
2.1 什么是fs文件系统模块
fs 模块是 Node.js 官方提供的、用来读、写文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求。
因为 Node.js 是运行在服务端的 JavaScript 环境,服务器程序和浏览器程序相比,最大的特点是没有浏览器的安全限制了,而且,服务器程序必须能接收网络请求,读写文件,处理二进制内容,所以,Node.js 内置的常用模块就是为了实现基本的服务器功能。这些模块在浏览器环境中是无法被执行的,因为它们的底层代码是用 C/C++ 在 Node.js 运行环境中实现的。
我们知道,JavaScript 有且仅有一个全局对象,在浏览器中叫window对象
。而在Node.js环境中,也有唯一的全局对象,不过它不叫window了,而是叫global
。
和所有其它 JavaScript 模块不同的是,fs 模块同时提供了 异步 和 同步 的方法。
回顾一下什么是异步方法。因为 JavaScript 的单线程模型,执行IO操作时,JavaScript 代码无需等待,而是传入回调函数后,继续执行后续JavaScript代码。比如 jQuery 提供的getJSON()
操作;而同步的IO操作则需要等待函数返回
例如:fs.readFile()
方法:用来 读取 指定文件中的内容
fs.writeFile()
方法:用来向指定的文件中 写入内容
如果要在 JavaScript 代码中,使用 fs 模块来操作文件,则需要使用如下的方式先导入它:
// 导入 fs
const fs = require ('fs')
附:如何判断JavaScript执行环境
有很多 JavaScript 代码既能在浏览器中执行,也能在 Node 环境执行,但有些时候,程序本身需要判断自己到底是在什么环境下执行的,常用的方式就是根据浏览器和 Node 环境提供的全局变量名称来判断:if (typeof(window) === 'undefined') {
console.log('node.js');
} else {
console.log('browser');
}
2.2 读取文件中的指定内容
2.2.1 fs.readFile() 的语法格式
使用 fs.readFile()
方法,可以 读取 指定文件中的内容,语法格式如下:
fs.readFile(path[, options],callback)
- 参数解释:
- 参数1:必选 参数,字符串,表示文件的路径。
- 参数2:可选参数,表示以什么 编码格式 来读取文件。
- 参数3:必选 参数,文件读取完成后,通过回调函数拿到读取的结果。
2.2.2 示例代码
以 utf8
的编码格式,读取指定文件的内容,并打印 err
和 dataStr
的值:
const fs = require ('fs')
fs.readFile('./files/123.txt','utf8',function(err,dataStr){
console.log(err);
console.log('-----');
console.log(dataStr);
})
2.2.3 判断文件是否读取成功
异步读取时,传入的回调函数接收两个参数,当正常读取时,err
参数为null
,data
参数为读取到的String
。当读取发生错误时,err
参数代表一个错误对象,data
为undefined
。这也是Node.js
标准的回调函数:第一个参数代表错误信息,第二个参数代表结果。后面我们还会经常编写这种回调函数。
由于err
是否为null
就是判断是否出错的标志,所以通常的判断逻辑总是:
if (err) {
// 出错了
} else {
// 正常
}
例如,获取文件读取的结果可写成如下代码:
const fs = require ('fs')
fs.readFile('./files/123.txt','utf8',function(err,dataStr){
if(err){
return console.log('文件读取失败'+ err.message);
}
console.log('文件读取成功,内容是:'+ result );
})
如果我们要读取的文件不是文本文件,而是二进制文件,怎么办?
下面的例子演示了如何读取一个图片文件:
'use strict';
var fs = require('fs');
fs.readFile('sample.png', function (err, data) {
if (err) {
console.log(err);
} else {
console.log(data);
console.log(data.length + ' bytes');
}
});
当读取二进制文件时,不传入文件编码时,回调函数的data参数将返回一个Buffer对象。在Node.js中,Buffer对象就是一个包含零个或任意个字节的数组(注意和Array不同)。
Buffer对象可以和String作转换,例如,把一个Buffer
对象转换成String:
// Buffer -> String
var text = data.toString('utf-8');
console.log(text);
或者把一个String
转换成Buffer:
// String -> Buffer
var buf = Buffer.from(text, 'utf-8');
console.log(buf);
2.3 向指定的文件中写入内容
2.3.1 fs.writeFile()语法格式
使用fs.writeFile()方法,可以向指定的文件中写入内容,语法格式如下:
fs.writeFile(file,data[,options],callback)
writeFile()的参数依次为文件、数据和回调函数。如果传入的数据是String
,默认按UTF-8
编码写入文本文件,如果传入的参数是Buffer
,则写入的是二进制文件。回调函数由于只关心成功与否,因此只需要一个err
参数。
-
参数解释:
- 参数1:必选参数,需要指定一个文件路径的字符串,表示文件的存放路径。
- 参数2:必选参数,表示要写入的内容。
- 参数3:可选参数,表示以什么格式写入文件内容,默认值是
utf8
。 - 参数4:必选参数,文件写入完成后的回调函数。
2.3.2 示例代码
向指定的文件路径中,写入文件内容
const fs = require('fs')
fs.writeFile('./files/123.txt','Hello Node.js!',function(err){
console.log(err); // 输出结果为 null
})
注: 如果 files 文件夹下不存在 123.txt
文件,则会自动创建该文件,并写入 Hello Node.js! 这个内容
2.3.3 判断文件是否写入成功
通过判断 err
对象是否为 null
,获取文件写入的结果:
const fs = require('fs')
fs.writeFile('./files/123.txt','Hello Node.js!',function(err){
if(err){
return console.log('文件写入失败!'+ err.message);
};
console.log('文件写入成功!');
})
2.4 stat 获取文件信息
如果我们要获取文件大小,创建时间等信息,可以使用fs.stat()
,它返回一个Stat对象
,能告诉我们文件或目录的详细信息:
'use strict';
var fs = require('fs');
fs.stat('sample.txt', function (err, stat) {
if (err) {
console.log(err);
} else {
// 是否是文件:
console.log('isFile: ' + stat.isFile());
// 是否是目录:
console.log('isDirectory: ' + stat.isDirectory());
if (stat.isFile()) {
// 文件大小:
console.log('size: ' + stat.size);
// 创建时间, Date对象:
console.log('birth time: ' + stat.birthtime);
// 修改时间, Date对象:
console.log('modified time: ' + stat.mtime);
}
}
});
fs.stat()
运行结果如下:
isFile: true
isDirectory: false
size: 145
birth time: Fri Dec 09 2020 09:43:41 GMT+0800 (CST)
modified time: Fri Dec 09 2020 12:09:00 GMT+0800 (CST)
使用严格模式
如果在 JavaScript 文件开头写上'use strict'
;,那么 Node 在执行该 JavaScript 时将使用严格模式。但是,在服务器环境下,如果有很多 JavaScript 文件,每个文件都写上 'use strict';
很麻烦。我们可以给Nodejs传递一个参数,让Node直接为所有js文件开启严格模式:
// 给 Node传递 --use_strict 参数来开启严格模式
node --use_strict calc.js
2.5 练习-考试成绩整理
案例中需要用到的素材:网盘下载
使用 fs 文件系统模块,将素材目录下的《成绩.txt》文件中的考试数据,整理到《成绩-ok.txt》文件中。
整理前,成绩.txt文件中的数据格式如下:
整理完成之后,希望得到的《成绩-OK.txt》文件中的数据格式如下:
核心实现步骤:
- 导入需要的
fs
文件系统模块; - 使用 fs.readFile() 方法,读取素材目录下的 成绩.txt 文件;
- 判断文件是否读取失败;
- 文件读取成功后,处理成绩数据;
- 将处理完成的成绩数据,调用 fs.writeFile() 方法,写入到新文件 成绩-ok.txt 中。
考试成绩整理练习答案(完整代码)
// 1. 导入 fs 模块
const fs = require('fs')
// 2. 调用 fs.readFile() 读取文件的内容
fs.readFile('../素材/成绩.txt', 'utf8', function(err, dataStr) {
// 3. 判断是否读取成功
if (err) {
return console.log('读取文件失败!' + err.message)
}
// console.log('读取文件成功!' + dataStr)
// 4.1 先把成绩的数据,按照空格进行分割
const arrOld = dataStr.split(' ')
// 4.2 循环分割后的数组,对每一项数据,进行字符串的替换操作
const arrNew = []
arrOld.forEach(item => {
arrNew.push(item.replace('=', ':'))
})
// 4.3 把新数组中的每一项,进行合并,得到一个新的字符串
const newStr = arrNew.join('\r\n')
// 5. 调用 fs.writeFile() 方法,把处理完毕的成绩,写入到新文件中
fs.writeFile('./files/成绩-ok.txt', newStr, function(err) {
if (err) {
return console.log('写入文件失败!' + err.message)
}
console.log('成绩写入成功!')
})
})
【代码解释】arrNew.join('\r\n')
:\r\n
在windows 里表示换行。
2.6 fs 模块 - 路径动态拼接的问题
问题: 在使用 fs 模块操作文件时,如果提供的操作路径是以 ./ 或 …/ 开头的相对路径时,很容易出现路径动态拼接错误的问题。
原因: 代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径。
解决方案: 在使用 fs 模块操作文件时,直接提供完整的路径,不要提供 ./ 或 …/ 开头的相对路径,从而防止路径动态拼接的问题。
// 不要使用./ 或 ../ 这样的相对路径
fs.readFile('./files/123.txt','utf8',function(err,dataStr){
if (err) return console.log('读取文件失败!' + err.message);
console.log(dataStr);
})
// __dirname 表示当前文件所处的目录
fs.readFile(__dirname + '/files/1.txt','utf8',function(err,dataStr){
if(err) return console.log('读取文件失败!' + err.message);
console.log(dataStr);
})
注:类似 D:\ node \ FW
这样的路径,需要更改为 D: \ \ node \ \ FW
这样的双斜线。因为在 js 里单 \
代表转义。
小结
1 Node.js的交互模式和直接运行的区别
用文本编辑器写 JavaScript 程序,然后保存为后缀为 .js 的文件,就可以用 node 直接运行这个程序了。
Node 的交互模式和直接运行.js文件有什么区别呢?
直接输入node进入交互模式,相当于启动了Node解释器,但是等待你一行一行地输入源代码,每输入一行就执行一行。
直接运行node hello.js文件相当于启动了Node解释器,然后一次性把hello.js文件的源代码给执行了,你是没有机会以交互的方式输入源代码的。
小贴士:
在编写JavaScript代码的时候,完全可以一边在文本编辑器里写代码,一边开一个Node交互式命令窗口,在写代码的过程中,把部分代码粘到命令行去验证,事半功倍!前提是得有个27’的超大显示器!????
2 同步和异步的使用场景
在 fs 模块中,提供同步方法是为了方便使用。那我们到底是应该用异步方法还是同步方法呢?
由于 Node.js 环境执行的 JavaScript 代码是服务器端代码,所以,绝大部分需要在服务器运行期反复执行业务逻辑的代码,必须使用异步代码,否则,同步代码在执行时期,服务器将停止响应,因为 JavaScript 只有一个执行线程。
服务器启动时如果需要读取配置文件,或者结束时需要写入到状态文件时,可以使用同步代码,因为这些代码只在启动和结束时执行一次,不影响服务器正常运行时的异步执行。
3. path路径模块
3.1 path路径模块定义
path 模块是 Node.js 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。
例如:
-
path.join() 方法:用来将多个路径片段拼接成一个完整的路径字符串;
-
path.basename() 方法:用来从路径字符串中,将文件名解析出来
如果要在 JavaScript 代码中,使用 path 模块来处理路径,则需要使用如下的方式先导入它:
// 导入 path 模块
const path = require('path')
3.2 路径拼接
3.2.1 path.join的语法格式
使用 path.join()
方法,可以把多个路径片段拼接为完整的路径字符串,语法格式如下:
path.join(...paths)
【参数解释】
- …paths :< string> 路径片段的序列;
- 返回值:< string>
3.2.2 代码示例
使用 path.join()
方法,可以把多个路径片段拼接为完整的路径字符串:
注: 凡是涉及到路径拼接的操作,都要使用 path.join()
方法进行处理。不要直接使用 + 进行字符串的拼接。
上面示例代码中的路径字符串 …/ ,会抵销掉一层路径,因此最终输出的路径中,不包含路径片断c
。
3.3 获取路径中的文件名
3.3.1 path.basename() 的语法格式
使用 path.basename()
方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名,语法格式如下:
【参数解释】
path < string> 必选参数,表示一个路径的字符串
ext < string> 可选参数,表示文件扩展名
返回:< string> 表示路径中的最后一部分
3.3.2 代码示例
使用 path.basename()
方法,可以从一个文件路径中,获取到文件的名称部分:
const fpath='/a/b/c/index.html' //文件的存放路径
var fullName=path.basename(fpath)
console.log(fullName); // 输出index.html
var nameWithoutExt=path.basename(fpath,'.html')
console.log(nameWithoutExt); // 输出index
3.4 获取路径中的文件扩展名
3.4.1 path.extname() 的语法格式
使用 path.extname()
方法,可以获取路径中的扩展名部分,语法格式如下:
【参数解释】
- path 必选参数,表示一个路径的字符串;
- 返回: 返回得到的扩展名字符串。
3.4.2 示例代码
使用 path.extname()
方法,可以获取路径中的扩展名部分:
const fpath='/a/b/c/index.html' //路径字符串
const fext=path.extname(fpath)
console.log(fext); // 输出 .html
3.5 综合案例-时钟案例
3.5.1 案例要实现的功能
将素材目录下的 index.html 页面,拆分成三个文件,分别是:
- index.css
- index.js
- index.html
并且将拆分出来的 3 个文件,存放到 clock 目录中。
3.5.2 案例的实现步骤
- 创建两个正则表达式,分别用来匹配
<style>
和<script>
标签; - 使用
fs 模块
,读取需要被处理的HTML
文件; - 自定义
resolveCSS
方法,来写入 index.css 样式文件; - 自定义
resolveJS
方法,来写入index.js 脚本文件; - 自定义
resolveHTML
方法,来写入 index.html 文件。
步骤1 - 导入需要的模块并创建正则表达式
// 1、导入fs 文件系统模块
const fs=require('fs');
// 2、导入 path 路径处理模块
const path = require('path')
// 3、匹配<style></style> 标签的正则
// 其中 \s 表示空白字符;\S 表示非空白字符;* 表示匹配任意次
const regStyle=/<style>[\s\S]*<\/style>/
// 4、匹配<script></script> 标签的正则
const regScript=/<script>[\s\S]*<\/script>/
步骤2 - 使用 fs 模块读取需要被处理的 html 文件
// 1、读取需要被处理的 HTML 文件
fs.readFile(path.join(__dirname,'../素材/index.html'),'utf8',(err,dataStr)=>{
// 2、读取 HTML 文件失败
if (err) return console.log('读取HTML文件失败!' + err.message);
// 3、读取 HTML 文件成功后,调用对应的方法,拆解出 css、js 和 html 文件
resolveCSS(dataStr);
resolveJS(dataStr);
resolveHTML(dataStr);
})
步骤3 – 自定义 resolveCSS 方法
// 处理 css 样式
function resolveCSS(htmlStr){
// 1、使用正则提取页面中的 <style></style> 标签
const r1=regStyle.exec(htmlStr);
// 2、将提取出来的样式字符串,做进一步的处理
const newCSS=r1[0].replace('<style>','').replace('</style>','');
// 3、将提取出来的 css 样式,写入到 index.css 文件中
fs.writeFile(path.join(__dirname,'./clock/index.css'),newCSS,err =>{
if(err) return console.log('写入 css 样式失败!'+ err.message);
console.log('写入 CSS 样式成功!');
})
}
步骤4 - 自定义resolveJS 方法
// 处理 JS 脚本
function resolveJS(htmlStr){
// 1、使用正则提取页面中的 <script></script> 标签
const r2=regScript.exec(htmlStr);
// 2、将提取出来的脚本字符串,做进一步的处理
const newJS=r2[0].replace('<script>','').replace('</script>','');
// 3、将提取出来的 js 脚本,写入到 index.js 文件中
fs.writeFile(path.join(__dirname,'./clock/index.js'),newJS,err =>{
if(err) return console.log('写入 JavaScript 脚本失败!'+ err.message);
console.log('写入 JS 脚本成功!');
})
}
步骤5 – 自定义 resolveHTML 方法
// 处理 HTML 文件
function resolveHTML(htmlStr){
// 1、使用字符串的replace 方法,把内嵌的 <style> 和 <script> 标签,替换为外联的<link> 和 <script>标签
const newHTML=htmlStr
.replace(regStyle,'<link rel="stylesheet" href="./index.css" />')
.replace(regStyle,'<script src="./index.js"></script>')
// 2、将替换完成之后的 html 代码,写入到 indexedDB.html 文件中
fs.writeFile(path.join(__dirname,'./clock/index.html'),newHTML,err =>{
if (err) return console.log('写入 HTML 文件失败!' + err.message);
console.log('写入 HTML 页面成功!');
})
}
3.5.4 案例的两个注意点
-
fs.writeFile()
方法只能用来创建文件,不能用来创建路径; - 重复调用
fs.writeFile()
写入同一个文件,新写入的内容会覆盖之前的旧内容。