Node基础
一、初始Node.js
1. 什么是Node.js
Node.js is a JavaScript runtime
built on Chrome’s V8 JavaScript engine
Node.js
是一个基于Chrome V8 引擎的 JavaScript运行环境
Node.js的官网地址:https://nodejs.org/zh-cn/
2. Node.js中的JavaScript运行环境
注意:
浏览器
是JavaScript的前端运行环境
Node.js
是JavaScript的后端运行环境
- Node.js中
无法调用
DOM和DOM等浏览器内置API
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
4. Node.js怎么学
浏览器中的JavaScript学习路劲:
JavaScript基础语法 + 浏览器内置API(DOM + BOM)+第三方库(jQuery、art-template等)
Node.js的学习路劲:
JavaScript基础语法 + Node.js内置API模块(fs、path、http等)+ 第三方API(express、mysql等)
5. Node.js环境的安装
如果希望通过Node.js来运行JavaScript代码,则必须在计算机上安装Node.js环境才行。
安装包可以从Node.js的官网首页直接下载,进入到Node.js的官网首页(下载 | Node.js 中文网 (nodejs.cn)),点击绿色的按钮,下载所需要的版本后,双击直接安装即可
二、fs 文件系统模块
1. 什么是fs文件系统模块
fs模块
是Node.js官方提供的、用来操作文件的模块。他提供了一系列的方法和属性,用来满足用户对文件的操作需求
例如:
fs.readFile()
方法,用来读取
指定文件中的内容fs.writeFile()
方法,用来向指定文件中写入
内容
如果要在JavaScript代码中,使用fs模块用操作文件,则需要使用如下的方式到导入它:
const fs = require('fs')
2. 读取指定文件中的内容
2.1 fs.readFile()的语法格式
使用fs.readFile()方法,可以读取指定文件中的内容,语法格式如下:
fs.readFile(path[,options],callback)
参数解读:
- 参数1:
必选
参数,字符串,表示文件的路径。 - 参数2:可选参数,表示以什么
编码格式
来读取文件。 - 参数3:
必选
参数,文件读取完成后,通过回调函数拿到读取的结果。
2.2 fs.readFile()的示例代码
以utf8的编码格式,读取指定文件的内容,并打印err和dataStr的值:
// 1.导入 fs模块,来操作文件
const fs = require('fs')
// 2. 调用fs.readFile()方法读取文件
// 参数1:读取文件的存放路径
// 参数2: 读取文件时候采用的编码格式,一般默认指定utf8
// 参数3:回调函数,拿到读取失败和成功的结果 err dataStr
// 2.1 打印失败的结果
// 如果读取成功,则err的值为null
// 如果读取失败,则err的值我错误对象,dataStr的值为 undefined
fs.readFile('./files/1.txt','utf8',(err,dataStr) => {
console.log(err);
console.log('---------');
console.log(dataStr);
})
2.3 判断文件是否读取成功
可以判断err对象是否为null,从而知晓文件读取的结果:
const fs = require('fs')
fs.readFile('./files/1.txt','utf8',(err,dataStr) => {
// 判断文件是否读取成功
if(err) {
return console.log('文件读取失败!' + err.message);
}
console.log('文件读取成功!' + dataStr);
})
3. 向指定的文件中写入内容
3.1 fs.writeFile()语法格式
使用fs.writeFile()方法,可以向指定的文件中写入内容,语法格式如下:
fs.writeFile(file,data[,options],callback)
参数解读:
- 参数1:必选参数,需要指定一个文件路径的字符串,表示文件的存放路径
- 参数2:必选参数,表示要写入的内容
- 参数3:可选参数,表示以什么格式写入文件内容,默认值是utf8
- 参数4:必选参数,文件写入完成后的回调函数
3.2 fs.writeFile()的示例代码
// 1.导入fs文件系统模块
const fs = require('fs')
// 2.调用 fs.writeFile()方法,写入文件的内容
// 参数1:表示文件的存放路径
// 参数2:表示哟啊写入的内容
// 参数3:回调函数,拿到读取失败和成功的结果
let str = '你好,Node.js'
fs.writeFile('./files/2.txt',str, err => {
// 2.1 如果文件写入成功,则err的值等于null
// 2.2 如果文件写入失败, 则err的值等于一个 错误对象
console.log(err);
})
3.3 判断文件是否写入成功
// 1.导入fs文件系统模块
const fs = require('fs')
// 2.调用 fs.writeFile()方法,写入文件的内容
// 参数1:表示文件的存放路径
// 参数2:表示哟啊写入的内容
// 参数3:回调函数,拿到读取失败和成功的结果
let str = '你好,Node.js'
fs.writeFile('./files/2.txt',str, err => {
// 2.1 如果文件写入成功,则err的值等于null
// 2.2 如果文件写入失败, 则err的值等于一个 错误对象
if(err) {
return console.log('文件写入失败!' + err.message);
}
console.log('文件写入成功!');
})
4. 练习- 考试成绩整理
// 1. 导入fs文件系统模块
const fs = require('fs')
// 2. 调用 fs.readFile()读取文件内容
fs.readFile('./files/成绩.txt','utf8',(err,dataStr) => {
// 3. 判断文件是否读取成功
if(err) {
return console.log('文件读取失败!' + err.message)
}
// 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,err => {
if(err) {
return console.log('写入文件失败!' + err.message)
}
console.log('写入文件成功!');
})
})
5. fs模块 - 路径动态拼接的问题
在使用fs模块操作文件时,如果提供的操作路径是以./
或../
开头的相对路径
时,很容易出现路径动态拼接错误的问题
原因:代码在运行的时候,会以执行node命令时所处的目录
,动态拼接出被操作文件的完整路径
解决方案:在使用fs模块模块操作文件时,直接提供完整的路径,不要提供./或…/开头的相对路径,从而防止路径动态拼接的问题
// __dirname 表示当前文件所处的目录
// 1. 导入fs文件系统模块
const fs = require('fs')
fs.readFile(__dirname + '/files/1.txt','utf8', (err,dataStr) =>{
if(err) {
return console.log('读取文件失败!' + err.message)
}
console.log('读取文件成功!' + dataStr);
})
三、path路径模块
1. 什么是path路径模块
path模块
是Node.js官方提供的、用来处理路径
的模块。他提供了一些列的方法和属性,用来满足用户对路径的处理需求。
例如:
-
path.join()
方法,用来将多个路径片段拼接成一个完整的路径字符串
-
path.basename()
方法,用来从路径字符串中,将文件名解析出来
如果要在JavaScript代码中,使用path模块来处理路径,则需要使用如下的方式先导入它:
const path = require('path')
2. 路径拼接
2.1 path.join()
的语法格式
使用path.join()方法,可以把多个路径片段拼接为完整的路径字符串,语法格式如下:
path.join([...paths])
参数解读:
- …paths路径片段的序列
- 返回值:
2.2 path.join()
的代码示例
使用path.join()方法,可以把多个路径片段拼接为完整的路径字符串:
const path = require('path')
const fs = require('fs')
// 注意: ../ 会抵消前面的路径
const pathStr = path.join('/a','/b/c','../../','./d','e')
console.log(pathStr); // \a\d\e
fs.readFile(path.join(__dirname,'./files/成绩-ok.txt'),'utf8',(err,dataStr) => {
if(err) {
return console.log(err.message);
}
console.log(dataStr);
})
注意:今后凡是涉及到路径拼接的操作,都要使用path.join()方法进行处理。
不要直接使用 + 进行字符串的拼接
3. 获取路径中的文件名
3.1 path.basename()的语法格式
使用path.basename()方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名,语法格式如下:
path.basename(path[,ext])
参数解读:
- path必选参数,表示一个路径的字符串
- ext可选参数,表示文件扩展名
- 返回:表示路径中的最后一部分
3.2 path.basename()的代码示例
使用path.basename()方法,可以从一个文件路径中,获取到文件的名称部分:
const path = require('path')
// 定义文件存放路径
const fpath = './files/1.txt'
const fullName = path.basename(fpath)
console.log(fullName);
const nameWithoutExt = path.basename(fpath,'.txt')
console.log(nameWithoutExt);
4. pathextname()的语法格式
4…1
使用path.extname()方法,可以获取路径中的扩展名部分,语法格式如下:
path.extname(path)
参数解读:
- path必选参数,表示一个路径的字符串
- 返回:返回得到的扩展名字符串
4.2 path.extname()的代码示例
使用path.extname()方法,可以获取路径中的扩展名部分:
const path = require('path')
// 这里是文件的存放路径
const fpath = './path.basename()使用.js'
const fext = path.extname(fpath)
console.log(fext);
5.综合案例
// 1.1 导入 fs 模块
const fs = require('fs')
// 1.2 导入 path 模块
const path = require('path')
// 1.3 定义正则表达式,分别匹配<style></style> 和<script></script>
const regStyle = /<style>[\s\S]*<\/style>/
const regScript = /<script>[\s\S]*<\/script>/
// 2.1 调用 fs.readFile() 方法读取文件
fs.readFile(path.join(__dirname,'./files/index.html'),'utf8',(err,dataStr) => {
// 2.2 读取 HTML 文件失败
if(err) {
return console.log('读取HTML文件失败!' + err.message)
}
// 2.3 读取文件成功后,调用对应的三个方法,分别拆解除css,js,读取HTML文件
resolveCss(dataStr)
resolveScript(dataStr)
resolveHTML(dataStr)
})
// 3.1 定义处理css样式的方法
function resolveCss(htmlStr) {
// 3.2 使用正则提取需要的内容
const r1 = regStyle.exec(htmlStr)
// 3.3 将提取出来的样式字符串,进行字符串的replace替换操作
const newCss = r1[0].replace('<style>','').replace('</style>','')
// 3.4 调用 fs.writeFile()方法,将提取的方式,写入到clock目录中idnex.css的文件里面
fs.writeFile(path.join(__dirname,'./clock/index.css'),newCss, err => {
if(err) {
return console.log('写入CSS样式失败!' + err.message)
}
console.log('写入样式成功');
})
}
// 4.1 定义处理js脚本的方法
function resolveScript (htmlStr) {
// 4.2 使用正则提取需要的内容
const r2 = regScript.exec(htmlStr)
// 4.3 将提取出来的脚本字符串,进行字符串的replace替换操作
const newScript = r2[0].replace('<script>','').replace('</script>','')
// 4.4 调用 fs.writeFile()方法,将提取的脚本,写入到clock目录中index.js的文件里面
fs.writeFile(path.join(__dirname,'./clock/index.js'),newScript, err => {
if(err) {
return console.log('写入JS脚本失败!' + err.message)
}
console.log('写入JS脚本成功!');
})
}
// 5.1 定义处理HTML 结构的方法
function resolveHTML(htmlStr) {
// 5.2 将字符串调用replace方法,把内嵌的style和script标签,替换为外联的link和script标签
const newHTML = htmlStr.replace(regStyle,'<link rel = "stylesheet" href = "./index.css"/>')
.replace(regScript,'<script src="./index.js"></script>')
// 5.3 写入 index.html这个文件
fs.writeFile(path.join(__dirname,'./clock/index.html'),newHTML,err => {
if(err) {
return console.log('写入HTML文件失败!' + err.message)
}
console.log('写入HTML文件成功!');
})
}
- 案例的
两个注意点
- fs.writeFile()方法只能用来创建文件,不能用来创建路径
- 重复调用fs.writeFile()写入同一个文件,新写入的内容会覆盖之前的旧内容
四、http模块
1. 什么是http模块
回顾:什么是客户端
、什么是服务器
?
在网络节点中,负责消费资源的电脑,叫做客户端;负责对外提供网络资源的电脑
,叫做服务器。
http模块
是Node.js官方提供的、用来创建web服务器
的模块。通过http模块提供的http.createServer()
方法,就能方便的把一个普通的电脑,变成一台Web服务器,从而对外提供Web资源服务。
如果要希望使用http模块创建Web服务器,则需要先导入他:
const http = require('http')
2. http模块的作用
服务器和普通电脑的区别
在于,服务器上安装了web服务器
软件,例如:IIS、Apache
等。通过安装这些服务器软件,就能把一台普通的电脑变成一台web服务器
在Node.js中,我们不需要使用
IIS、Apache等这些第三方web服务器软件
。因为弥望可以基于Node.js提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件
,从而对外提供web服务
3. 服务器相关概念
3.1 IP地址
IP地址
就是互联网上每台计算机的唯一地址
,因此IP地址具有唯一性,如果把"个人电脑"比作"一个电话",那么"IP地址"就相当于"电话号码",只有在知道对方IP地址的前提下,才能与对应的电脑之间进行数据通信。
IP地址的格式:通常用"点分十进制
"表示成(a.b.c.d
)的形式,其中,a,b,c,d都是0~255之间的十进制整数。如果:用点分十进制表示的IP地址(192.168.1.1)
注意:
互联网中每台Web服务器,都有之间的IP地址
,例如:大家可以在Windows的终端运行ping www.baidu.com命令,即可查看百度服务器的IP地址- 在开发期间,自己的电脑即是一台服务器,也是一个客户端,为了方便测试,可以在自己的浏览器中输入127.0.0.1这个IP地址,就鞥把自己的电脑当做一台服务器进行访问了
3.2 域名
和域名服务器
尽管IP地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观
,而且不便于记忆
,于是人们又发明了另一套字符型
的地址方案
,即所谓的域名(Domain Name)地址
IP地址
和域名
是一一对应的关系
,这份对应关系存放在一种叫做域名服务器
(DNS,Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供IP地址和域名之间的转换服务器的服务器
注意:
- 单纯使用IP地址,互联网中的电脑也能够正常工作。但是有了域名的加持,能让互联网的世界变得更加方便。
- 在开发测试期间,
127.0.0.1
对应的域名是localhost
它们都代表我们之间的这台电脑,在使用效果上没有任何区别
3.3 端口号
计算机中的端口号,就好像是现实生活中的门牌号用于。通过门牌号,外卖小哥可以在整栋大楼从多的房间中,准确把外卖送到你的手中。
同样的道理,在一台电脑中,可以运行成百上千个web服务。每个web服务器都对应一个唯一的端口号。客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的web服务
进行处理。
注意:
- 每个端口号不能同时被多个web服务占用
- 在实际应用中,URL中的
80端口可以被省略
4. 创建最基本的web服务器
4.1 创建web服务器的基本步骤
- 导入http模块
- 创建web服务器实例
- 为服务器实例绑定
request
事件,监听客户端的请求
- 启动服务器
(1)导入http模块
如果希望在自己的电脑上创建一个web服务器,从而对外提供web服务,则需要导入http模块:
const http = require('http')
(2)创建web服务器实例
调用`http.createServer()方法,即可快速创建一个web服务器实例:
const server = http.createServer()
(3)为服务器实例绑定request事件
为服务器实例绑定request事件,即可监听客户端发送过来的网络请求:
// 使用服务器实例的 .on()方法,为服务器绑定一个request事件
server.on('request',(req,res) => {
// 只要有客户端来请求我们自己的服务器,就会触发request事件,从而调用这个事件处理函数
console.log('Someone visit our web server .')
})
(4)启动服务器
调用服务器实例的.listen()方法,即可启动当前的web服务器实例:
// 调用server.listen(端口号,回调)方法,即可启动web服务器
server.listen(80,() => {
console.log('http server running at http://127.0.0.1')
})
// 1. 导入http模块
const http = require('http')
// 2. 创建web服务器实例
const server = http.createServer()
// 3. 为服务器实例绑定 request事件,监听客户端的请求
server.on('request',function(req,res) {
console.log('Someone visit our web server.');
})
// 4. 启动服务器
server.listen(80,function() {
console.log('server running at http://127.0.0.1');
})
(5)req
请求对象
只要服务器接收到了客户端的请求,就会调用通过server.on()
为服务器绑定的request时间处理函数
。
如果想在事件处理函数中,访问与客户端相关的数据或属性
,可以使用如下方式:
const http = require('http')
const server = http.createServer()
server.on('request', req => {
// req.url 是客户端请求的url地址
const url = req.url
// req.method 是客户端请求的method类型
const method = req.method
const str = `Your request url is ${url},and request method is ${method}`
console.log(str);
})
server.listen(80,function() {
console.log('server running at http://127.0.0.1');
})
(6)res响应对象
在服务器的request事件处理函数中,如果想访问与服务器相关的数据或属性
,可以使用如下方式:
const http = require('http')
const server = http.createServer()
server.on('request', (req,res) => {
// req.url 是客户端请求的url地址
const url = req.url
// req.method 是客户端请求的method类型
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');
})
4.2 解决中文乱码
问题
当调用res.end()方法,向客户端发送中文内容的时候,会出现乱码问题,此时,需要手动设置内容的编码格式
const http = require('http')
const server = http.createServer()
server.on('request',(req,res) => {
// 定义一个字符串,包含中文的内容
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,() => {
console.log('server running at http://127.0.0.1');
})
5. 根据不同的url响应不同的html内容
5.1 核心实现步骤
- 获取
请求的url地址
- 设置
默认的响应内容
为404 Not found- 判断用户请求的是否为
/
或/ index.html
首页- 判断用户请求的是否为
/ about.html
关于页面- 设置
Content-Type响应头
,防止中文乱码- 使用
res.end()
把内容响应给客户端
5.2 动态响应内容
const http = require('http')
const server = http.createServer()
server.on('request',(req,res) => {
// 1. 获取请求的url地址
const url = req.url
// 2. 设置默认的响应内容为 404 Not found
let content = '<h1>404 Not found</h1>'
// 3. 判断用户请求的是否为 / 或 /index.html首页
// 4. 判断用户请求的是否为 /about.html 关于页面
if(url === '/' || url === '/index.html') {
content = '<h1>首页</h1>'
} else if(url === '/about.html') {
content = '<h1>关于页面</h1>'
}
// 5. 设置 Content-Type 响应头,解决中文乱码的问题
res.setHeader('Content-Type','text/html; charset=utf-8')
// 6. 使用 res.end()把内容响应给客户端
res.end(content)
})
server.listen(80,() => {
console.log('server running at http://127.0.0.1');
})
6. 案例 - 实现clock时钟的web服务器
6.1 核心思路
把文件的实际存放路径
,作为
每个资源的请求url地址
6.2 实现步骤
- 导入需要的模块
- 创建基本的web服务器
- 将资源的请求url地址隐射为文件的存放路径
- 读取文件内容并响应给客户端
- 优化资源的请求路径
(1)导入需要的模块
// 1.1 导入http模块
const http = require('http')
// 1.2 导入fs文件系统模块
const fs = require('fs')
// 1.3 导入path路径处理模块
const path = require('path')
(2)创建基本的web服务器
// 2.1 创建web服务器实例
const server = http.createServer()
// 2.2 监听web服务器 request事件,监听客户端的请求
server.on('request',(req,res) => {
})
// 2.3 启动web服务器
server.listen(80,() => {
console.log('server running at http://127.0.0.1');
})
(3)将资源的请求url地址映射为文件的存放路径
// 3.1 获取到客户端请求的url地址
// /clock/index.html
// /clock/index.css
// /clock/index.js
const url = req.url
// 3.2 把调用的URL地址映射为具体文件的存放路径
const fpath = path.join(__dirname,url)
(4)读取文件的内容并响应给客户端
// 4.1 根据 "隐射"过来的文件路径读取文件
fs.readFile(fpath,'utf8',(err,dataStr) => {
// 4.2 读取文件失败后,向客户端应固定的 "错误消息"
if(err) {
return console.log('404 Not fount.');
}
// 4.3 读取文件成功后,将 "读取成功的内容"响应给客户端
res.end(dataStr)
})
(5)优化资源的请求路径
// 5.1 预定义一个空白的文件存放路径
let fpath = ''
if (url === '/') {
fpath = path.join(__dirname, './clock/index.html')
} else {
// /index.html
// /index.css
// /index.js
fpath = path.join(__dirname, '/clock', url)
}
(6)实现代码
// 1.1 导入 http 模块
const http = require('http')
// 1.2 导入 fs 模块
const fs = require('fs')
// 1.3 导入 path 模块
const path = require('path')
// 2.1 创建 web 服务器
const server = http.createServer()
// 2.2 监听 web 服务器的 request 事件
server.on('request', (req, res) => {
// 3.1 获取到客户端请求的 URL 地址
// /clock/index.html
// /clock/index.css
// /clock/index.js
const url = req.url
// 3.2 把请求的 URL 地址映射为具体文件的存放路径
// const fpath = path.join(__dirname, url)
// 5.1 预定义一个空白的文件存放路径
let fpath = ''
if (url === '/') {
fpath = path.join(__dirname, './clock/index.html')
} else {
// /index.html
// /index.css
// /index.js
fpath = path.join(__dirname, '/clock', url)
}
// 4.1 根据“映射”过来的文件路径读取文件的内容
fs.readFile(fpath, 'utf8', (err, dataStr) => {
// 4.2 读取失败,向客户端响应固定的“错误消息”
if (err) return res.end('404 Not found.')
// 4.3 读取成功,将读取成功的内容,响应给客户端
res.end(dataStr)
})
})
// 2.3 启动服务器
server.listen(80, () => {
console.log('server running at http://127.0.0.1')
})
五、模块化
1. 模块化的基本概念
1.1 什么是模块化
(1)现实中的模块化
(2)编程领域中的模块化
编程领域中的模块化,就是遵守固定的规则
,把一个大文件
拆成独立并互相赖
的多个小模块
把代码进行模块化拆分的好处:
- 提高了代码的
复用性
- 提高了代码的
可维护性
- 可以实现
按需加载
1.2 模块化规范
模块化规范
就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。
例如:
- 使用说明样的语法格式来
引用模块
- 在模块中使用什么样的语法格式
向外暴露成员
模块化规范的好处
:大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的互相调用
2. Node.js中的模块化
2.1 Node.js中模块的分类
Node.js中根据模块来源的不同,将模块分成了3个大类,分别是:
内置模块
(内置模块是由Node.js官方提供的,例如 fs、path、http等)自定义模块
(用户创建的每个.js文件,都是自定义模块)第三方模块
(由第三方开发出来的模块
,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载
)
2.2 加载
模块
使用强大的require()方法,可以加载需要的内置模块,用户自定义模块、第三方模块进行使用。例如:
// 1. 加载内置的 fs模块
const fs = require('fs')
// 2. 加载用户的自定义模块
const custom = require('./custom.js')
// 3. 加载第三方模块(关于第三方模块的下载和使用)
const moment = require('moment')
注意:
使用require()方法加载其他模块时,会执行被加载模块中的代码
2.3 Node.js中的模块作用域
(1)什么是模块作用域
和函数作用域
类似,在自定义模块中定义的变量、方法
等成员,只能在当前模块内被访
,这种模块级别的访问限制
,叫做模块作用域
(2)模块作用域的好处
防止了全局变量污染的问题
2.4 向外共享模块作用域中的成员
(1)module对象
在每个.js自定义模块中都有一个module对象,他里面存储了和当前模块有关的信息,打印如下:
(2)module.exports
对象
在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用。
外界用require()
方法导入自义定模块时,得到的就是module.exports所指的对象
(3)共享成员时的注意点
使用 require()方法导入模块时,导入的结果,永远以module.exports指向的对象为准
(4)exports对象
由于module.exports单词写起来比较复杂,为了简化向外共享成员的代码,Node提供了exports
对象。默认情况下,exports和module.exports指向同一个对象
.最终共享的结果,还是以module.exports指向的对象为准
exports = {
username: '小米',
sayHi() {
console.log('Hi!')
}
}
- exports和 module.exports的使用误区
时刻谨记,require()模块时,得到永远是module.exports指向的对象:
2.5 Node.js中的模块化规范
Node.js遵循了CommonJS模块化规范,CommonJS规定了模块的特性
和各模块之间如何互相依赖
CommonJS规定:
- 每个模块内部,
module变量
代表当前模块- module变量是一个对象,它的exports属性(即
module.exports
)是对外的接口
- 加载某个模块,其实是加载该模块的module.exports属性。
require()方法用于加载模块
3. npm与包
3.1 包
(1)什么是包
Node.js中的第三方模块
又叫做包
。
就像电脑
和计算机
指的是相同的东西,第三方模块
和包
指的是同一个概念,只不过叫法不同
(2)包的来源
不同于Node.js中的内置模块与自定义模块,包是由第三方个人或团队开发出来的
,免费供所有人使用。
注意:
Node.js中的包都是免费且开源的,不需要付费即可免费下载使用
(3)为什么需要包
由于Node.js的内置模块仅提供了一些底层的API,导致在基于内置模块进行项目开发的时,效率很低。
包是基于内置模块封装出来的
,提供了更高级、更方便的API,极大的提高了开发效率
。
包
和内置模块
之间的关系,类似于jQuery
和浏览器内置API
之间的关系
(4)从哪里下载包
国外有一家IT公司,叫做npm,lnc.
这家公司旗下有一个非常著名的网站:https://www.npmjs.com/,它是全球最大的包共享平台
,你可以从这个网站上搜索到任何你需要的包,只要你有足够的耐心!
到目前为止,全球约1100多万
的开发人员,通过这个包共享平台,开发并共享了超过120多万个包
供我们使用
npm,lnc.公司
提供了一个地址为https://registry.npmjs.org/的服务器,来对外共享所有的包,我们可以从这个服务器上下载自己所需要的包
3.2 npm初体验
(1)格式化时间的传统做法
- 创建格式化时间的自定义模块
- 定义格式化时间的做法
- 创建补零函数
- 从自定义模块中导出格式化时间的函数
导入格式化时间的自定义模块
调用格式化时间的函数
function dataFormat(dtStr) {
const dt = new Date(dtStr)
let y = padZero(dt.getFullYear())
let m = padZero(dt.getMonth() + 1)
let d = padZero(dt.getDate())
let hh = padZero(dt.getHours())
let mm = padZero(dt.getMinutes())
let ss = padZero(dt.getSeconds())
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}
// 定义补零函数
function padZero(n) {
return n > 9 ? n : '0' + n
}
module.exports = {
dataFormat
}
(2)格式化时间的高级用法
使用npm包管理工具,项目中安装格式化时间的包moment
- 使用require()导入格式化时间的包
- 参考moment的官方API文档对时间进行格式化
(3)在项目中安装包的命令
如果想在项目中安装指定名称的包,需要运行如下的命令:
npm install 包的完整名称
上述的装包命令,可以简写成如下格式:
npm i 完整的包名称
(4)初次装包后多了那些文件
初次装包完成后,在项目文件夹下多一个叫做node_modules
的文件夹和package-lock.json
的配置文件
其中:
node_modules文件夹
用来存放所有已安装到项目中的包
。require()导入第三方包时,就是从这个目录中查找并加载包。
package-lock.json配置文件
用来记录node_modules目录下的每一个包的下载信息
,列如包的名字、版本号、下载地址等
注意:
程序员不要手动修改node_modules或package-lock.json文件中的任何代码,npm包管理工具会自动维护它们
(5)安装指定版本的包
默认情况下,使用npm install 命令安装包的时候,会自动安装最新版本的包
,可以在包名之后,通过@符号
指定具体的版本,例如:
npm i moment@2.22.2
(6) 包的语义化版本规范
包的版本号是以"点分十进制"形式进行定义的,总共有三位数字,例如2.24.0
其中每一位数字所代表的含义如下:
第1位数字:大版本
第2为数字:功能版本
第3为数字:Bug修复版本
版本号提升的规则
:只要前面的版本号增长了,则后面的版本号归零
3.3 包管理配置文件
npm规定,在项目根目录
中,必须
提供一个叫做package.json
的包管理配置文件。用来记录与项目有关的一些配置信息。例如:
- 项目的名称、版本号、描述等
- 项目中都用到了那些包
- 那些包只在
开发期间
会用到 - 那些包在
开发
和部署
时都需要用到
(1)多人协作的问题
(2)如何记录项目中安装了那些包
在项目根目录
中,创建一个叫做package.json
的配置文件,即可用来记录项目中安装了那些包。从而方便剔除node_modules目录之后,在团队成员之间共享项目的源代码
注意:
今后在项目开发中,一定要把node_modules文件夹,添加到.gitignore忽略文件或在那个
(3)快速创建package.json
npm包管理工具提供了一个快捷命令
,可以在执行命令时所处的目录中
,快速创建package.json这个包管理配置文件:
// 作用:在执行命令所处的目录中,快速新建package.json文件
npm init -y
注意:
- 上述命令
只能在英文的目录下成功运行
!所以,项目文件夹的名称一定要使用英文命名,不要使用中文,不能出现空格
。- 运行 npm install命令安装包的时候,npm包管理工具会自动把
包的名称
和版本号
,记录岛package.json中
(4) dependencies节点
package.json 文件中,有一个dependencies
节点,专门用来记录您使用npm install命令安装了那些包
(5)一次性安装所有的包
当我们拿到一个剔除了 node_modules的项目之后,需要先把所有的包下载到项目中,才能将项目运行起来。
否者会报类似于下面的错误:
// 由于项目运行依赖于 moment这个包,如果没有提前安装好这个包,就会报如下的错误:
Error: Cannot find module 'moment'
可以运行 npm install命令 (或 npm i)一次性安装所有的依赖包:
// 运行 npm install 命令时,npm包管理工具会先读取 package.json中的 dependencies节点
// 读取到记录的所有依赖包名称和版本号之后, npm 包管理工具会把这些包一次性下载到项目目录中
npm install
(6)卸载包
可以运行 npm uninstall命令,来卸载指定的包:
// 使用 npm uninstall 具体的包名,来卸载包
npm uninstall moment
注意:npm uninstall 命令执行成功后,会把卸载的包,自动从package.json的dependencies中移除掉
(7)devDependencies
节点
如果某些包只在项目开发阶段
会用到,在项目上线之后不会用到
,则建议把这些包记录到devDependencies节点中。
与之对应的,如果某些包在开发
和项目上线之后
都需要用到,则建议把这些包记录岛dependencies节点中。
您可以使用如下的命令,将包记录到devDependencies节点中:
// 安装指定的包,并记录到devDependencies节点中
npm i 报名 -D
// 注意:上述命令是简写形式,等价与下面完整的写法:
npm install 包名 --save-dev
3.4 解决下包速度慢的问题
(1)为什么下包速度慢
在使用npm下包的时候,默认从国外的https://registry.npmjs.org/服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢
(2)淘宝 NPM镜像服务器
在这里插入图片描述
(3)切换 npm的下包镜像源
下包的镜像源,指的就是下包的服务器地址
# 查看当前的下包镜像源
npm config get registry
# 将下包的镜像源切换为淘宝镜像源
npm config set registry=https://registry.npm.taobdo.org/
# 检查镜像源是否下载成功
npm config get registry
(4)nrm
为了更方便的切换下包的镜像源,我们可以装nrm这个小工具,里面nrm提供的终端命令,可以快速查看和切换下包的镜像源.
# 通过 npm 包管理器,将nrm安装为全局可用的工具
npm i nrm -g
# 查看所有可用的镜像源
npm ls
# 将下包的镜像源切换为 taobao镜像
nrm use taobao
3.5 包的分类
使用npm 包管理工具下载的包,共分为两大类,分别是:
- 项目包
- 全局包
(1)项目包
那些被安装到项目
的node_modules
目录中的包,都是项目包
项目包又分为两类,分别是:
开发依赖包
(被记录到devDependencies
节点中的包,只在开发期间会用到)核心依赖包
(被记录到dependencies
节点中的包,在开发期间和项目上线之后都会用到)
npm i 包名 -D #`开发依赖包`(被记录到`devDependencies`节点中的包,只在开发期间会用到)
npm i 包名 # `核心依赖包`(被记录到`dependencies`节点中的包,在开发期间和项目上线之后都会用到)
(2)全局包
在执行 npm install 命令时,如果提供了 -g
参数,则会把包安装为全局包
全局包会被安装到C:\User\用户目录\AppData\Roaming\npm\node_modules
目录下:
npm i 包名-g # 全局安装指定的包
npm uninstall 包名 -g # 卸载全局安装的包
注意:
- 只有
工具性质的包
,才有全局安装的必要性,因为他们提供了好的终端命令- 判断某个包是否需要全局安装后能使用,可以
参考官方提供的使用说明
即可
(3)i5ting_toc
i5ting_toc是一个开源把md文档转为html页面的小工具,使用步骤如下:
# 将i5ting_toc 安装诶全局包
npm install -g i5ting_toc
# 调用 i5ting_toc,轻松实现md转html的功能
i5ting_toc -f 要转换的md文件路径 -o
3.6 规范的包结构
在清除了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构
一个规范的包,他的组成结构,必须符合一下3点要求:
- 包必须以
单独的目录
而存在- 包的顶级目录下要必须包含
package.json
这个包管理配置文件- package.json 中必须包含
name,version,main
这三个属性,分别代表包的名字、版本号、包的入口
注意:以上3点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:
https://yarnpkg.com/zh-Hans/docs/package-json
3.7 开发属于自己的包
(1)需要实现的功能
- 格式化日期
- 转义HTML中的特殊字符
- 还原HTML中的特殊字符
// 1. 导入直接的包
const itheima = require('itheima-utils')
// ----功能3:还原HTML中的特殊字符----
const rawHTML = itheima.htmlUnEscape(str)
console.log(rawHTML)
(2)初始化包的基本结构
- 新增itheima-tools文件夹,作为包的根目录
- 在itheima-tools文件夹中,新建如下三个文件:
- package.json(包管理配置文件)
- index.js(包的入口文件)
- README.md(包的说明文档)
(3)初始化 package.json
{
"name": "itheima-tools",
"version": "1.0.0",
"main": "index.js",
"description": "提供了格式化时间、HTMLEscape相关的功能",
"keywords": [
"itheima",
"dateFormat",
"escape"
],
"license": "ISC"
}
关于更多license许可协议相关的内容,可参考https://www.jianshu.com/p/86254523e898
(4)在index.js中定义格式化时间的方法
function dataFormat(dtStr) {
const dt = new Date(dtStr)
let y = padZero(dt.getFullYear())
let m = padZero(dt.getMonth() + 1)
let d = padZero(dt.getDate())
let hh = padZero(dt.getHours())
let mm = padZero(dt.getMinutes())
let ss = padZero(dt.getSeconds())
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}
// 定义补零函数
function padZero(n) {
return n > 9 ? n : '0' + n
}
module.exports = {
dataFormat
}
(5)在index.js中定义转义HTML的方法
// 定义转义 HTML字符函数
function htmlEscape(htmlstr) {
return htmlstr.replace(/<|>|"|&/g, match => {
switch(match) {
case '<':
return "<"
case '>':
return ">"
case '"':
return """
case '&':
return "&"
}
})
}
(6)在index.js中定义还原HTML的方法
// 定义还原 HTML 字符函数
function htmlUnescape(str) {
return str.replace(/<|>|"|&/g, match => {
switch(match) {
case '<':
return "<"
case '>':
return ">"
case '"':
return '"'
case '&':
return "&"
}
})
}
(7)不同的功能进行模块化拆分
- 将格式化时间的功能,拆分到 src > dateFormat.js中
- 将处理HTML字符串的功能,拆分到 src > htmlEscape.js中
- 在index.js中,导入两个模块,得到需要向外共享的方法
- 在index.js中,使用module.exprts把对应的方法共享出去
(8)编写包的说明文档
包根目录中的README.md
文件,是包的使用说明文档
。通过他,我们可以事先把包的使用说明,以markdown的格式写出来,方便用户参考
README文件中具体写说明内容,没有强制的要求;只要能够清晰地把包的作用、用法、注意事项等描述清除即可。
我们所创建的这个包的README.md文档中,会包含以下6项内容:
安装方式、导入方式、格式化时间、转义HTML中的特殊字符、还原HTML中的特殊字符、开源协议
## 安装
```
npm install itheima-tools
```
## 导入
```js
const itheima = require('itheima-tools')
```
## 格式化时间
```js
// 调用 dateFormat 对时间进行格式化
const dtStr = itheima.dateFormat(new Date())
// 结果 2022-08-04 17:20:58
console.log(dtStr)
```
## 转义 HTML 中的特殊字符
```js
// 带转换的 HTML 字符串
const htmlStr = '<h1 title="abc">这是h1标签<span>123 </span></h1>'
// 调用 htmlEscape 方法进行转换
const str = itheima.htmlEscape(htmlStr)
// 转换的结果 <h1 title="abc">这是h1标签<span>123&nbsp;</span></h1>
console.log(str)
```
## 还原 HTML 中的特殊字符
```js
// 待还原的 HTML 字符串
const str2 = itheima.htmlUnEscape(str)
// 输出的结果 <h1 title="abc">这是h1标签<span>123 </span></h1>
console.log(str2)
```
## 开源协议
ISC
3.8 发布包
(1)注册npm账号
- 访问https://www.npmjs.com/网站,点击
sign up
按钮,进入注册用户界面- 填写账号相关的信息:Full Name、
Public Email、Username、Password
- 点击
Create an Acount
按钮,注册账号- 登陆邮箱,
点击验证链接
,进行账号的验证
(2)登陆npm账号
npm账号注册完成后,可以在终端中执行npm login
命令,依次输入用户名、密码、邮箱后,即可登陆成功
(3)把包发布到npm上
将终端切换到包的根目录之后,运行npm publish
命令,即可将包发布到npm上 (注意:包名不同雷同
)
(4)删除已发布的包
运行npm unpublish 包名 --force命令,即可从npm删除已发布的包.
注意:
- npm unpublish命令只能删除
72小时以内
发布的包- npm unpublish删除的包,在
24小时内
不允许重复发布- 发布包的时候要慎重,
尽量不要往npm上发布没有意义的包
!
4. 模块的加载机制
4.1 优先从缓存中加载
模块在第一次加载后悔被缓存
。这也意味着多次调用require()
不会导致模块的代码被执行多次。
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率
4.2 内置模块
的加载机制
内置模块是由Node.js官方提供的模块,内置模块的加载优先级最高
。
例如,require(‘fs’)始终返回内置的fs模块,即使在node_modules目录下有名字相同的包也叫做fs。
4.3 自定义模块
的加载机制
使用 require() 加载自定义模块时,必须指定以./
或../
开头的路径标识符
。在加载自定义模块时,如果没有指定./或…/这样的路径标识符,则node会把他当做内置模块
或第三方模块
进行加载
同时,在使用require()导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序
分别尝试加载以下的文件:
- 按照
确切的文件名
进行加载- 补全
.js
扩展名进行加载- 补全
.json
扩展名进行加载- 补全
.node
扩展名进行加载- 加载失败,终端报错
4.4 第三方模块的加载机制
如果传递给require()的模块标识符不是一个内置模块,也没有以’./‘或’…/'开头,则Node。js会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块
如果没有找到对应的第三方模块,则移动到再上一层父目录或在那个,进行加载,直到文件系统的根目录。
例如,假设在’C:\Users\itheima\project
\foo.js’文件里调用了require('tools')
,则Node.js会按一下顺序查找:
C:\Users\itheima\projet
\node_modules\toolsC:\Users\ithiema
\node_modules\toolsC:\Users
\node_moduels\toolsC:
\node_modules\tools
4.5 目录作为模块
当把目录作为模块标识符,传递给require()进行加载的时候,有三种加载方式:
- 在被加载的目录下查找一个叫做package.json的文件,并寻找main属性,作为requier()加载的入口
- 如果目录里没有package.json文件,或者main入口不存在或无法解析,则Node.js将会试图加载目录下的
index.js文件
- 如果以上两步都失败了,则Node.js会在终端打印错误消息,报告模块的缺失: Error: Cannot find module ‘xxx’
同雷同`)
(4)删除已发布的包
运行npm unpublish 包名 --force命令,即可从npm删除已发布的包.
注意:
- npm unpublish命令只能删除
72小时以内
发布的包- npm unpublish删除的包,在
24小时内
不允许重复发布- 发布包的时候要慎重,
尽量不要往npm上发布没有意义的包
!
4. 模块的加载机制
4.1 优先从缓存中加载
模块在第一次加载后悔被缓存
。这也意味着多次调用require()
不会导致模块的代码被执行多次。
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率
4.2 内置模块
的加载机制
内置模块是由Node.js官方提供的模块,内置模块的加载优先级最高
。
例如,require(‘fs’)始终返回内置的fs模块,即使在node_modules目录下有名字相同的包也叫做fs。
4.3 自定义模块
的加载机制
使用 require() 加载自定义模块时,必须指定以./
或../
开头的路径标识符
。在加载自定义模块时,如果没有指定./或…/这样的路径标识符,则node会把他当做内置模块
或第三方模块
进行加载
同时,在使用require()导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序
分别尝试加载以下的文件:
- 按照
确切的文件名
进行加载- 补全
.js
扩展名进行加载- 补全
.json
扩展名进行加载- 补全
.node
扩展名进行加载- 加载失败,终端报错
4.4 第三方模块的加载机制
如果传递给require()的模块标识符不是一个内置模块,也没有以’./‘或’…/'开头,则Node。js会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块
如果没有找到对应的第三方模块,则移动到再上一层父目录或在那个,进行加载,直到文件系统的根目录。
例如,假设在’C:\Users\itheima\project
\foo.js’文件里调用了require('tools')
,则Node.js会按一下顺序查找:
C:\Users\itheima\projet
\node_modules\toolsC:\Users\ithiema
\node_modules\toolsC:\Users
\node_moduels\toolsC:
\node_modules\tools
4.5 目录作为模块
当把目录作为模块标识符,传递给require()进行加载的时候,有三种加载方式:
- 在被加载的目录下查找一个叫做package.json的文件,并寻找main属性,作为requier()加载的入口
- 如果目录里没有package.json文件,或者main入口不存在或无法解析,则Node.js将会试图加载目录下的
index.js文件
- 如果以上两步都失败了,则Node.js会在终端打印错误消息,报告模块的缺失: Error: Cannot find module ‘xxx’