黑马程序员Node.js与内置模块 基础知识点复盘
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
第一章 初识Node
第二章 fs模版
第三章 path模版
第三章 时钟案例
第四章 http模版
第五章 模块化
第六章 包与npm
第七章 包管理配置文件
第八章 express
第九章 路由
第十章 中间件
第十一章 编写接口
第十二章 跨域
注意:写的过程中没有按章节写,但内容都包含在内
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
目标
提示:这里可以添加本文要记录的大概内容:
学习完html、css3、JavaScript 进阶学习Node.js,你会知道node.js是什么?可以做什么?node.js中JavaScript的组成部分?fs模块读写操作文件?path模块处理路径?http模块写一个基本web服务器?
提示:以下是本篇文章正文内容,下面案例可供参考
一、 初识Node
1.1回顾思考
1.11技术支持:HTML CSS JavaScript
1.13.为什么JavaScrip可以在浏览器中被执行?
因为在浏览器中待执行的js代码可以被浏览器中的JavaScript解析引擎 解析执行
1.14.为什么JavaScript可以操作DOW和BOW ?
因为每个浏览器内置了DOW,BOW,Ajax的API函数,因此JavaScript才可以调用
1.15.V8引擎负责解析执行JavaScript代码 内置API是由运行环境提供的特殊接口,只能在所属的运行环境中被调用
1.16.使用JavaScript能否做后端开发
借助node.js作为运行环境可以做后端开发
1.2 什么是node.js?
Node.js是一个基于Chrome V8引擎的JavaScript运行环境
浏览器是JavaScript的前端运行环境
node.js是JavaScript的后端运行环境
node.js 中无法调用BOW,DOW 等浏览器内置API
1.3 Node.js可以做什么?
1.Express框架 构建web应用(http://www.expressjs.com.cn/)
2.Electron框架 构建跨平台的桌面应用(https://electronjs.org/)
3.restify框架 构建API接口项目(http://restify.com/)
4.读写和操作数据库、创建实用的命令行工具辅助前端开发
1.4 怎么学?
JavaScript学习路径:JavaScript语法+浏览器内置API(DOW/BOW)+第三方库(jquery,art-template)
Node.js学习路径:JavaScript语法+node.js内置API(fs/path/http)+第三方API模块(express,mysql)
1.5安装node.js
官网:https://nodejs.org/en 建议下载LTS 版本node.js安装即可(建议安装路径不变,一直点下一步,直到finish)
判断是否成功:
打开终端—输入node -v 如果有版本显示 表明安装成功
1.6 在node.js环境中执行JavaScript代码
1.用记事本写一个js文件
2.打开终端cd进文件所在目录运行js代码
C:\Users\29279>cd C:\node.js基础\day01\code
C:\node.js基础\day01\code>node 1.js
hello node.js
C:\node.js基础\day01\code>
1.7终端快捷键
键盘上箭头 快速定位上一次执行的命令
tab键 快速补全路径
esc键 清空输入命令
cls键 清空当前终端的所有命令
二、fs文件系统模块
2.1 什么是fs文件系统模块?
fs模块是官方提供用来操作文件的模块
fs.readFile()方法 读取文件内容
fs.writeFile()方法 写入文件内容
使用需要导入fs模块
代码如下(示例):
const fs = require('fs')
2.2 读取和写入文件
2.21使用 fs.readFile()方法
代码如下(示例):
const fs = require('fs')//导入fs模块
//调用fs.readFile()方法读取文件
//参数1:文件路径
// ~2:读取文件采用的编码格式 默认utf-8
// ~3:回调函数 拿到文件读取失败或成功的结果
fs.readFile('./file/file.text','utf-8',function(err,dataStr){
//打印失败的结果
//成功,err值为null
//失败:err的值为 错误对象 datastr的值为undefined
console.log(err)
console.log('--------')
//打印成功的结果
console.log(dataStr)
})
2.22判断文件是否读取成功
代码如下(示例):
const fs =require('fs')
fs.readFile('./file/file.text','utf-8',function(err,dataStr){
if(err){
return console.log('文件读取失败!'+err.message)
}
console.log('文件读取成功!'+dataStr)
})
2.23使用 fs.writeFile()方法
代码如下(示例):
const fs = require('fs')
//调用fs.writeFile()方法,写入文件内容
//参数1:文件存放路径
//~2:要写入的内容
//~3:回调函数
fs.writeFile('./file/file.text','I hade crash on you !',function(err){
//如果文件写入成功:err的值等于null
//失败:err的值为一个错误对象
if(err){
return console.log('文件写入失败'+err.message)
}
console.log('文件写入成功!')
})
2.24 整理成绩案例
代码如下(示例):
//1.导入fs模块
const fs = require('fs')
//2.调用fs.readFile()读取文件内容
fs.readFile('./file/成绩.text','utf-8',function(err,dataStr){
//3.判断是否读取成功
if(err){
return console.log('文件读取失败!'+err.message)
}
// console.log('文件读取成功'+dataStr)
//4.1 先把成绩的数据,按照空格进行分割
const arrOld = dataStr.split(' ')
//console.log(arrOld)
//4.2 循环分割后的数组,对每一项数据,进行字符串的替换操作
const arrNew = []
arrOld.forEach(item =>{
arrNew.push(item.replace('=',':'))
})
//console.log(arrNew)
//4.3把数组中的每一项,进行合并,得到一个新的字符串 \r\n回车换行
const newStr = arrNew.join('\r\n')
// console.log(newStr)
//将处理完成的成绩数据,调用fs.writeFlie()方法,写入到新文件 成绩-ok.text中
fs.writeFile('./file/成绩-ok.text',newStr,function(err){
if(err){
return console.log('文件写入失败!'+err.message)
}
console.log('文件写入成功!\r\n'+newStr)
})
})
2.3 fs模块-路径动态拼接
2.31 路径动态拼接问题
在使用fs模块操作文件时,如果提供的操作路径是以./或…/开头的相对路径时,很容易出现路径动态拼接错误问题。
原因:代码运行时,会以执行node命令时所处的目录,动态拼接处被操作文件的完整路径
代码如下(示例):
const fs = require('fs')
fs.readFile('./file/file.text','utf-8',function(err,dataStr){
if(err){
return console.log('文件读取失败!'+err.message)
}
console.log('文件读取成功!'+dataStr)
})
PS C:\node.js基础\day01\code> cd ../
PS C:\node.js基础\day01> node '.\code\1-05 演示路径错误问题.js'
文件读取失败!ENOENT: no such file or directory, open 'C:\node.js基础\day01\file\file.text'
PS C:\node.js基础\day01> cd ../
PS C:\node.js基础> node '.\day01\code\1-05 演示路径错误问题.js'
文件读取失败!ENOENT: no such file or directory, open 'C:\node.js基础\file\file.text'
PS C:\node.js基础>
2.32 解决路径动态拼接问题:直接提供一个完整的文件(绝对路径)存放路径
缺点:移植性差,不利于维护
代码如下(示例):
const fs = require('fs')
//提供完整路径解决路径动态拼接问题
//缺点:移植性差,不利于维护
fs.readFile('C:\\node.js基础\\day01\\code\\file\\file.text','utf-8',function(err,dataStr){
if(err){
return console.log('文件读取失败!'+err.message)
}
console.log('文件读取成功!'+dataStr)
})
PS C:\node.js基础\day01\code> node '.\1-05 演示路径错误问题.js'
文件读取成功!I hade crash on you !
PS C:\node.js基础\day01\code> cd ../
PS C:\node.js基础\day01> node '.\code\1-05 演示路径错误问题.js'
文件读取成功!I hade crash on you !
PS C:\node.js基础\day01> cd ../
PS C:\node.js基础> node '.\day01\code\1-05 演示路径错误问题.js'
文件读取成功!I hade crash on you !
PS C:\node.js基础>
2.33 __dirname 解决动态路径拼接问题解决:缺点移植性差,不利于维护
代码如下(示例):
fs.readFile(__dirname+'/file/file.text','utf-8',function(err,dataStr){
if(err){
return console.log('文件读取失败!'+err.message)
}
console.log('文件读取成功!'+dataStr)
})
PS C:\> node '.\node.js基础\day01\code\1-05 演示路径错误问题.js'
文件读取成功!I hade crash on you !
PS C:\>
建议:以后尽量使用__dirname拼接路径
三、path路径模块
3.1 path基本方法
定义:path模块是Node.js官方提供的,用来处理路径的模块。
3.11 使用前导入模块
const path = require('path')
3.12方法:
path.join()方法 用来将多个路径片段拼接成一个完整的路径字符串
代码如下(示例):
const path = require('path')
const fs = require('fs')
//注意:../会抵消前面的路径
const pathStr = path.join('/a','/b/c','../','./d','e')
console.log(pathStr)
//打印结果 \a\b\d\e
//fs.readFile(__dirname+'/file/file.text') 不推荐使用这种+链接路径
fs.readFile(path.join(__dirname,'/file/file.text'),'utf-8',function(err,dataStr){
if(err){
return console.log(err.message)
}
console.log(dataStr)
})
注意:今后凡是涉及路径拼接的操作,都要使用path.join ()方法进行处理。不要直接使用+进行字符串的拼接
path.basename()方法 用来从路径字符串中,将文件名解析出来
代码如下(示例):
const path = require('path')
const fs = require('fs')
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
path.extname()获取文件扩展名
代码如下(示例):
const path = require('path')
const fpath = 'a/b/c/index.html'//路径字符串
const fext = path.extname(fpath)
console.log(fext)//输出 .html
3.2 综合案例 - 时钟案例
3.21实现步骤
1.创建两个正则表达式,分别用来匹配页面中style和script标签
2.使用fs模块,读取需要处理的HTML文件
3.自定义resolveCSS方法,来写入index.css样式文件
4.自定义resolveJs方法,来写入index.js脚本文件
5.自定义resolveHTML方法,来写入index.html脚本文件
代码如下(示例):
//1.1导入fs、path模块
const fs = require('fs')
const path = require('path')
//1.2定义正则表达式,分别匹配<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,'./clock/index.html'),'utf-8',function(err,dataStr){
//读取文件失败
if(err) return console.log('读取文件失败!'+err.message)
//读取文件成功后,调用对应三个方法,分别解析出css,js,html文件
resolveCss(dataStr)
resolveJS(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>','')
fs.writeFile(path.join(__dirname,'./clock/index.css'),newCSS,function(err){
if(err)return console.log('写入css样式失败!'+err.message)
console.log('写入样式文件成功!')
})
}
//4.1定义处理js脚本的方法
function resolveJS(htmlStr){
//4.2通过正则,提取对应的<script></script>标签内容
const r2 = regScript.exec(htmlStr)
//4.3将提取出来的样式字符串,进行字符串的replace替换操作
const newJS = r2[0].replace('<script>','').replace('</script>','')
//4.4将处理的结果,写入到clock目录中的index.js文件里面
fs.writeFile(path.join(__dirname,'./clock/index.js'),newJS ,function(err){
if(err) return console.log('写入JavaScript脚本失败!'+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='./clock/index.css'/>").replace(regScript,"<script src='./clock/index.js'></script>")
//5.3写入index.html这个文件
fs.writeFile(path.join(__dirname,'./clock/index.html'),newHTML,function(err){
if(err) return console.log('写入HTML文件失败!'+err.message)
console.log('写入HTML页面成功!')
})
}
注意 fs.writeFile()方法只能用来创建文件,不能用来创建路径 重复调用fs.writeFile()写入同一个文件,新写入的内容会覆盖之前的旧内容
四、http模块
4.1 什么是http模块
4.11定义
http模块是Node.js官方提供的、用来创建web服务器的模块,通过http模块提供的http.createServer()方法,就能方便的把一台普通的电脑,变成一台web服务器,从而对外提供web资源服务
4.12导入模块
代码如下(示例):
const http = require('http')
4.2 创建最基本的web服务器
4.21创建最基本的web服务器基本步骤
1.导入http模块
2.创建web服务器实例
3.为服务器实例绑定request事件,监听客户端请求
4.启动服务器
代码如下(示例):
//导入http模块
const http = require('http')
//创建web服务器实例
const server = http.createServer()
//为web服务器实例绑定requet事件,监听客户端请求
server.on('request',(req,res)=>{
console.log('Welcome to my Webserver!')
})
//启动服务器
server.listen(80,() =>{
console.log('http server running at http://127.0.0.1')
})
4.22 req请求对象、 res响应对象
代码如下(示例):
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 = `You request url is ${url},and request method is ${method}` //注意为反单引号,不是单引号
//req 请求对象
console.log(str)
//调用 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')
})
4.23解决中文乱码问题
当调用res.end()方法,向客户端发送中文内容的时候,会出现乱码问题,此时需手动设置内容的编码格式,设置Content-Type响应头,解决中文乱码的问题。
4.3根据不同的url响应不同的html内容
1.获取请求的url地址
2.设置默认的响应内容为404 Not found
3.判断用户请求的是否为/或/index.html
4.判断用户请求的是否为about.html关于页面
5.设置Content-Type响应头,防止中文乱码
6.使用res.end()把内容响应给客户端
代码如下(示例):
const http = require('http')
const server = http.createServer()
server.on('request',function(req,res){
const url = req.url //1.获取请求的url地址
//2.1let和const的区别:let是创建新变量,const则是引用已经存在的
let Content = '<h1>404 Not found</h1>' //2.2设置默认的内容为404 Not found
// 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(8081,()=>{
console.log('server running at http://127.0.0.1:8081')
})
4.4案例 - 实现clock时钟的web服务器
4.41实现步骤:
1.导入需要的模块
2.创建基本的web服务器
3.将资源的请求url地址映射为文件的存放路径
4.读取文件内容并响应给客户端
5.优化资源的请求路径
代码如下(示例):
//1.导入需要的模块
const fs = require('fs')
const path = require('path')
const http = require('http')
//2.创建基本的web服务器
const server = http.createServer()
server.on('request',(req,res)=>{
//3.将资源的请求url地址映射为文件的存放路径
const url = req.url
//const fpath = path.join(__dirname,url)
//5.优化资源的请求路径
//5.1预定义空白文件存放路径
let fpath = ''
if(url==='/'){
//5.2如果请求的路径为/,这需要手动指定文件存放路径
fpath = path.join(__dirname,'/day02/code/clock/index.html')
}else{
//5.3如果请求的路径不为/ 则动态拼接文件路径
fpath = path.join(__dirname,'./day02/code/clock',url)
}
// /day02/code/clock/index.html 相对路径
//4.读取文件内容并响应给客户端
//4.1根据"映射"过来的文件路径读取文件的内容
fs.readFile(fpath,'utf-8',(err,dataStr)=>{
//4.2读取失败,向客户端响应固定的'错误消息'
if(err) return res.end('404 Not found!')
//4.3读取成功,将读取成功的内容,响应给客户端
res.end(dataStr)
})
})
server.listen(8086,()=>{
console.log('server running at http://127.0.0.1:8086')
})
五、模块化
5.1 模块化的基本概念
5.11什么是模块化
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程,对于整个系统来说,模块是可组合、分解和更换的单元(就是大文件拆分成独立且相互依赖的多个小模块)
优点:
复用性
可维护性
按需加载
5.12模块化规范
使用规定语法格式引用模块/暴露成员
5.2 Node.js中模块化
5.21Node.js 中模块分类
内置模块:
官方提供 fs path http 等模块
自定义模块:
用户创建 的.js文件
第三方模块:
第三方开发 使用需下载
5.22 加载模块
使用requrie()方法加载三种模块 语法格式稍有不同
代码如下(示例):
// 1.加载内置的fs模块
const fs =require('fs')
//2.加载用户自定义模块
const Custom = require('./Custom.js')//可以省略.js后缀名
//3.加载第三方模块 需下载使用
const moment = require('moment')
注意:使用require()方法加载其他模块时,会执行被加载模块中的代码
5.23Node.js中模块作用域
1.什么是模块作用域
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内访问,无法在外界被访问。
优点:防止全局变量污染
代码如下(示例):
//---------2-07模块作用域.js文件
const username = '张珊'
function sayHello(){
console.log('大家好,我是'+username)
}
//-----------1-07 test.js 文件
const Custom = require('./2-07模块作用域')
console.log(Custom)//打印结果为{}
5.24向外共享模块作用域中的成员
5.241module对象
每个.js自定义模块中都有一个module对象 存储当前模块有关信息
test:
console.log(module)
5.241module.exports 对象
可以使用 module.exports对象将模块内成员共享出去 供外界(得到 module.exports 所指向的对象)使用
代码如下(示例):
//---------2-11自定义模块.js文件
//module.exports 对象挂载username属性
module.exports.username = 'sa'
//module.exports 对象挂载sayHello方法
module.exports.sayHello = function(){
console.log('hello!')
}
const sa = '19'
module.exports.sa =sa
//让module.exports指向一个全新的对象
module.exports = {
nickname:'小金',
sayHi(){
console.log('Hi!')
}
}
//---------2-12test.js文件
const m = require('./2-11自定义模块')
console.log(m)//{打印结果:module.exports指向一个全新的对象 nickname: '小金', sayHi: [Function: sayHi] }
注意:永远以module.exports指向的对象为准
5.242 exports 对象
exports 对象目的为简化代码 。默认情况下,exports和module.exports指向同一个对象,最终共享结果以module.exports指向的对象为准
test;
onsole.log(exports)
console.log(module.exports)
console.log(exports===module.exports)//打印结果为true
exports基础使用示例
代码如下(示例):
//---------2-13exports对象.js文件
const username = '小骨'
exports.username = username
exports.age = 20
exports.sayHello = function(){
console.log('大家好!')
}
//---------2-14test.js文件
const m = require('./2-13exports对象.js')
console.log(m)
5.25exports和module.exports的使用误区
画图理解
六、 npm与包
5.31包
来源:第三方个人或团队开发出来,免费开源供所有人使用
与内置模块的关系:包是基于内置模块封装出来的 提高效率
5.32下载包
国外一架IT公司 npm,Inc 旗下网站 http://www.npmjs.com/ 全球最大包共享平台
该公司还提供了一个地址为https://registry.npmjs.org/的服务器,对外共享所有包,可以通过这个服务器下载自己所需的包
注意
1.从 http://www.npmjs.com/网站上搜索自己需要的包
2.从https://registry.npmjs.org/服务器下载自己需要的包
5.33npm初体验
1.格式化时间的传统做法:
创建格式化时间的自定义模块:
//定义格式化时间的方法
function dateFormate(dtStr){
const dt = new Date(dtStr)
const y = dt.getFullYear()
const m = padZero(dt.getMonth())
const d = padZero(dt.getDate())
const hh = padZero(dt.getHours())
const mm = padZero(dt.getMinutes())
const ss = padZero(dt.getSeconds())
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}
//定义补零函数
function padZero(n){
return n>9?n:"0"+n
}
module.exports={
dateFormate
}
调用格式化时间的自定义模块:
//导入自定义的格式化时间的模块
const TIME = require('./3-01dateFormate')
//调用方法:进行时间的格式化
const dt =new Date()
// console.log(dt) 打印没有格式化时间 进行对比
const newDT = TIME.dateFormate(dt)
console.log(newDT)
2.格式化时间的高级做法:
在项目中安装包的命令
npm install 包完整的名称
//简写
npm i 包完整的名称
终端安装格式化时间需要的包
npm i moment
注意:包的使用可以到npmjs.com官网查看相关用法
代码如下(示例):
//1.导入需要的包
//注意:导入的名称,就是装包的名称
const moment = require('moment')
const newDt = moment().format("YYYY-MM-DD HH:mm:ss")
console.log(newDt )
5.34初次安装包后的注意事项
(1)文件:
会多一个node_modules文件夹和package-lock.json的配置文件
node_modules文件夹:存放所有已经安装到项目中的包,require()导入第三方包,就是从这个目录中查找并加载包。
package-lock.json配置文件:用来记录包的下载信息 名字、版本号、下载地址、开发期间用到、开发和部署用到等。
(包管理配置文件必须在项目根目录中)
(2)安装指定版本的包:
npm i moment@2.22.2
(3)包的语义版本规范:
版本号是以点分十进制 定义: 2.24.0
1:大版本
2:功能版本
3:Bug修复版本
:版本号提升的规则:只要前面的版本号增长了,后面的版本号归零。
(4)注意团队成员中共享项目的源代码时,剔除node_modules目录后,创建package.json配置文件记录安装了那些包,以方便减少内存。
(5)快速创建package.json
作用:在执行命令所处的目录中,快速创建package.json文件
npm init -y
注意:1.上述命令只能在英文的目录下成功运行,所以项目文件夹的名称一定要使用英文命名,不使用中文和空格 2.运行npm install命令安装包的时候,npm包管理工具会自动把包的名称和版本号,记录到package.json文件中。
(6)dependencies节点
package.json文件中有一个dependencies节点,专门用来记录您使用npm install命令安装了那些包
(7)一次性安装所有的包
执行npm install 命令时,npm包管理工具会先读取package.json中的dependencies节点中的依赖包名称和版本号后会一次性下载所有的包。
npm install
(8)卸载包
无简写模式
npm uninstall moment
(6) devDependencies节点
如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,建议放到devDependencies节点中
与之,开发阶段会用到,在项目上线之后也会用到,建议放到dependencies节点中。
npm i 包名 -D
//完整写法:
npm install 包名 --save-dev
代码如下(示例):
npm install webpack -D
5.35解决下包速度慢的问题
1,为什么慢?
下包默认从国外的https://registry.npmjs.org/服务器进行下载,网络数据传输需要经过漫长的海底光缆,因此慢。
2,解决下包速度慢的问题:
淘宝NPM镜像服务器:专门隔一段时间同步国外官方服务器的包到国内的服务器
镜像:一种文件存储形式,一个磁盘上数据在另一个磁盘存在完全相同的副本即为镜像
3,切换npm的下包镜像源
//查看当前的下包镜像源
npm config get registry
//将下包的镜像源切换为淘宝镜像源
npm config set registry=https://registry.npm.taobao.org/
4,nrm
为了更方便的切换下包的镜像源,安装nrm小工具,可以快速查看和切换下包的镜像源
代码如下(示例):
//通过npm包管理器,将nrm安装为全局可用的工具
npm i nrm -g
//查看所有可用的镜像源
nrm ls
//将下包的镜像源切换为taobao镜像
nrm use taobao
5.36项目包
项目包:被安装到项目的node_modules目录中的包,都是项目包
分类:
开发依赖包:(被记录到devDependencies节点中,只会在开发期间会用到)
核心依赖包:(被记录到dependencies节点中,在开发期间和项目上线之后都会用到)
npm i 包名 -D (被记录到devDependencies节点中)
npm i 包名 (被记录到dependencies节点中)
全局包:执行npm install命令时,如果提供了-g参数,则会把包安装为全局包
全局包安装目录:C:\Users\用户目录\AppData\Roaming\npm\node_modules 目录下
npm i 包名 -g //全局安装指定的包
npm uninstall 包名 -g // 卸载全局安装的包
只有工具性质的包,才有全局安装的必要性,因为提供好用的终端命令。参考官方提供的使用说明
5.36包的分类
i5ting_toc是一个可以把md文档转为html页面的小工具
//将i5ting_toc安装为全局包
npm install -g i5ting_toc
//调用i5ting_toc,轻松实现md转html的功能
i5ting_toc -f 要转换的md文件路径 -o
5.37规范的包结构
包的内部结构
三点要求:
1.包必须以单独目录
存在
2.包的顶级目录下要必须包含package.json
这个包管理配置文件
3.package.json中必须包含name,version,main
三个属性,分别代表包的名字、版本号,包的入口。
注意:以上三点是基本约束,更多参考 https://yarnpkg.com/zh-Hans/docs/package-json网站
5.38如何开发一个属于自己的包
1.格式化日期
2.转义HTML中的特殊字符
3.还原HTML中的特殊字符
2.初始化包的基本结构
1.新建itheima-tools文件夹,作为包的根目录
2.在itheima-tools文件夹中,新建如下三个文件:
package.json(包管理配置文件):
代码如下(示例):
{
"name": "itheima-tools",
"version": "1.0.0",
"main": "index.js",
"description": "提供了格式化时间、HTML.Escape相关的功能",
"keywords": [
"itheima",
"dateFormat",
"escape"
],
"license":"ISC"
}
index.js(包的入口文件):
//这是包的入口文件
const date = require('./dateFormat')
const escape = require('./htmlEscape')
//向外暴露需要的
module.exports={
//...向外展开
...date,
...escape
}
README.md(包的说明文档):
代码如下(示例):
##安装
npm install itheima-tools
##导入
```js
const itheima = require('itheima-tools')
##格式化时间
//调用dataformate 对时间进行格式化
const newdt = TIME.dateFormate(new Date())
//结果 2024-03-20 20:51:00
console.log(newdt)
##转义html中的特殊字符
//待转化的html字符
const htmlStr = '<h1 title="abc">这是h1标签<span>123 </span></h1>'
//调用htmlEscape方法进行转换
const str = TIME.htmlEscape(htmlStr)
//转换结果 <h1 title="abc">这是h1标签<span>123&nbsp</span></h1>
console.log(str)
##还原html中的特殊字符
//待还原的html字符串
const str2 = TIME.htmlUnEscape(str)
//输出结果 <h1 title="abc">这是h1标签<span>123 </span></h1>
console.log(str2)
##开源协议
ISC
`vscode中ctrl+f 快捷键:查找文件中的某个代码`
关于license许可协议相关的内容,可参考[更多license许可协议相关的内容](https://www.jianshu.com/p/86251523e898)
3.定义转义html的方法
```javascript
//../itheima-tools/index.js文件
//定义转义html的方法
function htmlEscape(htmlStr){
return htmlStr.replace(/<|>|"|&/g,(match)=>{
switch(match){
case '<':
return '<'
case '>':
return '>'
case '"':
return '"'
case '&':
return '&'
}
})
}
//定义还原html的方法
function htmlUnEscape(str){
return str.replace(/<|>|"|&/g,(match)=>{
switch(match){
case '<':
return'<'
case '>':
return'>'
case '"':
return'"'
case '&':
return'&'
}
})
}
//向外暴露需要的
module.exports={
htmlEscape,
htmlUnEscape
}
const TIME = require('../itheima-tools/index')
const htmlStr = '<h1 title="abc">这是h1标签<span>123 </span></h1>'
const str = TIME.htmlEscape(htmlStr)
//测试还原html的方法
const str2 = TIME.htmlUnEscape(str)
console.log(str2)
5.38发布包
1.注册npm账号
(1)访问npm网站点击sign up 按钮,进入注册用户界面
(2)填写账号相关的信息:FullName、Public Email、 Username、Password
(3)点击Create an Account按钮,注册账号
(4)点击邮箱验证链接
2.登录npm账号
终端执行:npm login 命令
npm login
执行前要把下包的服务器切换到npm的官方服务器
执行前要把下包的服务器切换到npm的官方服务器
终端登录成功后cd进包目录执行npm publish命令
PS C:\node.js基础\itheima-tools-sliverlin> npm publish
5.39删除已发布的包
运行npm unpublish 包名 --force命令 ,既可以从npm删除已发布的包
注意:npm unpublish命令只能删除72小时以内发布的包,超过72小时永远不能删除 npm unpublish命令删除的包,在24小时内不允许重复发布
npm unpublish itheima-tools-sliverlin --force
尽量不要在npm发布没有意义的包
5.4 模块的加载机制
5.41优先从缓存中加载
模块在第一次加载后会被缓存,这也意味着多次调用require()不会导致模块的代码被执行多次,无论什么模块都是从缓存中加载,提高模块的加载效率
5.42内置模块的加载机制
内置模块是由Node.js官方提供的模块,内置模块的加载优先级最高
即使与内置模块fs名字与包fs名字相同,require(‘fs’)返回的依旧是内置的fs模块
5.43自定义模块的加载机制
使用reuire()加载自定义模块时,必须指定以./或…/开头的路径标识符,如果没有node会把它当作内置模块或第三方模块进行加载。
使用require()导入自定义模块时,如果省略文件的扩展名 ,node.js会按顺序尝试加载以下文件:
1.确切文件名加载
2.补全.js扩展名加载
3.补全.json扩展名加载
4.补全.node扩展名加载
5.加载失败终端报错
5.44第三方模块的加载机制
5.45目录作为模块
把目录作为模块标记符,传递给require()进行加载的时候,有三种加载方式
1.在被加载的目录下查找一个叫做package.json的文件,并寻找main属性,作为require()加载的入口
2.目录如果没有package.json文件,或者mian.js入口不存在或无法解析,会加载目录下index.js文件
3.以上两步都失败了,Node.js会在终端打印错误信息
七、express
5.51.初识Express
1.简介:
Express是基于Node.js平台,快速、开放、极简的Web开发框架
通俗理解:作用和Node.js内置的http模块类似,专门用来创建web服务器的
本质:是一个npm上的第三方包,提供了快速创建web服务器的便捷方法
Express的中文官网
思考:
1.有了http模块为什么还要express?
http内置模块复杂,开发效率低,express是基于http模块进一步封装出来的,能够极大的提高开发效率。
2.http内置模块与express什么关系
类似于浏览器Web API 和jQuery的关系,后者是基于前者进一步封装出来的。
3.express能做什么
前端程序员必备常见两种服务器
web网站服务器
:专门对外提供web网页资源的服务器
API接口服务器
:专门对外提供API接口的服务器
使用Express,可以快速创建web网站服务器和API接口服务器。
2.安装
npm i express@4.17.1
postman安装: postman网址
3.express的基本使用
代码如下(示例):
//1.导入express
const express = require('express')
//2.创建web服务器
const app = express()
//4.使用app.get()方法、app.post()方法 监听客户端的GET POST请求,并向客户端响应具体的内容
app.get('/user',(req,res)=>{
//调用express提供的res.send()方法,向客户端响应一个 JSON对象
res.send({name:'安家乐',gender:'男',age:23})
})
app.post('/user',(req,res)=>{
//调用express提供的res.send()方法,向客户端响应一个 文本字符串
res.send("请求成功!")
})
//3.调用app.listen(端口好,启动成功后的回调函数),启动服务器
app.listen(80,()=>{
console.log('express server running at http://127.0.0.1')
})
(1)获取URL中携带的查询参数
通过req.query
对象,可以访问到客户端通过查询字符串
的形式,发送服务器的参数
//其余代码省略
app.get('/',(req,res)=>{
//通过req.query可以获取到客户端发送过来的 查询参数
//注意:默认情况下,req.query是一个空对象。
console.log(req.query)
res.send(req.query)
})
postman中:http://127.0.0.1/?name=宝宝&age=20 (直接在url写参数与到方法内写是一样的,都打印出对象{ name: ‘宝宝’, age: ‘20’ })
(2)获取URL中的动态参数
通过req.params对象,可以访问到URL中,通过匹配的动态参数
app.get('/user/:id',(req,res)=>{
//通过req.params对象 可以动态匹配参数值 默认是一个空对象
console.log(req.params)
res.send(req.params)
})
注意:冒号后面的id参数可以任意起合法名字,且可跟多个参数
5.52express.static()快速托管静态资源
1.express.static()托管静态资源
express.static()可以创建一个静态资源服务器
const express = require('express')
const app = express()
//调用express。static()方法,快速对外提供静态资源
app.use(express.static('./clock'))
app.listen(80,()=>{
console.log('express server running at http://127.0.0.1')
})
注意:Express在指定的静态目录中查找文件,并对外提供资源访问路径,因此,存放静态文件的目录名不会出现在URL中
2.express.static()托管多个静态资源
//那个文件在前,就先托管那个文件
app.use(express.static('./files'))
app.use(express.static('./clock'))
3.挂载路径前缀
app.use('/public',express.static('/public'))
//访问files文件中的文件必须加上/files
app.use('/files',express.static('./files'))
4.nodemon工具
nodemon工具作用监听项目文件变动,当代码被修改后,nodemon会自动帮助我们重启项目,方便开发和调试
网站:nodemon工具
(1)安装nodemon
npm i -g nodemon
(2)使用nodemon
nodemon ‘文件名’
注意:vscode报错,nodemon 在此系统上禁止运行脚本,解决办法如下:
1.vscode报错,nodemon :因为在此系统上禁止运行脚本
注意:不仅仅适用于nodemon报错,报在此系统上禁止运行脚本的错都可以用以下方法解决
2.报错原因分析:windows 为了安全,默认的执行策略为 Restricted,因此需要将执行策略设置为 RemoteSigned 即可
3.解决方法
(1) get-executionpolicy
如果你电脑显示是RomoteSigned,那就不用改了
如果不是,往下看
(2)修改执行策略为RemoteSigned
set-ExecutionPolicy RemoteSigned
(3)get-executionpolicy 返回 RemoteSigned 验证修改成功。
修改执行策略风险须知:
5.53express路由精简项目结构
1.路由:
路由是按键与服务之间的映射关系
Express路由:路由指的是客户端的请求与服务器处理函数之间的映射关系
三部分构成:请求的类型、请求的URL地址、处理函数
2.路由的用法
初始化包管理配置文件:
npm init -y
(1)模块化路由
可以直接在app上挂载路由,此举不建议 推荐将路由抽离为单独的模块。
1.创建路由模块对应的.js文件
2.调用express.Router()函数创建路由对象
3.向路由对象上挂载具体的路由
4.使用module.exports向外共享路由对象
5.使用app.use()函数注册路由模块
代码如下(示例):服务器
const express = require('express')
const app = express()
//1.导入路由模块
const router =require('./router')
//2.注册路由模块
//用于其他:app.use(express.static('./files'))
app.use(router)
//注意:app.use()函数的作用,就是来注册全局中间件
app.listen(80,()=>{
console.log('express server running at http://127.0.0.1')
})
代码如下(示例):路由模块
//路由模块
//1.导入express
const express = require('express')
//2.创建路由对象
const router = express.Router()
//3.挂载具体路由
router.get('/user/list',(req,res)=>{
res.send('Get user list')
})
router.post('/user/add',(req,res)=>{
res.send('Add new user')
})
//4.向外导出路由对像
module.exports = router
(2)为路由模块添加前缀
app.use('/api',router)
访问地址:http://127.0.0.1/api/user/list
5.54express中间件
1.中间件概念:
例子:污水处理环节就是中间件
2.中间件的格式
express中间件本质就是一个function处理函数
中间件的处理函数必须包含next参数,而路由处理函数中只包含req,res
3.next函数的作用
next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或者路由。
代码如下(示例):定义一个简单的中间件函数
const express = require('express')
const app = express()
//定义一个最简单的中间件函数
const mw = function(req,res,next){
console.log('这是最简单的中间件函数')
//把流转关系,转交给下一个中间件或路由
next()
}
app.listen(80,()=>{
console.log('http://127.0.0.1')
})
4.全局生效的中间件
调用app.use(中间件函数)即可定义一个全局生效的中间件。
代码如下(示例):全局生效的中间件
const express = require('express')
const app = express()
//定义一个最简单的中间件函数
const mw = function(req,res,next){
console.log('这是最简单的中间件函数')
//把流转关系,转交给下一个中间件或路由
next()
}
//将mv注册为全局生效的中间件
app.use(mw)
app.get('/',(req,res)=>{
res.send('Home page.')
})
app.post('/user',(req,res)=>{
res.send('User page.')
})
app.listen(80,()=>{
console.log('http://127.0.0.1')
})
5.中间件的作用
多个中间件之间,共享一份req和res
,基于这样的特性,我们可以在上游
的中间件中,统一
为req或res对象添加自定义的属性和方法,供下游
的中间件或路由进行使用。
全局生效的中间件简化形式:
app.use((req,res,next)=>{
console.log('这是最简单的中间件函数')
next()
})
代码如下(示例):中间件的作用
const express = require('express')
const app = express()
app.use((req,res,next)=>{
//获取到请求到达服务器的时间
const time = Date.now()
//为req对象,挂载自定义属性,从而把时间共享给后面的所有路由
req.startTime = time
next()
})
app.get('/',(req,res)=>{
res.send('Home page.'+ req.startTime)
})
app.post('/user',(req,res)=>{
res.send('User page.'+ req.startTime)
})
app.listen(80,()=>{
console.log('http://127.0.0.1')
})
6.定义多个全局中间件
可以使用app.use()连续定义多个
全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序
依次进行性。
代码如下(示例):定义多个全局中间件
const express = require('express')
const app =express()
//定义第一个全局中间件
app.use((req,res,text)=>{
console.log('这是第一个全局中间件')
text()
})
//定义第一个全局中间件
app.use((req,res,text)=>{
console.log('这是第二个全局中间件')
text()
})
//定义路由
app.get('/user',(req,res)=>{
res.send("这是路由")
})
app.listen(80,()=>{
console.log('http://127.0.0.1')
})
7.局部生效的中间件
不使用app.use()
定义的中间件,叫做局部生效的中间件
代码如下(示例):局部生效的中间件
const express = require('express')
const app = express()
//创建局部中间件
const net = (req,res,text)=>{
console.log('调用了局部生效的中间件')
text()
}
//创建路由
//有三个参数,url 局部中间件 回调函数
app.get('/',net,(req,res)=>{
res.send('Home page.')
})
app.post('/user',(req,res)=>{
res.send('User page.')
})
app.listen(80,()=>{
console.log('express server running at http://127.0.0.1')
})
8.定义多个局部生效的中间件
app.get('/',nw1,nw2,(req,res)=>{
res.send('Home page.')
})
//或者[]
app.get('/',[nw1,nw2],(req,res)=>{
res.send('Home page.')
})
注意:1.一定要在路由之前注册中间件 、2.不能忘记调用next()函数 、 3.调用next()函数后不要再写额外代码 、4.连续调用多个中间件时共享req和res对象
9.中间件的分类
(1)应用级别的中间件
通过app.use()或app.get()或app.post(),绑定到app实例上的中间件。
(2)路由级别的中间件
绑定到express.Router()实例上的中间件,应用级别中间件绑定到app实例上,路由级别中间件绑定到router实例上
(3)错误级别的中间件
必须含有四个形参,(err,req,res,next) 作用:专门用来捕获项目发生的异常错误。
代码如下(示例):错误级别的中间件
const express = require('express')
const app = express()
//1.创建路由
app.get('/',(req,res)=>{
//人为制造错误
throw new Error('服务器内部发生了错误!')
res.send('Home page.')
})
//2.定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err,req,res,next)=>{
console.log('发生了错误!'+err.message)
res.send('Error:'+err.message)
next()
})
app.listen(80,()=>{
console.log('express server running at http://127.0.0.1')
})
注意:错误级别的中间件必须注册在所有路由之后!
(4)express内置的中间件
自Express 4.16.0版本开始,Express内置了3
个常用的中间件,极大的提高了Express项目的开发效率和体验
1.express.static快速托管静态资源 例如 html文件,图片,css样式等(无兼容性)
2.express.json解析JSON格式的请求数据(有兼容性,仅在4.16.0+版本中可用)
3.express.urlencoded解析URL-encoded格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)
(5)第三方的中间件
非官方提供,第三方开发 按需下载并配置
例如:在express@4.16.0之前的版本中,经常使用body-parser这个第三方中间件,来解析请求数据,使用步骤:
1.运行npm i body-parser安装中间件
2.使用require导入中间件
3.调用app.use()注册并使用中间件
//1.导入express
const express = require('express')
//2.创建web服务器
const app = express()
//(1)导入解析表单数据的中间件body-parser
const parser = require('body-parser')
//(2)使用body-parser注册中间件
app.use(parser.urlencoded({extended:false}))
//内置中间件:(express.urlencoded({extended:false})
app.post('/user',(req,res)=>{
console.log(req.body)
res.send('ok')
})
//3.调用app.listen(端口好,启动成功后的回调函数),启动服务器
app.listen(80,()=>{
console.log('express server running at http://127.0.0.1')
})
注意:Express内置的express.urlencoded中间件,就是基于body-parser这个第三方中间件进一步封装出来的。
(6)自定义中间件
手动模拟一个类似于express.urlencoded这样的中间件,来解析post提交到服务器的表单数据
步骤:
1.定义中间件
2.监听req的data事件
app.use((req,res,next)=>{
//定义中间件具体的业务逻辑
//1.定义一个str字符串,专门用来存储客户端发送过来的请求体数据
let str = ''
//2.监听req的data事件
req.on('data',(chunk)=>{
str += chunk
})
})
3.监听req的end事件
app.use((req,res,next)=>{
//定义中间件具体的业务逻辑
//1.定义一个str字符串,专门用来存储客户端发送过来的请求体数据
let str = ''
//3.监听req的end事件
req.on('end',()=>{
//在str中存放的是完整的请求体数据
console.log(str)
//TODO:把字符串格式的请求体数据,利用querystring模块中的parse函数解析成对象格式
const body = qs.parse(str)
// console.log(body)
req.body = body
next()
})
})
4.使用querystring模块解析请求体数据内置模块querystring模块,专门用来处理查询字符串
5.将解析出来的数据对象挂载为req.body
app.post('/user',(req,res)=>{
res.send(req.body)
})
6.将自定义中间件封装为模块
//1.导入querystring模块
const qs = require('querystring')
const bodyParser = (req,res,next)=>{
//定义中间件具体的业务逻辑
//1.定义一个str字符串,专门用来存储客户端发送过来的请求体数据
let str = ''
//2.监听req的data事件
req.on('data',(chunk)=>{
str += chunk
})
//3.监听req的end事件
req.on('end',()=>{
//在str中存放的是完整的请求体数据
console.log(str)
//TODO:把字符串格式的请求体数据,利用querystring模块中的parse函数解析成对象格式
const body = qs.parse(str)
// console.log(body)
req.body = body
next()
})
}
module.exports = bodyParser
5.55express创建API接口
1.编写GET接口
router.get('/get',(req,res)=>{
//通过req.query获取客户端通过查询字符串,发送到服务器的数据
const query = req.query
//调用res.send()方法。向客户端响应处理的结果
res.send({
status:0,//0表示处理成功,1表示处理失败
msg:'GET 请求成功!',//状态的描述
data:query//需要响应给客户端的数据
})
})
2.编写POST接口
router.post('/post',(req,res)=>{
//1.获取客户端通过请求体,发送服务器的URL-encoded数据
const body =req.body
//2.调用res.send()方法,把数据响应给客户端
res.send({
staus:0,//0表示处理成功,1表示处理失败
msg:'POST请求成功!',//状态的描述
data:body//需要响应给客户端的数据
})
})
//接口js
//配置解析表单数据的中间件
app.use(express.urlencoded({extended:false}))
注意:如果要去获取URL-encoded格式的请求体数据,必须配置中间件app.use(express.urlencoded({extended:false}))
5.55express中启用cors跨域资源共享
1.接口的跨域问题
刚才编写的GET和POST 接口,存在一个很严重的问题:不支持跨域请求
网站:staticfile.org 由此网站复制juery代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<button id="btnGET">GET</button>
<button id="btnPOST">POST</button>
<script>
$(function(){
//1.测试GET接口
$('#btnGET').on('click',function(){
$.ajax({
type:'GET',
url:'http://127.0.0.1/api/get',
data:{name:'zs',age:30},
success:function(res){
console.log(res)
}
})
})
//2.测试POST接口
$('#btnPOST').on('click',function(){
$.ajax({
type:'POST',
url:'http://127.0.0.1/api/post',
data:{name:'水浒传',author:'施耐庵'},
success:function(res){
console.log(res)
}
})
})
})
</script>
</body>
</html>
2.解决接口跨域问题的方案主要有两种:
1.CORS(主流的解决方案,推荐使用)
使用cors中间件解决跨域问题
步骤:
(1)运行npm install cors安装中间件
(2)使用const cors = require(‘cors’)导入中间件
(3)在路由之前调用app.use(cors())配置中间件
//一定要在路由之前,配置cors这个中间件,从而解决接口跨域问题
const cors = require('cors')
app.use(cors())
2.JSONP(有缺陷的解决方案:只支持GET请求)
3.什么事cors
4.CORS的注意事项
(1)CORS主要在服务器
端进行配置,客户端浏览器无须做任何额外配置
,即可开启CORS的接口
(2)CORS在浏览器中有兼容性
,只有XMLHttpRequestLevel2的浏览器,才能正常访问开启了CORS的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)
5.CORS跨域资源共享
CORS响应头部 Access-Control-Allow-Origin
(1)允许访问所有该资源所有的外域URL
res.setHeader('Access-Control-Allow-Origin','*')
(2)只允许访问http://itcast.cn
res.setHeader('Access-Control-Allow-Origin','http://itcast.cn')
8.CORS请求的分类
客户端在请求CORS接口时,根据请求方式和请求头的不同,可以将CORS的请求分为两大类:
(1)简单请求
同时满足两个条件:
请求方式:GET、POST、HEAD三者之一
http头部信息超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type(只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
(2)预检请求
只要符合以下任何一个请求
(1)请求方式GET POST HEAD之外
的请求Method类型
(2)请求头中包含自定义头部字段
(3)向服务器发送了application/json格式的数据
浏览器与服务器正式通信之前,浏览器会先发送OPTION请求预检,以获取服务器是否允许该实际请求,所以这一次的OPTION请求称为预检请求,服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据
(3)两次请求的区别
简单请求特点:客户端与服务器只会发送一次请求
预检请求特点:客户端与服务器只会发送两次请求,OPTION请求预检成功后才会发送真正的请求。
5.JSONP接口
概念:浏览器端通过
6.创建JSONP接口
注意事项:项目中已经配置了CORS的跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明JSONP接口
,否则JSONP接口会被处理成开启了CORS的接口
实现JSONP接口的步骤:
1.获取客户端发送过来色回调函数的名字
2.得到要通过JSONP形式发送给客户端的数据
3.根据前两步得到的数据,拼接出一个函数调用的字符串
4.把上一步得到色字符串,响应给客户端的<script标签进行解析执行