Node.js+npm+express学习笔记

Node.js+npm+express学习笔记

2022/7/18 星期一

1.初始Node.js与内置模块

1.1 初始Node.js

1.1.1 知识思考与引入

  1. 为什么JavaScript可以在浏览器中被执行?
  • 因为浏览器中存在JavaScript解析引擎,可以将js代码解析为可执行的文件
  1. 不同的浏览器使用不同的JavaScript解析引擎:
  • Chrome浏览器⇒V8
  • Firefox浏览器⇒OdinMonkey(奥丁猴)
  • Safri浏览器⇒JSCore
  • IE浏览器⇒Chakra(查克拉)…
  1. 为什么JavaScript可以操纵DOM和BOM?
  • 每个浏览器都内置了DOM和BOM这样的API函数,因此,浏览器中的JavaScript才可以调用它们
  1. 浏览器中的JavaScript运行环境
  • js解析引擎(负责解析和执行JavaScript代码)+内置API(由运行环境提供的特殊接口,只能在所属的运行环境中被调用)
  1. JavaScript能否做后端开发?
  • JavaScript除了可以做前端开发,还可以做后端开发,需要借助运行环境node.js

1.1.2 Node.js简介

  1. 什么是Node.js?
  • Node.js是一个基于Chrome V8引擎的JavaScript运行环境
  1. Node.js的官方地址:http://nodejs.cn/
  2. Node.js中的JavaScript运行环境
  • V8解析引擎+内置API(fs、path、http、querystring…)

注意:

  1. Node.js可以做什么?
  • node.js可以作为一个JavaScript的运行环境,仅仅提供了基础的功能和API,然而,基于Node.js提供的这些基础功能,很多强大的工具和框架不断出现

如:Express框架(可以快速构建web应用)、Electron框架(可以构建跨平台的桌面应用)、restify框架(可以快速构建API接口项目)…

  1. node.js的学习路径
  • JavaScript基础语法+Node.js内置API模块(fs、path、http等)+第三方API模块(express、mysql等)

1.1.3 安装Node.js

  1. LTS版本和Current版本的不同
  • LTS为长期稳定版,对于追求稳定性的企业级项目,推荐安装LTS版本
  • Current为新特性尝鲜版,适合热衷于尝试新特性的用户使用。可能存在bug和漏洞。
  1. 什么是终端?
  • 终端是专门为开发人员设计的,用于实现人机交互的一种方式
  1. 终端命令行中查看node版本
  1. node.js具体安装,找到LTS版本直接安装即可

1.1.4 在node.js环境中执行Javascript代码

  1. 基本步骤:
  • 打开终端→切换到js文件路径→输入node 文件名

Untitled

  • 也可以在js文件所在的目录shift+右键,点击Windows Powershell直接进入当前目录,而不用再使用cd命令

Untitled

  • Powershell为Windows系统中新推出的终端,功能比cmd更强大,两者都能满足开发人员的使用
  1. 终端中的快捷键
  • 上箭头:快速定位到上次执行的命令
  • tab键:快速补全文件路径

Untitled

  • Esc键:快速清空当前已输入的内容
  • cls命令:清空终端中的内容

1.2 fs文件系统模块

1.2.1 什么是fs文件系统模块

  1. fs模块是Node.js官方提供的、用来操纵文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求
  • fs.readFile():用来读取指定文件的内容
  • fs.writeFile():用来向指定文件
  1. 在JavaScript代码中,使用fs模块来操作文件,需要使用如下方法进行导入:

1.2.2 读取指定文件中的内容

  1. 使用fs.readFile()方法,可以读取指定文件中的内容,语法格式:
  • path和callback为必选参数,其中path是字符串,表示文件路径,callback表示文件读取完后,通过回调函数拿到读取的结果
  • options为可选参数,表示以什么编码格式读取文件
  1. fs.readFile()的实例代码,以utf8的编码格式读取文件,读取是否成功都会执行回调函数
//  1.导入fs模块来操作文件
const fs=require('fs')

//  2.调用fs.readFile()方法读取文件
fs.readFile('./files/file1.txt','utf8',function(err,dataStr){
    console.log(err);  // 读取失败后的结果
    console.log("-----------");
    console.log(dataStr);  // 读取成功后的结果
})

Untitled

  • 读取成功后,err的值为null;如果读取失败,err的值为一个错误对象,dataStr的值为undefined

Untitled

  1. 所以err的值为null即说明读取文件成功,否则读取失败
//  1.导入fs模块来操作文件
const fs=require('fs')

//  2.调用fs.readFile()方法读取文件
fs.readFile('./files/file1.txt','utf8',function(err,dataStr){
    if(err){
        return console.log("读取文件失败"+err.message);
    }
    console.log("读取文件成功");
})

Untitled

1.2.3 向指定文件中写入内容

  1. 使用fs.writeFile()方法,可以向指定文件中写入内容,语法格式:
  • file,data,callback是必选参数,file用于指定一个文件路径的字符串,表示文件存放地址,data表示要写入的内容,callback是文件写入后的回调函数
  • options是可选参数,表示以什么样的格式写入文件,默认是utf8
  1. fs.writeFile()实例代码
const fs=require('fs')

fs.writeFile('./files/file1.txt','abcd',function(err){
    console.log(err)
})

Untitled

  • 如果写入成功,err的值为nul;如果写入失败,err的值为一个错误对象
  1. 所以err的值为null即说明写入文件成功,否则写入失败
const fs=require('fs')

fs.writeFile('./files/file11.txt','abcd',(err)=>{
    if(err){
        return console.log("文件写入失败:"+err.message);
    }
    console.log("文件写入成功");
})

1.2.4 整理成绩案例

  1. 把成绩单中的内容整理到成绩单-ok.txt中
// 导入fs模块
const fs=require('fs')

fs.readFile('./files/成绩单.txt','utf-8',(err,dataStr)=>{
    // 判断是否读取成功
    if(err){
        return console.log("文件读取失败:"+err.message)
    }

    // 将读取到的字符串以空格为分隔符建立数组
    const oldArr=dataStr.split(" ");
    const newArr=[];

    // 将数组中的每个元素中-替换为:
    oldArr.forEach(element => {
        newArr.push(element.replace("-",":"));
    });

    // 数组中的元素以换行符\r\n连接为字符串
    const newStr=newArr.join("\r\n");
    console.log(newStr); 

    // 把得到的字符串写入文件
    fs.writeFile('./files/成绩-ok.txt',newStr,()=>{
        if(err){
           return console.log("文件写入失败:"+err.message);
        }
        console.log("文件写入成功");
    })
})

Untitled

1.2.5 fs模块-路径动态拼接问题

  1. 在使用fs模块操作文件时,如果提供的操作路径是以./或../开头的相对路径时,很容易出现路径动态拼接错误的问题

原因:代码在运行的时候,会以执行node命令时所处的目录,动态拼接出被操作文件的完整路径

Untitled

  • 要解决此问题,可使用绝对路径来指定我们要操作的文件,但这样不利于项目的维护,移植性差。
  1. 可以使用__dirname来解决路径动态拼接问题,__dirname表示当前文件所处的目录
const fs=require('fs')

//  __dirname表示当前文件所处的目录
fs.readFile(__dirname+'/files/file1.txt','utf8',function(err,dataStr){
    if(err){
        return console.log("读取文件失败"+err.message);
    }
    console.log("读取文件成功"+dataStr);
})

Untitled

1.3 path路径模块

1.3.1 什么是path路径模块

  1. path模块是Nodejs官方提供的、用来处理路径的模块。它提供一系列的方法和属性,用来满足用户对路径的处理需求
  • path.join()方法:用来将多个路径片段拼接成一个完整的路径字符串
  • path.basename()方法:用来从路径字符串中,将文件名解析出来
  1. 在JavaScript代码中,使用path模块来处理路径,需要使用如下方法进行导入:

1.3.2 path.join()的使用

  1. 使用path.join()方法,可以将多个路径拼接为完整的路径字符串。
const path=require('path')

// ../会抵消前面的路径
const pathStr=path.join('/a','/b/c','../','./d','/e')
console.log(pathStr)  // \a\b\d\e

const pathStr2=path.join(__dirname,'/a','./b/c')
console.log(pathStr2)  // D:\桌面文件私人\myStudy\Nodejs\a\b\c

Untitled

1.3.3 path.basename()的使用

  1. 使用path.basename(),可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名,语法格式:
  • path是必选参数,表示一个路径的字符串,ext是可选参数,表示文件扩展名
  1. 代码示例
const path=require('path')

const fpath='a/b/c/index.html'
const fullname=path.basename(fpath)
console.log(fullname)

const nameWithoutext=path.basename(fpath,'.html')
console.log(nameWithoutext)

Untitled

  1. 使用path.extname()可以获取文件的扩展名

1.3.4 综合案例

  1. 把一个完整的html文件中的css样式和JavaScript代码提取出来,写入新的文件中
// 导入fs模块
const fs=require('fs')

//导入path模块
const path=require('path')

// 定义正则表达式,匹配<style></style>和<script></script>
const resStyle=/<style>[\s\S]*<\/style>/
const resScript=/<script>[\s\S]*<\/script>/

// 调研readFile读取文件
fs.readFile(path.join(__dirname,'下拉菜单/dropdown2.html'),'utf-8',(err,dataStr)=>{
    if(err) return console.log("读取文件失败:"+err.message)
    // 读取成功后调用对应的方法,解析出html、css、script
    resolveCss(dataStr);
    resolveJs(dataStr);
    resolveHtml(dataStr);
})

// 定义处理css的方法
const resolveCss=(str)=>{
    // 使用正则表达式提取需要的内容
    const r1=resStyle.exec(str);
    const newCss=r1[0].replace('<style>','').replace('</style>','');
    // 调用fs.writeFile()将提取出的css写入文件
    fs.writeFile(path.join(__dirname,'/下拉菜单/index.css'),newCss,(err)=>{
        if(err) return console.log("写入css样式失败"+err.message);
        console.log("写入css成功");
    })
}
// 定义写入js的方法
const resolveJs=(str)=>{
    // 使用正则表达式提取需要的内容
    const r1=resScript.exec(str);
    const newJs=r1[0].replace('<script>','').replace('</script>','');
    fs.writeFile(path.join(__dirname,'/下拉菜单/index.js'),newJs,(err)=>{
        if(err) return console.log("写入js失败"+err.message);
        console.log("写入js成功");
    })
}
// 定义写入html的方法
const resolveHtml=(str)=>{
    const newHtml=str.replace(resStyle,'<link rel=\'stylesheet\' href=\'./index.css\'>')
    .replace(resScript,'<script src="./index.js"></script>');
    fs.writeFile(path.join(__dirname,'/下拉菜单/index.html'),newHtml,(err)=>{
        if(err) return console.log("写入html失败"+err.message);
        console.log("写入html成功");
    })
}

Untitled

Untitled

1.4 http模块

1.4.1 什么是http模块

  1. 什么是客户端,什么是服务器?
  • 在网络结点中,负责消费资源的电脑叫做客户端,负责对外提供资源的电脑叫做服务器
  1. http模块是node.js官方提供的、用来创建web服务器的模块。通过http模块提供的http.createServer()方法,就能方便地把一台普通电脑变成一台web服务器,从而对外提供web资源服务
  2. 使用http模块创建web服务器,需要先导入:

1.4.2 http模块的作用

  1. 服务器和普通电脑的区别在于,服务器上安装了web服务器软件,例如:IIS、Apache等,通过安装这些服务器软件,就能把一台普通电脑变成一台web服务器
  2. 在node.js中,基于node.js提供的http模块,通过几行简单的代码,就能轻松手写一个服务器软件,从而提供web服务

1.4.3 服务器相关概念

  1. ip地址(计网相关内容)
  2. 域名和域名服务器

ip地址和域名是一一对应的关系,这份对应关系存放在域名服务器中(DNS),域名服务器就是提供域名和ip地址之间转换服务的服务器

  1. 127.0.0.1ip地址用于本地环回测试,对应的域名是localhost,代表着自己的电脑
  2. 端口号(计网相关内容)

1.4.4 创建基本的web服务器

  1. 基本步骤
  • 导入http模块⇒创建web服务器实例⇒为服务器绑定request事件,监听客户端要求⇒启动服务器
  • 导入http模块
  • 创建web服务器实例
  • 绑定request事件,监听用户发送的请求
  • 启动服务器
  1. 创建服务器实例:
  • req请求对象:只要服务器接收到了客户端的请求,就会调用server.on()为服务器绑定的request事件处理函数
// 导入http模块
const http=require('http')
// 创建web服务器实例
const server=http.createServer()
// req是请求对象,它包含了与客户端相关的数据和属性,如:
// req.url是客户端请求的URL地址
// req.method是客户端请求的method类型
server.on('request',(req,res)=>{
    const url=req.url;
    const method=req.method;
    const str=`your request url is ${url},request method is ${method}`;
    console.log(str);
})
// 启动服务器
server.listen(8000,()=>{
    console.log("server running at http://127.0.0.1:8000");
})

Untitled

  • res响应对象 :在request事件处理函数中,如果想访问与服务器相关的数据或属性,可以使用:
const http=require('http')
const server=http.createServer()
server.on('request',(req,res)=>{
    const url=req.url;
    const method=req.method;
    const str=`your request url is ${url},request method is ${method}`;
    console.log(str);

    // 调用res.end()向客户端响应一些内容
    res.end(str);
})
server.listen(8000,()=>{
    console.log("server running at http://127.0.0.1:8000");
})

Untitled

  1. 解决中文乱码问题
const http=require('http')
const server=http.createServer()
server.on('request',(req,res)=>{
    const url=req.url;
    const method=req.method;
    const str=`你请求的 url 是 ${url},请求的 method 是 ${method}`;
    console.log(str);
    //调用res.setHeader设置响应头,解决中文乱码问题
    res.setHeader('Content-Type','text/html;charset=UTF-8')
    res.end(str);
})
server.listen(8000,()=>{
    console.log("server running at http://127.0.0.1:8000");
})

1.4.5 根据不同的url响应不同的html

const http=require('http')
const server=http.createServer()
server.on('request',(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(8000,()=>{
    console.log("server running at http://127.0.0.1:8000");
})

1.4.6 http模块案例

  1. 根据输入的url请求资源,显示在网页上
const http=require('http')
const fs=require('fs')
const path=require('path')
const server=http.createServer()
server.on('request',(req,res)=>{
    const url=req.url;
    let fpath='';
    if(url==='/'){
        fpath=path.join(__dirname,'./下拉菜单/index.html')
    }else{
        fpath=path.join(__dirname,'./下拉菜单',url)
    }
    fs.readFile(fpath,'utf8',(err,dataStr)=>{
        if(err) return res.end("not found 404")
        res.end(dataStr)
    })

})
server.listen('8000',()=>{

})

2.模块化

2.1 模块化基本概念

  1. 什么是模块化?
  • 模块化是解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程,对整个系统来说,模块是可组合、分解和更换的单元
  1. 编程领域中的模块化?
  • 编程领域中的模块化,就是遵守固定的规则,把一个大文件拆分为独立并且相互依赖的多个小模块

2.1.1 模块化规范

  1. 模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则
  2. 模块化规范的好处:降低沟通成本、方便了模块间的相互调用、利人利己

2.2 node.js中的模块化

2.2.1 node.js中模块的分类

  1. node.js中根据模块来源的不同,将模块分为了三类,分别是:
  • 内置模块(由node官方提供,例如fs、path、http)
  • 自定义模块(用户创建的每个.js文件,都是自定义模块)
  • 第三方模块(由第三方开发出来的模块,使用前需要下载)

2.2.2 加载模块

  1. 使用require()方法可以加载需要的内置模块、自定义模块或第三方模块。
  1. 在使用require加载用户自定义模块时,可以省略.js的后缀名

2.2.3 node.js中的模块作用域

  1. 什么是模块作用域?
  • 和函数作用域类似,在自定义的模块中定义的变量和方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域
  1. 模块作用域的好处:
  • 防止全局变量污染的问题

2.2.4 向外共享模块作用域中的成员

  1. module对象:在每个自定义的模块中,都有一个module对象,里面存储了和当前模块有关的信息

Untitled

  1. 如图,里面的exports空对象允许我们定义向外共享成员
  2. 在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用,外界用require()方法导入自定义模块时,得到的就是module.exports所指向的对象
const m=require('./自定义模块')
console.log(m)
// 向module.exports对象上挂载username属性
module.exports.username='张三'

// 向module.exports对象上挂载函数
module.exports.sayHello=()=>{
    console.log('hello!')
}

Untitled

  1. 使用require()方法导入模块时,导入的结果永远以module.exports指向的对象为准
  2. exports对象,由于module.exports单词写起来比较复杂,为了简化由外共享成员的代码,node提供了exports对象,默认情况下,exports和module.exports对象指向同一个对象。
console.log(exports===module.exports)  // true
const m=require('./exports对象')
console.log(m)
exports.username='张三'
exports.sayHello=()=>{
    console.log("what do you mean?")
}
exports.age=20

Untitled

  1. require模块时,得到的永远是module.exports指向的对象

3.npm与包

3.1 包

  1. 什么是包?
  • node.js中的第三方模块又叫做包
  1. 包的来源
  • 包是由第三方个人或团体开发出来的,免费供所有人使用
  • node.js中的包都是免费且开源的
  1. 从哪里下载包
  1. npm,inc.公司提供了一个包管理工具,我们可以使用这个包管理工具,从https://registry.npmjs.org/服务器上把需要的包下载到本地

这个包管理工具的名字叫做Node Package Manager(简称npm包管理器),这个包管理工具随着node.js的安装包一起被安装到了电脑上

  1. 在终端输入npm -v可查看包管理器的版本

Untitled

3.2 npm初体验

  1. 格式化时间的传统做法
		const dateFormat=(dtStr)=>{
    const dt=new Date(dtStr);

    const y=dt.getFullYear();
    const m=padZero(dt.getMonth());
    const s=padZero(dt.getSeconds());

    const hh=padZero(dt.getHours());
    const mm=padZero(dt.getMinutes());
    const ss=padZero(dt.getSeconds());

    return `${y}-${m}-${s} ${hh}:${mm}:${ss}`;
}

const padZero=(n)=>{
    return n>9 ? n:'0'+n
}

module.exports={
    dateFormat
}
const TIME=require('./dateformat')

const dt=new Date()
const newdt=TIME.dateFormat(dt)
console.log(newdt)

Untitled

  1. 使用moment包进行格式化时间
  • 使用如下命令安装需要使用的包
  • 安装moment包

Untitled

  1. 使用moment
const moment=require('moment')

const t=moment().format('YYYY-MM-DD HH:mm:ss')

console.log(t)

Untitled

  1. 初次装包后多出的文件
  • 在初次装包完成后,项目文件夹里多出了一个node_modules文件夹和package-lock.json配置文件
  • node_modules文件夹:用来存放已安装到项目中的包
  • package-lock.json配置文件:用来记录node_modules目录下的每一个包的下载信息

Untitled

  1. 安装指定版本的包
  • 默认情况下,使用npm install 命令安装包时,会自动安装最新版本的包。可通过@符号来指定具体版本,当重新安装指定版本的包时,会覆盖原来安装的包
  • 包的版本号三位数字分别表示大版本、功能版本、Bug修复版本

3.3 包管理配置文件

  1. npm规定,在项目根目录中,必须提供一个叫做package.json的包管理配置文件,用来记录与项目有关的一些配置信息。例如:
  • 项目的名称、版本号、描述等
  • 项目中用到了哪些包
  • 哪些包只在开发期间会用到
  • 哪些包在开发和部署时都需要用到
  1. 如何记录项目中安装了哪些包?
  • 在项目根目录中,创建一个package.json配置文件,用来记录项目中安装了哪些包
  1. npm包管理器提供了一个快捷命令,可以在执行命令所处的目录中,快速创建package.json配置文件
  • 该命令只能在英文目录下运行成功
  • npm包管理工具会自动把包的名称和版本号记录到package.json文件中

Untitled

  1. dependencies节点
  • package.json文件中,有一个dependencies节点,专门用来记录使用npm install命令安装了哪些包5

Untitled

  1. 可以运行npm install(npm i)命令一次性安装所有的依赖包
  2. 可以运行npm uninstall命令卸载指定的包
  3. devDependencies节点
  • 如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到devDependencies节点中
  • 可以使用如下命令,将包记录到devDependencies节点中

Untitled

Untitled

3.4 解决下包速度慢的问题

  1. 切换npm的下包镜像源(即下包的服务器地址)

Untitled

3.5 包的分类

  1. 项目包:那些被安装到项目的node_modules目录中的包,都是项目包
  2. 项目包分为两类:
  • 开发依赖包(devDependencies中的包)
  • 核心依赖包(dependencies中的包)
  1. 全局包:在执行npm install命令时,如果提供了-g参数,则会把包安装为全局包
  • 全局包会被安装到C:\Users\用户目录\AppData\Roaming\npm\node_modules目录下
  • 只有工具性质的包,才有全局安装的必要性

3.6 规范的包结构

  1. 一个规范的包,它的组成结构必须符合以下三点要求
  • 包必须以单独的目录而存在
  • 包的顶级目录下要必须包含package.json这个包管理配置文件
  • package.json文件中必须包含name,version,main这三个属性,分别代表包的名字、版本号、包的入口

3.7 开发属于自己的包

  1. package.json
{
    "name":"mytools",
    "version":"1.0.0",
    "main": "index.js",
    "description": "提供了格式化时间,HTMLEscape的功能",
    "keywords":[
        "mytools",
        "dateFormat",
        "escape"
    ],
    "license": "ISC"
}
  1. index.js
// 包的入口文件
const date=require("./src/dateFormat")
const escape=require('./src/htmlEscape')

module.exports={
    ...date,
    ...escape
}
  1. src/dateFormat.js
const dateFormat=(dtStr)=>{
    const dt=new Date(dtStr);

    const y=dt.getFullYear();
    const m=padZero(dt.getMonth());
    const s=padZero(dt.getSeconds());

    const hh=padZero(dt.getHours());
    const mm=padZero(dt.getMinutes());
    const ss=padZero(dt.getSeconds());

    return `${y}-${m}-${s} ${hh}:${mm}:${ss}`;
}

const padZero=(n)=>{
    return n>9 ? n:'0'+n
}

module.exports={
    dateFormat
}
  1. src/htmlEscape
// html转义
const htmlEscape=(htmlStr)=>{
    return htmlStr.replace(/<|>|"|&/g,(match)=>{
        switch(match){
            case '<':
                return '&lt;'
            case '>':
                return '&gt;'
            case '"':
                return '&quot;'
            case '&':
                return '&amp;'
        }
    })
}

// html还原
const htmlUnEscape=(str)=>{
    return str.replace(/&lt;|&gt;|&amp;|&quot;/g,(match)=>{
        switch(match){
            case '&lt;':
                return '<'
            case '&gt;':
                return '>'
            case '&quot;':
                return '"'
            case '&amp;':
                return '&'
        }
    })
}
module.exports={
    htmlEscape,
    htmlUnEscape
}
  1. 包根目录中的README.md文件,是包的使用说明文档,通过它,我们可以事先把包的使用说明,以markdown的格式写出来,方便用户参考
## 安装

npm install mytools


## 导入
```js
const tools=require('mytools')

格式化时间

// 调用dateFormat格式化时间
const datastr=mytools.dateFormat(new Date())
console.log(datastr)

转义html中的特殊字符

const htmlStr=`<h1 title="abc">这是h1标签<span>123&nbsp;</span></h1>`;
const str=mytools.htmlEscape(htmlStr);
console.log(str);

还原字符串为html

const str2=mytools.htmlUnEscape(str);
console.log(str2);

开源协议

ISC


# 4.模块的加载机制

## 4.1 优先从缓存中加载

1. 模块在第一次加载后会被缓存,这也意味着多次调用require()不会导致模块的代码被执行多次
- 不论是内置模块、用户自定义模块、第三方模块,都会优先从缓存中加载,从而提高模块的加载效率

## 4.2 内置模块的加载机制

1. 内置模块是由nodejs官方提供模块,其加载优先级最高

## 4.3 自定义模块的加载机制

1. 使用require加载自定义模块时,必须制定以./或../开头的路径标识符,在加载自定义模块时,如果没有指定这样的路径标识符,node会把它当做内置模块或第三方模块进行加载。
2. 在使用require导入自定义模块时,如果省略了文件的扩展名,则nodejs会按顺序分别尝试加载以下文件:
- 按确切的文件名加载
- 补全.js进行加载
- 补全.json进行加载
- 补全.node进行加载

## 4.4 第三方模块的加载机制

1. 如果传递给require()的模块标识符不是一个内置模块,也没有以./或../开头,则nodejs会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块
2. 如果没有从/node_modules找到对应的第三方模块,则会移到上一层父目录中,进行加载,直到文件系统的根目录

## 4.5 目录作为模块

1. 当把目录作为模块标识符,传递给require()进行加载时,有三种加载方式:
- 在被加载的目录下查找叫做package.json的文件,并寻找main属性,作为require加载的入口
- 如果目录中没有package.json文件,或者main入口不存在或无法解析,则nodejs将会加载目录下的index.js文件
- 如果上两步都失败,则nodejs会在终端打印错误消息,报告模块的缺失:
    
    > “Error:cannot find module xxx”
    > 

# 5. Express

## 5.1 Express简介

1. 什么是express
- express是基于nodejs平台,快速、开放、极简的web开发框架
- 通俗来说,express是专门用于创建web服务器的,本质上npm的第三方包,提供了快速创建web的便捷方法
1. http内置模块和express的关系
- 类似于浏览器中,web api与jquery的关系,后者是基于前者封装起来的,Express是基于内置http模块封装的,能极大提高开发效率
1. express能做什么
- 使用express可以快速创建web网站的服务器和api接口的服务器

## 5.2 Express的基本使用

1. 安装:在目录下执行如下命令

<aside>
💡 npm i express@4.17.1

</aside>

1. 创建基本的web服务器

```jsx
// 导入express
const express=require('express')
// 创建web服务器
const app=express()
// 启动web服务器
app.listen(80,()=>{
    console.log("xxxxx")
})

Untitled

  1. 监听get请求
  • 通过app.get()方法,可以监听客户端的get请求,具体语法格式:
  1. 监听post请求,可以监听客户端的post请求,具体语法格式:
  1. 把内容响应给客户端,通过res.send()方法可以把内容响应给客户端
// 导入express
const express=require('express')
const res = require('express/lib/response')
// 创建web服务器
const app=express()

// 监听客户的get或post请求
app.get('/user',(req,res)=>{
    // 调用res.send()方法向客户端响应对象
    res.send({name:'zs',age:20,gender:'男'})
})
app.post('/user',(req,res)=>{
    res.send("请求成功")
})

// 启动web服务器
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)
})

http://127.0.0.1/?action=run&time=200min

Untitled

  1. 获取url中的动态参数
  • 通过req.params对象,可以访问到url中,通过:匹配到的动态参数:
// 这里的:id是一个动态的参数
app.get('/user/:id',(req,res)=>{
    // req.params是动态匹配到的url参数,默认是空对象
    console.log(req.params)
    res.send(req.params)
})

5.3 托管静态资源

5.3.1 Express.static()

  • express提供了一个非常好的函数,叫做express.static(),通过它,我们可以非常方便地创建一个静态资源服务器
  • 通过以下代码就可以将public目录下的图片、css文件、javascript文件对外开放访问了:

注意:Express在指定的静态目录中查找资源,并对外提供资源的访问路径,因此,存放静态文件的目录名不会出现在url中

const express=require('express')
const app=express()
app.use(express.static('./下拉菜单'))

app.listen(80,()=>{
    console.log("express server running at http://127.0.0.1")
})

5.3.2 托管多个静态资源

  1. 如果要托管多个静态资源目录,请多次调用express.static()函数:

访问静态资源文件时,express.static()函数会根据目录的添加顺序查找所需文件

5.3.3 挂载路径前缀

  1. 如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下方法:
const express=require('express')
const app=express()
app.use('/lalal',express.static('./下拉菜单'))

app.listen(80,()=>{
    console.log("express server running at http://127.0.0.1")
})

Untitled

5.4 nodemon

  1. 使用nodemon工具,能够监听项目文件的变动,当代码被修改后,会自动帮我们重启项目
  2. 安装nodemon:
  1. 使用nodemon app.js即可运行文件,当文件代码被修改时,nodemon会自动重启项目

Untitled

Untitled

注意:使用nodemon运行文件时,需要在cmd终端运行,在powershell下运行可能会出现错误

5.5 Express路由

  1. express中,路由指的是客户端的请求与服务器处理函数间的映射关系
  2. express中的路由由三个部分组成,分别是请求的类型,请求的url地址,处理函数,格式如下:

5.5.1 路由的匹配过程

  1. 每当一个请求到达服务器后,需要先经过路由的匹配,只有匹配成功后,才会调用对应的函数
  2. 在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的url同时匹配成功,则express会将这次请求转交给对应的function函数进行处理

5.5.2 路由的使用

  1. 在express中使用路由最简单的方式,就是把路由挂载到app实例上
const express=require('express')
const app=express()

// 挂载路由
app.get('',(req,res)=>{
    res.send("hello world!")
})
app.post('',(req,res)=>{
    console.log("post request")
})

app.listen(80,()=>{
    console.log("http://127.0.0.1")
})
  1. 模块化路由
  • 为了方便对路由进行模块化的管理,express不建议直接将路由挂载到app上,而是推荐将路由抽离为单独的模块:
  1. 注册路由模块
const express=require("express")
const app=express()

// 导入路由模块
const router=require("./router")
// 注册路由模块
app.use(router)

app.listen(80,()=>{
    console.log("http://127.0.0.1")
})

// 这是路由模块
const express=require("express")
// 创建路由对象
const router=express.Router()

// 挂载具体路由
router.get('/user/list',(req,res)=>{
    res.send("get user list")
})
router.post("/user/add",(req,res)=>{
    res.send("add new user")
})

// 向外导出路由对象
module.exports=router
  1. 为路由模块添加前缀
  • 类似于托管静态资源时,为静态资源统一挂载访问前缀一样,路由模块添加前缀的方式也非常简单

5.6 Express中间件

5.6.1 中间件的概念

  1. 完成某项任务的各个环节可以称为中间件
  2. Express中间件的调用流程
  • 当一个请求到达Express的服务器后,可以连续调用多个中间件,从而对这次请求进行预处理
  • 上一个中间件的输出会作为下一个中间件的输入
  1. Express中间件的格式
  • Express的中间件,本质上是就是一个function处理函数,Express中间件的格式如下:

注意:中间件函数的形参列表中,必须包含next参数,而路由处理函数只包含req和res

  1. next函数的作用
  • next函数是实现多个中间件连续调用的关键,他表示把流转关系转交给下一个中间件或路由器

5.6.2 Express中间件的初体验

  1. 定义中间件函数
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")
})
  1. 全局生效的中间
  • 客户端发起任何请求,到达服务器后,都会触发的中间件,叫做全局生效的中间件
  • 通过app.use(中间件函数),即可定义一个全局生效的中间件
const express=require('express')
const app=express()

// 定义一个最简单的中间件函数
const mw=function(req,res,next){
    console.log('这是一个最简单的中间件函数')
    next()
}

// 将mw注册为全局生效的中间件函数
app.use(mw)

app.get('/',(req,res)=>{
    console.log("调用了/路由")
    res.send("home page!");
})

app.get('/user',(req,res)=>{
    console.log("调用了/user路由")
    res.send("user page!")
})

app.listen(80,()=>{
    console.log("http://127.0.0.1")
})
  1. 定义全局中间件的简化形式
  1. 中间件的作用
  • 多个中间件之间,共享一份req和res,基于这样的特性,可以在上游的中间件中,统一为req或res对象添加自定义的属性和方法,供下游的中间件或路由使用
const express=require('express')
const app=express()

app.use((req,res,next)=>{
    // 获取请求到达服务器的时间
    const time=Date.now()
    req.starttime=time;
    next()
})

app.get('/',(req,res)=>{
    res.send("home page!"+req.starttime);
})

app.get('/user',(req,res)=>{
    res.send("user page!"+req.starttime)
})

app.listen(80,()=>{
    console.log("http://127.0.0.1")
})

Untitled

  1. 定义多个全局中间件
  • 可以使用app.use()连续定义多个全局中间件,客户端请求到达服务器后,会按照中间件定义的先后顺序依次进行调用
app.use((req,res,next)=>{
	  console.log("第一个中间件")
    next()
})
app.use((req,res,next)=>{
	  console.log("第二个中间件")
    next()
})
app.use((req,res,next)=>{
	  console.log("第三个中间件")
    next()
})
  1. 局部生效的中间件
  • 不使用app.use()定义的中间件,叫做局部生效的中间件
const express=require('express')
const app=express()

const mw1=function(res,req,next){
    console.log('调用了局部生效的中间件')
    next()
}

app.get('/',mw1,(req,res)=>{
    res.send('home page')
})

app.get('/user',(req,res)=>{
    res.send('user page')
})
app.listen(80,()=>{
    console.log('http://127.0.0.1')
})
  1. 定义多个局部中间件
  • 通过如下两种等价方式,使用多个局部中间件:
const express=require('express')
const app=express()

const mw1=function(req,res,next){
    console.log('调用了局部生效的中间件')
    next()
}
const mw2=function(req,res,next){
    console.log('调用了第二个局部生效的中间件')
    next()
}

app.get('/',mw1,mw2,(req,res)=>{
    res.send('home page')
})

app.get('/user',(req,res)=>{
    res.send('user page')
})
app.listen(80,()=>{
    console.log('http://127.0.0.1')
})

5.6.3 中间件的分类

  1. 为了方便对中间件的理解和记忆,Express官方把常见的中间件用法,分成了5大类,分别是:
  • 应用级别的中间件
    路由级别的中间件
    错误级别的中间件
    Express内置的中间件
    第三方的中间件
  1. 应用级别的中间件
  • 通过app.use()或app.get()或app.post(),挂载到app实例上的中间件,叫做应用级别的中间件,
  1. 路由级别的中间件
  • 绑定到express.Router()实例上的中间件,叫做路由级别的中间件,其用法与应用级别的中间件没有任何区别
  1. 错误级别的中间件
  • 错误级别中间件的作用,专门用于捕获整个项目中发生的异常错误,从而防止项目异常崩溃的错误
const express=require('express')
const app=express()

app.get('/',(req,res)=>{
    // 人为制造错误
    throw new Error('服务器内部发生异常')
    res.send('user page')
})

// 定义错误级别的中间件,捕获整个项目的异常错误
app.use((err,req,res,next)=>{
    console.log('发生了错误'+err.message)
    res.send('error:'+err.message)
    next()
})
app.listen(80,()=>{
    console.log('http://127.0.0.1')
})

注意:错误级别的中间件必须注册在所有路由之后

5.6.4 自定义中间件

  1. 使用app.use()来定义全局生效的中间件:
// 解析表单数据的中间件
app.use((req,res,next)=>{

})
  1. 监听req的data事件
  • 在中间件中,需要监听req对象的data事件,来获取客户端发送到服务器的数据,如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器,
app.use((req,res,next)=>{
    let str=''; // 存储客户端发送过来的请求体数据
    req.on('data',(chunk)=>{  // 监听req的data事件
        str+=chunk;
    })
})
  1. 监听req的end事件
  • 当请求体数据接收完毕后,会自动触发req的end事件
app.use((req,res,next)=>{
    let str=''; // 存储客户端发送过来的请求体数据
    req.on('data',(chunk)=>{  // 监听req的data事件
        str+=chunk;
    })

    req.on('end',()=>{  // 监听req的end事件
        // 在str中存放的是完整的请求体数据
        // 把字符串格式的数据解析为对象格式
        console.log(str)
    })
})
  1. 使用querystring模块解析请求体数据
  • nodejs内置了一个querystring模块,专门用来处理查询字符串,通过这个模块提供的parse()函数,可以轻松把查询字符串,解析为对象的格式,示例代码:
  1. 将解析出来的数据对象挂载为req.body
req.on('end',()=>{
	const body=qs.parse(str)
	req.body=body
	next()
})
  1. 将自定义的中间件封装为模块
  • 为了优化代码的结构,我们可以把自定义的中间件函数,封装为独立的模块,示例代码

5.7 使用Express写接口

5.7.1 创建API路由模块

const express=require('express')
const router=express.Router()
 // 在这里挂载对应的路由

 module.exports=router

/* ---------*/
const express=require('express')
const app=express()

// 导入路由模块
const router=require('./apiRouter')
// 把路由模块注册到app上
app.use('/api',router)

app.listen(80,()=>{
    console.log('http://127.0.0.1')
})

5.7.2 编写get接口

const express=require('express')
const router=express.Router()
 // 挂载对应的路由
router.get('/get',(req,res)=>{
    // 通过req.query获取客户端通过查询字符串,发送到服务器的数据
    const query=req.query
    // 调用res.send()方法,向客户端响应处理的结果
    res.send({
        status:0,
        msg:'get请求成功',
        data:query
    })
})

 module.exports=router

Untitled

5.7.3 编写post接口

// 定义post接口
router.post('/post',(req,res)=>{
    // 通过req.body获取请求体中包含的url-encode格式数据
    const body=req.body
    // 调用res.send()方法,向客户端响应处理的结果
    res.send({
        status:0,
        msg:'post请求成功',
        data:body
    })
})

5.7.4 CORS跨域资源共享

  1. 刚才编写的GET和POST接口,存在一个严重的问题:不支持跨域问题
  2. 解决接口跨域问题的方案主要有两种:
  • CORS(主流解决方案)
  • JSONP(有缺陷的解决方案,只支持GET请求)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
</head>
<body>
    <button id="btnGet">GET</button>
    <button id="btnPost">POSt</button>
</body>
<script>
    $(function(){
        $('#btnGet').on('click',()=>{
            $.ajax({
                type:'GET',
                url:'http://127.0.0.1/api/get',
                data:{
                    name:'zss',
                    age:20
                },
                success:function(res){
                    console.log(res)
                }
            })
        });

        $('#btnPost').on('click',()=>{
            $.ajax({
                type:'POST',
                url:'http://127.0.0.1/api/post',
                data:{
                    bookname:'水浒传',
                    author:'施耐庵'
                },
                success:function(res){
                    console.log(res)
                }
            })
        });
    })
</script>
</html>

Untitled

  1. cors中间件是Express的一个第三方中间件,通过安装和配置cors中间件,可以很方便地解决跨域问题:

Untitled

  1. CORS即跨域资源共享,由一系列http响应头组成,这些http响应头决定浏览器是否阻止前端js代码跨域获取资源
  2. 浏览器的同源安全策略默认会阻止网页跨域获取资源,但如果接口服务器配置了CORS相关的HTTP响应头,就可以解除浏览器端的跨域访问限制

注意:CORS主要在服务器端进行配置,客户端浏览器无须进行额外的配置;CORS在浏览器中有兼容性(IE10、Chrome4+、FireFox3.5+)

5.7.5 CORS相关的三个响应头

  1. Cors响应头——Access-Control-Allow-Origin
  • 响应头部中可以携带一个Access-Control-Allow-Origin字段,语法:

其中,origin参数指定了允许访问该资源的外源URL

  • 如果指定了Access-Control-Allow-Origin字段的值为通配符*,表示允许来自任何域的请求,实例代码:
  1. Cors响应头——Access-Control-Allow-Headers
  • 默认情况下,CORS仅支持客户端向服务器发送如下9个请求头:

Accept、Accept-Language、Content-Language、DPR、Content-Type……

  • 如果客户端向服务器发送了额外的请求头信息,则需要在服务器,通过Access-Control-Allow-Headers对额外的请求头进行声明,否则请求会失败
  1. Cors响应头——Access-Control-Allow-Methods
  • 默认情况下,CORS仅支持客户端发起GET、POST、HEAD请求
  • 如果客户端希望通过PUT、DELETE等方式请求服务器的数据,则需要在服务器端,通过Access-Control-Allow-Methods来指明实际请求所允许使用的HTTP方法

5.7.4 CORS请求的分类

  1. 客户端在请求CORS接口时,根据请求方式和请求头的不同,可以将CORS的请求分为两大类:
  • 简单请求
  • 预检请求
  1. 简单请求:同时满足一下两种条件的请求即为简单请求
  • 请求方式:GET、POST、HEAD三者之一
  • HTTP头部信息:Accept、Accept-Language、Content-Language、DPR、Content-Type……
  1. 预检请求:符合以下任何一个条件的请求,都需要进行预检请求
  • 请求方式为GET、POST、HEAD之外的请求method类型
  • 请求头中包含自定义头部字段
  • 向服务器发送了application/json格式的数据

在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知浏览器是否允许该实际请求,这一次的OPTION请求即为预检请求,服务器成功响应预检请求后,才会发送真正的请求,并携带真实数据

$('#btnDelete').on('click',()=>{
            $.ajax({
                type:'DELETE',
                url:'http://127.0.0.1/api/delete',
                success:function(res){
                    console.log(res)
                }
            })
        });
// 定义Delete接口
router.delete('/delete',(req,res)=>{
    res.send({
        status:0,
        msg:'delete请求成功',
    })
})

Untitled

5.8 JSONP接口

  1. 概念:浏览器通过
posted @   旺旺哈  阅读(216)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示