Node.js学习笔记
1. 什么是Node.js
Node.js
是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Js代码
的运行环境包括浏览器和Node.js。如果把Js代码
放在浏览器中运行那就说明我们在做前端开发,如果放在Node.js中执行那就说明我们在做后端开发。
- 如图是
Node.js
中JavaScript 运行环境,V8引擎负责解析和执行js代码
Node.js
作为一个 JavaScript 的运行环境,仅仅提供了基础的功能和 API。然而,基于 Node.js 提供的这些基础能,很多强大的工具和框架都出现了。
- 基于 Express 框架(http://www.expressjs.com.cn/),可以快速构建 Web 应用
- 基于 Electron 框架(https://electronjs.org/),可以构建跨平台的桌面应用
- 基于 restify 框架(http://restify.com/),可以快速构建 API 接口项目
- 读写和操作数据库、创建实用的命令行工具辅助前端开发、etc…
- 可在
Node.js
的官网地址 链接下载并按照Nodejs,推荐下载长期维护版(LTS)。
2. fs 文件系统模块
1.fs 模块
是 Node.js
官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求。
fs.readFile() 方法,用来读取指定文件中的内容
fs.writeFile() 方法,用来向指定的文件中写入内容
2.如果要在 JavaScript 代码中,使用 fs 模块来操作文件,则需要使用如下的方式先导入它:
const fs = require('fs');
fs.readFile()
读取存在的文件示例:
// 参数1:读取文件的存放路径
// 参数2:读取文件时候采用的编码格式,一般默认指定 utf8
// 参数3:回调函数,拿到读取失败和成功的结果 err data
const fs = require('fs');
// 1.txt是存在的文件 里面内容为:1111
fs.readFile('./1.txt', 'utf8', function(err, data) {
console.log(err);
console.log('------------------');
console.log(data);
})
打开中断PowerShell
或者cmd
,执行命令 node .\1.js
注意
:如果读取成功,则 err 的值为 null。data 的值为 1.txt中的内容
读取不存在的文件示例:
// 参数1:读取文件的存放路径
// 参数2:读取文件时候采用的编码格式,一般默认指定 utf8
// 参数3:回调函数,拿到读取失败和成功的结果 err data
const fs = require('fs');
// 11.txt是不存在的文件
fs.readFile('./11.txt', 'utf8', function(err, data) {
console.log(err);
console.log('------------------');
console.log(data);
})
打开中断PowerShell
或者cmd
,执行命令 node .\1.js
注意
:如果读取失败,则 err 的值为 错误对象,data 的值为 undefined
判断文件是否读取成功
可以判断 err 对象是否为 null,从而知晓文件读取的结果:
const fs = require('fs');
fs.readFile('./11.txt', 'utf8', function(err, data) {
if (err) {
return console.log('读取文件失败!' + err.message);
}
console.log('读取文件成功!' + data);
})
fs.writeFile()
示例
:如果文件2.txt
不存在,则会自动创建。如果本来就存在,则写入的内容会覆盖掉原来的内容。
const fs = require('fs');
fs.writeFile('./2.txt', 'abcd', function(err) {
console.log(err);
})
如果写入成功err的值为null,否则为错误对象。
判断文件是否写入成功
const fs = require('fs');
fs.writeFile('./3.txt', 'abcd', function(err) {
if (err) {
return console.log('文件写入失败!' + err.message);
}
console.log('文件写入成功!');
})
路径拼接
- 在使用 fs 模块操作文件时,如果提供的操作路径是以
./
或../
开头的相对路径时,很容易出现路径动态拼接错误的问题。 - 原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径。 也就是说如果你所处的目录和文件所在目录不是一个目录,则会出错。
- 解决方法:
__dirname
表示当前文件所处的目录,不会随着执行 node 命令时所处的目录变化
const fs = require('fs');
// __dirname 表示当前文件所处的目录
fs.readFile(__dirname + '/file/1.txt', 'utf8', function(err, data) {
if (err) {
return console.log('读取文件失败!' + err.message);
}
console.log('读取文件成功!' + data);
})
3. path 路径模块
path 模块
是 Node.js
官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。
path.join() 方法,用来将多个路径片段拼接成一个完整的路径字符串
path.basename() 方法,用来从路径字符串中,将文件名解析出来
如果要在 JavaScript 代码中,使用 path 模块来处理路径,则需要使用如下的方式先导入它:
const path = require('path');
path.join()
使用 path.join() 方法,可以把多个路径片段拼接为完整的路径字符串,语法格式如下:
注意
:今后凡是涉及到路径拼接的操作,建议使用 path.join()
方法进行处理。不要直接使用 +
进行字符串的拼接
示例:
const path = require('path');
// ../会抵消掉/c
const pathStr = path.join('/a', '/b/c', '../', './d', 'e');
console.log(pathStr); // \a\b\d\e
// /file/1.txt 写成 ./file/1.txt也不会出错,但是如果用加号拼接会出错
const pathStr2 = path.join(__dirname, '/file/1.txt')
console.log(pathStr2);
path.basename()
使用 path.basename()
方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名。
注意
:如果第二个参数存在,则只返回名字(不带扩展名)。
示例:
const path = require('path');
const fpath = '/a/b/c/index.html';
const fullname = path.basename(fpath);
console.log(fullname); // index.html
const name = path.basename(fpath, '.html');
console.log(name); //index
path.extname()
使用path.extname()
方法,可以获取路径中的扩展名部分
示例:
const path = require('path');
const fpath = '/a/b/c/index.html';
const fext = path.extname(fpath);
console.log(fext); // .html
4. http模块
- 在网络节点中,负责消费资源的电脑,叫做客户端;负责对外提供网络资源的电脑,叫做服务器。
- 服务器和普通电脑的区别在于,服务器上安装了 web 服务器软件,例如:
IIS
、Apache
等。通过安装这些服务器软件,就能把一台普通的电脑变成一台 web 服务器。 - 在
Node.js
中,我们不需要使用 IIS、Apache 等这些第三方 web 服务器软件。因为我们可以基于 Node.js 提供的http 模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供 web 服务。 - http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。通过 http 模块提供的
http.createServer()
方法,就能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务。
IP 地址
- IP 地址就是互联网上每台计算机的唯一地址,因此 IP 地址具有唯一性。只有在知道对方 IP 地址的前提下,才能与对应的电脑之间进行数据通信。
IP 地址的格式
:通常用“点分十进制”表示成(a.b.c.d)
的形式,其中,a,b,c,d 都是 0~255 之间的十进制整数。例如:用点分十进表示的 IP地址(192.168.1.1)- 互联网中每台 Web 服务器,都有自己的 IP 地址。
域名和域名服务器
- 尽管 IP 地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,即所谓的域名(Domain Name)地址。
- IP地址和域名存在对应的关系,这份对应关系存放在一种叫做域名服务器(DNS,Domain name server)的电脑中。
- 使用者只需通过域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供 IP 地址和域名之间的转换服务的服务器。
端口号
在一台电脑中,可以运行成百上千个 web 服务。每个 web 服务都对应一个唯一的端口号。客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的 web 服务进行处理。
创建最基本的 web 服务器
基本步骤
代码
在终端启动node .\http模块.js
,则会输出server running at http://127.0.0.1:8080
。
然后在浏览器中输入http://127.0.0.1:8080
,则会输出Someone visit our web server.
// 导入 http 模块
const http = require('http');
// 调用 http.createServer() 方法,即可快速创建一个 web 服务器实例
const server = http.createServer()
// 为服务器实例绑定 request 事件,即可监听客户端发送过来的网络请求
server.on('request', function(req, res) {
console.log('Someone visit our web server.');
})
// 调用服务器实例的 .listen() 方法,即可启动当前的 web 服务器实例
server.listen('8080', function() {
console.log('server running at http://127.0.0.1:8080');
})
注意
:8080为启动的端口,如果是80则http://127.0.0.1:80
后的:80
可以省略。
crt+c
可以停止服务器
req 请求对象
req 请求对象
可以让服务器访问与客户端相关的数据或属性。比如req.url
是客户端请求的 URL 地址 ,req.method
是客户端请求的 method 类型。
// 导入 http 模块
const http = require('http');
// 调用 http.createServer() 方法,即可快速创建一个 web 服务器实例
const server = http.createServer()
// 为服务器实例绑定 request 事件,即可监听客户端发送过来的网络请求
server.on('request', function(req) {
// req.url 是客户端请求的 URL 地址 ,req.method 是客户端请求的 method 类型
const url = req.url
const method = req.method
const str = `Your request url is ${url}, and request method is ${method}`
console.log(str)
})
// 调用服务器实例的 .listen() 方法,即可启动当前的 web 服务器实例
server.listen('80', function() {
console.log('server running at http://127.0.0.1');
})
在终端启动node .\http模块.js
,则会输出server running at http://127.0.0.1
。
然后在浏览器中输入http://127.0.0.1
,则会输出Your request url is /, and request method is GET
在Postman中发起POST请求
res 响应对象
在服务器的 request 事件处理函数中, 调用 res.end()
方法,可以向客户端响应一些内容
//
const http = require('http');
const server = http.createServer()
server.on('request', function(req, res) {
// req.url 是客户端请求的 URL 地址 ,req.method 是客户端请求的 method 类型
const url = req.url
const method = req.method
const str = `Your request url is ${url}, and request method is ${method}`
console.log(str)
// 调用 res.end() 方法,向客户端响应一些内容
res.end(str);
})
server.listen('80', function() {
console.log('server running at http://127.0.0.1');
})
在Postman
中发送一个POST请求
解决中文乱码问题
当调用 res.end()
方法,向客户端发送中文内容的时候,会出现乱码问题
const http = require('http');
const server = http.createServer()
server.on('request', function(req, res) {
// req.url 是客户端请求的 URL 地址 ,req.method 是客户端请求的 method 类型
const str = `您请求的URL地址是 ${req.url}, 请求的method类型是 ${req.method}`
// 调用 res.end() 方法,向客户端响应一些内容
res.end(str);
})
server.listen('80', function() {
console.log('server running at http://127.0.0.1');
})
此时,需要调用 res.setHeader()
方法,设置 Content-Type 响应头:
const http = require('http');
const server = http.createServer()
server.on('request', function(req, res) {
// req.url 是客户端请求的 URL 地址 ,req.method 是客户端请求的 method 类型
const str = `您请求的URL地址是 ${req.url}, 请求的method类型是 ${req.method}`
// 调用 res.setHeader() 方法,设置 Content-Type 响应头,解决中文乱码的问题
res.setHeader('Content-Type', 'text/html;charset=utf-8')
// 调用 res.end() 方法,向客户端响应一些内容
res.end(str);
})
server.listen('80', function() {
console.log('server running at http://127.0.0.1');
})
根据不同的 url 响应不同的 html 内容
获取请求的 url 地址,根据不同的url地址返回不同的内容
const http = require('http');
const server = http.createServer()
server.on('request', function(req, res) {
const url = req.url;
let content = '<h1>404 Not found!</h1>'
if (url === '/' || url === '/index.html') {
content = '<h1>首页</h1>'
} else if (url === '/about.html') {
content = '<h1>关于页面</h1>'
}
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(content);
})
server.listen('80', function() {
console.log('server running at http://127.0.0.1');
})
5. 模块化
- 模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元
- 编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。把代码进行模块化拆分的好处:
Node.js 中模块化
分类
1.Node.js
中根据模块来源的不同,将模块分为了 3 大类,分别是:
2.使用require()
方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用。例如:
// 加载内置的fs模块
const fs = require('fs')
//加载用户自定义模块
const zdy = require('./自定义模块.js')
// 加载第三方模块
const moment = require('moment')
注意
:
- 使用 require() 方法加载其它模块时,会执行被加载模块中的代码。比如自定义模块中中有一句
console.log('加载了自定义模块!');
,则加载进来时,终端会打印这行信息。 - 在使用 require加载用户自定义模块时,可以省略.js的后缀名。
模块作用域
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。
如图,custom中为空。
module
-
在每个
.js
自定义模块中都有一个 module 对象,它里面 存储了和当前模块有关的信息,使用console.log(module)
打印如下:
我们可以使用module.exports
对象,将模块内的成员共享出去,供外界使用。外界用require()
方法导入自定义模块时,得到的就是 module.exports 所指向的对象。
注意:
在一个自定义模块中,默认情况下module.exports = {}
。 -
使用 require()加载自定义模块时,必须指定以
./
或../
开头的路径标识符。且导入的结果永远以 module.exports 指向的对象为准。如图,module.export
指向了一个最新的对象,则require得到的模板的内容则是module.export
最新的对象。
-
由于 module.exports 单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了 exports 对象。默认情况下,exports 和 module.exports 指向同一个对象。
-
下面我们来分析
exports
和module.exports
的使用误区。记住使用require() 模块时得到的永远是 module.exports 指向的对象:
CommonJS模块化规范
Node.js
遵循了 CommonJS
模块化规范,CommonJS 规定了模块的特性和各模块之间如何相互依赖。
npm与包
-
不同于
Node.js
中的内置模块与自定义模块,包是由第三方个人或团队开发出来的。 -
由于
Node.js
的内置模块仅提供了一些底层的 API,导致在基于内置模块进行项目开发的时,效率很低。包是基于内置模块封装出来的,提供了更高级、更方便的 API,极大的提高了开发效率。 -
我们可以在这个链接搜索自己需要的包,然后在电脑终端上执行
npm install xx
或者npm i xx
下载 ( xx表示包,比如moment。)
注意
:在默认情况下,使用npm install
命令安装包的时候,会自动安装最新版本的包。如果需要安装指定版本的包,可以在包名之后,通过 @ 符号指定具体的版本,例如:npm i moment@2.24.0
-
初次装包完成后,在项目文件夹下多一个叫做
node_modules
的文件夹和package-lock.json
、package.json
的配置文件。package.json
的配置文件,用来记录项目中安装了哪些包。从而方便剔除node_modules
(体积很大)目录之后,在团队成员之间共享项目的源代码。
-
包的版本号是以“点分十进制”形式进行定义的,总共有三位数字,例如
2.24.0
。其中每一位数字所代表的的含义如下:
注意
:版本号提升的规则:只要前面的版本号增长了,则后面的版本号归零。 -
package.json
文件中,有一个dependencies
节点,专门用来记录您使用 npm install 命令安装了哪些包。
如果我们拿到的文件没有node_modules
,则可以通过npm i
或者npm install
命令下载第三方包。执行该命令时,npm包管理工具
会先读取package.json中的dependencies节点,读取到记录的所有依赖包名称和版本号之后,会把这些包一次性下载到项目中。 -
可以运行
npm uninstall
命令,来卸载指定的包。npm uninstall
命令执行成功后,会把卸载的包,自动从 package.json 的 dependencies 中移除掉。
-
devDependencies
节点
可以使用如下的命令,将包记录到devDependencies
节点中:
//两种写法相同 -D和包名的位置可以互换
npm i 包名 -D
npm install 包名 --save-dev
如果不知道放在 devDependencies
节点中还是 Dependencies
节点中,可以到https://www.npmjs.com/
中查看相关包的下载说明。比如webpack
的下载是需要放在 devDependencies
节点中的。
9. 在使用 npm 下包的时候,默认是从国外的 https://registry.npmjs.org/ 服务器进行下载的,下包速度会很慢。如果解决该问题,可查看该博客链接
包的分类
使用 npm 包管理工具
下载的包,共分为两大类,分别是:项目包
、全局包
- 项目包:那些被安装到项目的 node_modules 目录中的包。
- 全局包:在执行 npm install 命令时,如果提供了 -g 参数,则会把包安装为全局包。
全局包会被安装到C:\Users\用户目录\AppData\Roaming\npm\node_modules
目录下。
注意
:只有工具性质的包,才有全局安装的必要性。因为它们提供了好用的终端命令,比如nrm
。判断某个包是否需要全局安装后才能使用,可以参考官方提供的使用说明即可。
i5ting_toc
i5ting_toc
是一个可以把md 文档
转为 html 页面
的小工具,使用步骤如下:
- 执行命令
npm install -g i5ting_toc
,将其安装为全局包 - 执行命令
i5ting_toc -f day.md -o
,day.md
指要转化的md文档
,-o
参数表示要在转化成功后直接打开浏览器查看html页面
模块的加载机制
优先从缓存中加载
模块在第一次加载后会被缓存。 这也意味着多次调用 require()
不会导致模块的代码被执行多次。
注意:
不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
内置模块的加载机制
内置模块是由 Node.js
官方提供的模块,内置模块的加载优先级最高。例如,require('fs')
始终返回内置的fs 模块
,即使在 node_modules 目录下有名字相同的包也叫做fs
。
自定义模块的加载机制
-
使用
require()
加载自定义模块时,必须指定以./
或../
开头的路径标识符。如果没有指定./
或../
这样的路径标识符,则 node 会把它当作内置模块或第三方模块进行加载。
-
在使用
require()
导入自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下的文件:
第三方模块的加载机制
- 如果传递给
require()
的模块标识符不是一个内置模块,也没有以 ‘./’ 或 ‘…/’ 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。 - 如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
目录作为模块
当把目录作为模块标识符,传递给 require()
进行加载的时候,有三种加载方式:
6. 进阶学习
Express——基于 Node.js 平台的 web 应用开发框架链接