node.js阶段总结
什么是前端和后端
后端的主要工作
- 后端为前端程序员暴露API接口;
- 后端也要操作数据库;
- 优化后端业务的性能;
前端的主要工作
- 绘制网页的页面(HTML)
- 写CSS样式美化页面、写JS做网页交互(更多的是网页的特效)
- 借助于 XHR($.ajax $.get $.post)请求后端的接口;实现前后端分离开发
- 使用前端的(框架)去完成界面的开发
- 总结:前端的主要工作:用户能看到的东西,基本上都是前端做出来;
前后端协作流程
- 将来进入工作,大家会接触到【前后端分离开发】;
- 协作开发的流程:后端为我们暴露数据接口,前端单纯的调用后端接口;
- 在当前Node阶段中,我们学习的是 后端开发;
- 在Node阶段,我们主要教大家如何写后端的接口;
环境安装
LTS 和 Current 版本区别
- LTS 是长期稳定版的意思(这个安装包用起来比较稳定)【推荐在企业中使用】
- Current 是最新特征版,这个安装包中有最新的Node特性,但是,可能有一些潜藏的Bug未解决;【推荐学习或尝鲜去使用】
下载安装
查看 Node 版本号
打开终端,在命令行输入命令node -v即可
如何进入终端呢? - 使用快捷键
windows徽标 + R打开运行面板,输入cmd后直接回车; - 在任意目录的空白位置,先按住
shift键不松开,然后,在空白位置,鼠标右键单击,会出来一个右键菜单,选择在此处打开 powershell/cmd 窗口【将来第二种方式用的会多一些】环境变量
什么是环境变量
Path环境变量的作用:能够让我们通过命令行的形式,快速启动一些应用程序;系统环境变量和用户环境变量的区别
- 用户环境变量,是每个用户私有的,用户之间不会共享;
- 全局环境变量,是共享的,只要你能登录这台计算机,就能访问到全局的环境变量;【今后在配置环境变量的时候,推荐直接配置到系统环境变量】
通过命令行快速启动应用程序时,路径的查找规则
- 先在当前 cmd 终端的目录中查找,如果有则直接运行;
- 如果当前目录中没有,则去全局的path环境变量中查找;
浏览器中的 JavaScript
Javascript的诞生
- JS 诞生 和 网景公司 有关;form
- JS 诞生的需求,就是为了做客户端表单验证的;
- JS作者刚把JS创建出来之后,它叫 LiveScript -> Javascript
浏览器一战
主角是 IE 和 网景浏览器; - 一战的果实:ECMAScript 规范的确立!
- 短暂的和平期:在和平期阶段,JS都在做什么事情呢(进行表单的验证、做简单的网页动效、狗皮膏药),一战之后,JS能力有限,当时被称作是“脚本语言”
浏览器二战
- 在 2008 年,二战开始了;
- 谷歌chrome、IE、火狐(浴火重生)
- 在 2008 年,谷歌 利用 XHR 这个对象,实现了网页的局部刷新;
- 2010 - 2013年左右,公司中,不管是Java,还是.NET, 还是 PHP(会不会Ajax)
- 在 一战 和 二战期间,JS 只能运行在 浏览器中;
浏览器中的JS组成部分:ECMAScript核心 + DOM + BOM
浏览器属于前端环境,所以,之前的JS只运行在前端浏览器中;也就是,无法使用Javascript实现后端编程;
Node中的Javascript
- Node.js 的诞生,解放了Javascript,从此之后,Javascript 就可以在 服务器端运行了;
ECMAScript
- Node中的Javascript也有一个ECMAScript核心
没有 BOM 和 DOM
- Node中并没有浏览器的概念,所以,BOM和 DOM ,Node中不需要,因此,就把它们给剔除了;
全局成员(网页和Node中都可以用)
- ECMAScript核心 + 全局成员 + 模块系统成员(这是Node平台所独有的)
- 全局成员(console.log, setTimeout setInterval)
- 模块系统成员(就是Node中的一些核心模块,提供了一些后端编程的能力)
ECMAScript 规范 和 浏览器中的JS 以及 Node 中的 JS 之间的关系
- ECMAScript 规范(标准):就是一本书,这本书中记录了基本的语法定义;
- 浏览器中的 JS:浏览器中的JS是一门具体的编程语言,实现了 ECMAScript 规范;
- 浏览器中的JS组成部分: ECMAScript 核心 + DOM + BOM
- Node中的JS:也是一门具体的编程语言,也实现了 ECMAScript 规范;
- Node中的JS组成部分: ECMAScript 核心 + 全局成员 + 模块系统成员
总结-什么是 Node.js
- 基于 Chrome 的V8 JS 解析引擎之上,解放了Javascript的编程能力,为 Javascript 提供了 后端编程的能力;
- 所以说,Node.js 是 一个后端编程的平台,用到的语言是Javascript;
Node.js 环境中执行JS代码的两种方式
REPL 环境
- 如何进入 REPL 环境: 打开任意终端,直接输入
node并回车,就会进入到 REPL 环境中; - 如何离开 REPL 环境:按两次
ctrl + c就能退出 REPL 环境; - REPL中,每个字母代表什么意思呢:
- (Node为JS提供了一个脱离浏览器平台也能被解析的执行环境)
- 直接使用
node 要执行的js文件的路径来执行指定的JS文件
- 使用 ↑ 快速定位到上一次执行的命令
- 使用 tab 键能够快速补全路径
- 使用 cls 可以清屏
ECMAScript 6常用语法
let 与 const
- 之前定义变量,用 var 关键字,用var有没有缺点:
- 变量提升问题
- 没有块级作用域
- let特性:
- 使用let关键字定义变量,不存在变量提升的问题
- let定义的变量受到{ } 作用域的影响(有{}的作用域)
- let有块级作用域
- const特性:
- 所谓的解构赋值,就是把 某个对象中的属性,当作变量,给解放出来,这样,今后就能够当作变量直接使用了
- 可以使用
:为解构出来的变量重命名1
2
3// 变量的解构赋值
const { name : name123, age, gender } = person
console.log(name123)
字符串扩展
- 模板字符串
- 模板字符串的特点: 支持换行,可以嵌入变量的值
1
2
3
4
5str +=`<tr>
<td>${arr[i].id}</td>
<td>${arr[i].name}</td>
<td>${arr[i].age}</td>
</tr>`
- startsWith() 和 endsWith()
- startsWith() 用来判断字符串,是否以指定的字符开头,如果是,返回值是 true,否则返回 false
- endsWith() 用来判断字符串,是否以指定的字符结尾;如果是,返回值是 true,否则返回 false
- padStart() 和 padEnd()
形参默认值
1
2
3function add(x, y = 0) {
return x + y;
}解构赋值和形参默认值结合使用
rest参数(定义函数的时候使用…来定义的参数叫做rest参数,定义一个接受所有参数的数组)
1
2
3
4
5
6
7
8
9
10
11
12// ------------------rest参数-------------------
function add(...args) {
console.log(args instanceof Array)
let total = 0
args.forEach(item => {
total += item
})
console.log(total)
}
add(1, 2, 3, 4)扩展运算符(在调用函数的时候,使用的…的运算符来扩展数组,…运算符称为扩展运算符)
1
2
3
4
5
6
7
8
9
10
11
12// ----------------------扩展运算符--------------
function add(...values) {
let total = 0
values.forEach(item => {
total += item
})
console.log(total)
}
const arr = [1, 2, 3]
add(...arr)
箭头函数(是ES6中新增的函数形式)【今后我们会每天写箭头函数的】
- 如何把 function 改成 箭头函数呢: 先把 function 删掉,然后,在 () 和 { } 之间,添加一个
=>就好了 - 箭头函数的特性: 箭头函数内部的 this, 永远和 箭头函数外部的 this 保持一致;
- 箭头函数,本质上就是一个匿名函数 (不能直接调用)
- 最标准的箭头函数格式是 ( 参数列表 ) => { 函数体 }
- 变体1: 如果 箭头函数左侧的 形参列表中,只有一个 形参,那么,( ) 可以省略 ( x ) => { console.log(x) } 可以改造成 x => { console.log(x) }
- 变体2:如果 箭头函数右侧的 函数体中,只有一行代码,那么, { } 可以省略 (x, y) => {console.log(x + y)} 可以改造成 (x, y) => console.log(x + y)
- 变体3:如果箭头函数 左侧 只有一个形参,右侧只有一行代码,那么, 左侧的 () 和 右侧的 {} 都可以省略 ( x ) => { console.log(x) } 可以改造成 x => console.log(x)
- 注意: 如果我们省略了 右侧的 { }, 那么,默认就会把 右侧函数体中的代码执行结果,返回出去 (x, y) => { return x + y } 可以简写成 (x, y) => x + y
对象中定义方法和属性的便捷方式
在Node中使用fs模块进行文件操作
文件读取
1 | // 1. 使用require 来导入需要的模块 |
文件写入
- 参数一: 文件路径
- 参数二: 要写入的数据
- 参数三: 可选的编码格式, 默认utf-8
- 参数四: 文件写入后的回调函数
1
2
3
4
5
6
7
8
9
10
11
12const fs = require('fs')
// 写入文件到指定文件中去(会覆盖原来的内容)
fs.write('文件路径','内容',(err) => {
// 内容的改变会覆盖上一次的上传
if(err) return console.log('写入失败了:' + err.message)
console.log('写入成功了!')
})
// fs.copyFile 也可以,但它只支持8.5.0以上的版本
fs.copyFile('./files/1.txt''./files/1-copy.txt'(err) => {
if(err) return console.log('写入文件失败:' + err.message)
console.log('恭喜,写入成功')
})
文件追加
- fs.appendFile(file,data[,option],callback)
1
2
3
4
5
6
7
8
9
10// 1. 导入 fs 文件系统模块
const fs = require('fs')
// 参数一: 要追加的文件的路径
// 参数二: 要追加的数据内容
// 第三个参数:追加时候的编码格式 默认为utf-8
// 参数三: 追加完毕之后的回调函数
fs.appendFile('./files/2.txt','\n降龙十八掌',(err) => {
if(err) return console.log('写入文件失败:' + err.message)
console.log('恭喜,写入成功')
})
fs模块中路径操作问题【难点】
- 在Node中,使用fs模块来操作文件的时候,如果我们给定的路径是一个以’./‘开头的相对路径,找文件指的是Node命令所在的目录来查找 ,所以最好使用绝对路径(拼接成绝对路径 __dirname + ‘文件名’)
- __dirname 表示当前文件所在的目录
- __filename 表示当前文件的完整路径
- const path = require(‘path’) const abspath = path.join(__dirname,’文件名’)来解决/的问题
- 注意: 今后在使用fs模块中任意方法时只要涉及路径的操作都是用_dirname做拼接
读取文件信息 fs.stat
在ES6中用path来进行路径操作
- path.join([…paths])
- path.join 来解决路径分隔符的问题
- 今后只要涉及到路径的拼接,推荐大家使用 path.join 方法
- path.sep
- path.basename(path[, ext])
- path.dirname(path)
- path.extname(path)
Javascript 是单线程的一门语言
- 什么是单线程:用户无法主动开启子线程,对于JS的运行来说,永远是主线程在执行关键代码;
- 什么是多线程:用户可以主动开启子线程; Thread td = new Thread()
- 在Node中,操作文件和网络都是比较耗时的操作;
Node中为什么大量使用异步方法
- 为什么要使用 异步方法呢: 因为 异步方法,不会阻塞CPU去执行其它任务;
- 为什么在Node中不推荐使用同步呢: 因为 同步,需要一个一个执行,耗时的操作会阻碍CPU执行后续任务,因此,效率慢;
CommonJS 模块规范和模块的使用
Node.js 实现了 CommonJS 模块化规范;
- 什么是 CommonJS 规范?
- CommonJS 是为了实现 Javascript 的模块化,而制定的一套规范;
- 为什么 Javascript 需要模块化?(require,exports,module)
- 所谓的模块化,就是在一个js文件中,能够引入其他的js文件
- 浏览器中的Javascript有没有实现模块化?(在一个JS文件中,能不能引用另外JS文件中的方法)
- 因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。
- 如何实现 Javascript 的模块化?
- 为了统一大家编写模块时候的规则,方便各模块之间的依赖和调用,于是 CommonJS 规范就应运而生了。
- 那么,CommonJS 模块化规范,到底是个什么东西??
- 定义了什么是模块
- 在NodeJs中,可以认为一个js文件就是一个模块,一个模块可以是一个js文件,也可以是由多个js文件组成的
- 一个JS模块中,如何引入其它的JS模块
- 用require引入: require(‘路径’)
- 一个JS模块中,如何向外暴露一些成员,供其它模块调用;
exports.xxx = xxxx- 使用module对象去访问当前模块,
module.exports.xxx=xxx
- 如果没有类似于 CommonJS 的规范,行不行?
- 只有大家遵守同样的规范,才能够协作开发,方便别人,同时也方便自己;
全局作用域和模块作用域
- 每个JS文件,就是一个独立的模块,在这个JS文件中,所定义的任何方法、属性、变量、常量、对象,默认都属于模块作用域,并不会属于 全局作用域;
- 如果在某个模块内部,想为 全局的 global 作用域挂载一些属性,需要显示的调用
global.***来挂载;global(全局作用域)
- 使用global这个全局对象,会造成全局变量的污染(推荐使用CommonJS规范中定义模块化机制来向外暴露成员)
require(模块引用)
每一个实现了 CommonJS 规范的模块,必须定义一个 require() 函数,使用这个 require 函数,就能够 很方便的导入其它 模块中的成员,供自己使用;exports(模块定义)
每一个模块中,如果想要把自己的一些私有成员,暴露给别人使用,那么,必须实现一个 exports 对象,这个对象,将来,如果你想把自己的成员,暴露给别人使用,只需要把自己的成员,挂载到 exports 上就行了(exports默认是空对象)module(模块标识)
这个 module 也是Common JS 规定的,它表示一个具体的模块,也是一个对象;
module.exports 和 exports 的关系
- 在一个模块中,默认情况下module.exports和exports是指向同一个空对象的
- 在一个模块中,在向外暴露成员的时候,永远以module.exports指向的对象为准
Node 中的 Javascript 由几部分组成
- ECMAScript 核心
- 全局成员
- 模块系统成员
- 模块系统成员,根据一些区别,又可以分为三大类: 核心模块、第三方模块、用户自定义模块
模块分类
核心模块
- 什么是核心模块:由Node官方提供的好用的模块,叫做核心模块;只要大家在计算机中,安装了Node这个应用程序,那么,我们的计算机中就已经安装了所有的 核心模块;
- 如何使用核心模块:
require('核心模块标识符')第三方模块
- 什么是第三方模块:一些非官方提供的模块,叫做第三方模块;注意,第三方模块,并不在我们的计算机上,如果大家需要使用某些第三方模块,必须去一个叫做
NPM的网站上搜索并下载才能使用; - 如何使用第三方模块:
- 什么是用户模块:程序员自己写的JS文件,就叫做 用户自定义模块;
- 如何使用用户模块:
require('路径标识符')
包
什么是包
- 英文名叫做
Packages,包是在模块基础上更深一步的抽象,目的是:方便分发推广基于 CommonJS 规范实现的 应用程序 或 类库; - 包可以看作是 模块、代码 和 其它资源 组合起来形成的 独立作用域;
定义一个包
- 首先包应该是一个独立的文件夹(名字应该是英文)
- 包的文件夹里面应该有一个package.json文件
- package.json是一个json文件,在文件里编写的代码必须符合json规范(编写一个对象,对象有三个属性: name,version, main)
- 自定义的包目录(图片)
规范的包结构
- 包都要以一个单独的目录而存在;
package.json必须在包的顶层目录下;package.json文件必须符合 JSON 格式,并且必须包含如下三个属性:name,version,main- name: 包的名字(名字不能为中文)
- version: 包的版本号
- main: 表示包的入口文件
- 二进制文件应该在bin目录下;
- javaScript代码应该在lib目录下;
- 文档应该在doc目录下;
- 单元测试应该在test目录下;
- Node.js对包要求并没有那么严格,只要顶层目录下有
package.json,并符合基本规范即可;包描述文件 package.json
1
2
3
4
5
6
7
8
9
10
11name:包的名称,必须是唯一
description:包的简要说明
version:符合语义化版本识别规范的版本字符串
keywords:关键字数据,通常用于搜索
maintainers:维护者数组,每个元素要包含name、email、web可选字段
contributors:贡献者数组,格式与maintainers相同。包的坐着应该是贡献者数据的第一个元素
bugs:提交bug的地址,可以是网址或者电子邮件地址
licenses:许可证数组,每个元素要包含type和url字段
repositories:仓库托管地址数组,每个元素要包含type、url和path字段
dependencies:包的依赖,一个关联数组,由包名称和版本号组成。
devDependencies:开发依赖项,表示一个包在开发期间用到的依赖项
npm
npm 的两层含义
- NPM 是一个 第三方模块的托管网站,指的就是
https://www.npmjs.com/; - NPM 是Node的包管理工具(全名叫做 Node package manager),在我们安装Node时候,就已经顺便也安装了 NPM 这个管理工具;
安装和卸载全局包
- 什么是全局的包:通过
npm install 包名 -g方式安装的包,都安装到了全局;一般全局的安装目录是C:\Users\自己的用户文件夹\AppData\Roaming\npm - 带大家演示如何安装一个全局的包:
npm install i5ting_toc -g, 注意:这里的-g表示全局安装包的意思; - 注意:一般,只有一些工具,才有全局安装的必要性;
- 如果要全局卸载某个包,比如要卸载
i5ting_toc了,直接运行npm uninstall i5ting_toc -g就可以全局卸载包了!安装和卸载本地包
- 什么是本地的包:跟着项目安装的包,叫做本地包;
- 如果拿到一个空项目,必须先初始化一个
package.json的配置文件,npm init或者npm init -y(方便些) - 运行
npm i 包名@版本号 --save去安装指定的包,本地安装的包,都安装到了node_modules的目录下 - 如果大家用的是npm 5.x的版本,可以不指定
--save命令,如果用的是 npm 3.x 的版本,则需要手动指定--save, 同时,--save有缩写形式,是:-S package-lock.json文件中记录了曾经装过的包的下载地址,方便下次直接下载包;- 卸载本地包:
npm uninstall 包名 --save其它常用命令
--save-dev它的缩写是-D- 注意:
dependencies节点,表示项目上线部署时候需要的依赖项;devDependencies节点,表示项目在开发阶段需要的依赖项,但是当项目要部署上线了,devDependencies节点中的包,就不再需要了! - 注意:当使用
npm i快速装包的时候,npm会检查package.json文件中,所有的依赖项,然后都为我们安装到项目中 --production表示只安装dependencies节点下,记录的包,不安装devDependencies节点下的包;当项目要上线了,才会使用--production命令解决 npm 下载慢问题
- 默认,NPM在下载包的时候,连接的是国外的服务器,所以,有时候如果网速不是特别好,可能下载不下来包;此时,大家可以安装一个工具,叫做
nrm,里面记录了好多下载NPM包的服务器地址,可以让我们方便的切换下载包时候请求的服务器; - 运行
npm i nrm -g(注意:只要是工具,一般都是全局 -g 安装) - 当装完 nrm 之后,可以运行
nrm ls查看所有可用的服务器列表 - 可使用
nrm use 服务器名称来切换下载包时候的服务器地址
构建web应用
Apache 是一个PHP的服务器,当我们把做好的网站,丢到 WWW 目录下,就能够使用 IP地址 + 端口号 访问我们的网站了;
- IP地址的作用,是为了表示当前网络中这台计算机唯一身份的;
- 端口号:端口号和应用程序有关联,每一个应用程序,只能独占一个端口号,不能说多个应用程序公用一个端口;
疑问:Node中,有没有类似于 Apache 这样的服务器软件,来提供对应的网站服务呢?注意:Node中,并没有现成的 类似于 Apache 的服务器软件,如果我们想通过Node,来对外托管一个网站的话,需要自己手写一个 类似于 Apache 的服务器;BS 交互模型
- 重点理解URL(统一资源定位符)的本质
在 Node 中使用模板引擎
使用
http核心模块 - 构建自己的 web server(web服务器)理解 BS 交互模型
B/S:表示 Browser / Server C/S Client / Server
- 什么是服务器:在网络节点中,专门对外提供资源服务的一台电脑;
- 什么是客户端:在网络节点中,专门用来消耗或呈现服务器中返回的数据的电脑;
- 什么是静态资源:像 .js , .css, .jpg, .html ;所谓的静态资源,就是无需数据交互,服务器直接把资源读取,并响应给客户端就完事儿;
- 什么是动态资源:当一些资源,服务器上并没有现成的,需要现在服务器端,做一层处理,最后把处理的结果返回给客户端,这样的资源,叫做动态资源;
- HTTP 协议的通信模型:
请求 - 处理 - 响应的过程;创建基本http服务器
- 1.先导入Node中提供分核心模块 http : const http = require(‘http’)
- 创建服务器 : const server = http.createServer()
- 为这个server服务器,通过on方法,绑定一个事件: server.on(‘request’,function(){
console.log(‘ok)
})
- 为这个server服务器,通过on方法,绑定一个事件: server.on(‘request’,function(){
- 200 是状态码
1
2
3res.writeHeader(200,{
"Content-Type":'text/html;chartset=utf-8'
})
实现静态资源服务器
- 后端路由的本质
静态资源请求路径问题
- 在浏览器的眼中,只要是地址栏中的地址,浏览器永远把 最后一个
/后面的符号,认为是文件名 - 对于服务器来说,客户端请求的 URL 资源路径只是一个 标识符 而已,URL 不一定非要对应实际的物理磁盘路径!
- 一定要区分html中书写的路径标识符
.././和/在浏览器中代表含义!
/表示,直接从端口号后面,开始资源请求./表示,在发起资源请求之前,浏览器需要先 把 URL 地址 ,和 资源路径做一层拼接../表示,在发起资源请求之前,浏览器需要先 把 URL 地址 ,和 资源路径做一层拼接使用
nodemon工具来自动重启web服务器- 这个工具的作用:能够实时监听 web 服务器中,代码的改变,只要代码被修改并保存了,则 nodemon 工具,会自动重新启动 web 服务器;
- 运行
npm i nodemon -g就能够在全局环境中,安装这个工具了 - 当安装完毕
nodemon之后,就可以 使用nodemon 要执行的js文件路径来运行JS文件了 - 今后在开发Web项目的时候,推荐使用 nodemon 来执行 web 服务器
Node 中的 Web 快速开发框架 - Express
定义什么是Express:
- 基于 Node.js 后端Javascript平台之上,开发出来的一套Web开发框架;
- Express中,基于 原生Node的特性,做了进一步的封装,提供了一些更加好用的方法,来提高Web开发的体验;
- Express中,并没有覆盖或者删除原生的http模块方法;
express 框架的安装和基本使用
- 直接运行
npm install express --save就可以安装Express框架了使用 express 快速托管静态资源
- 如果我们网站中,有很多静态资源需要被外界访问,此时,使用 res.sendFile 就有点力不从心了,这时候,express 框架,为我们提供了一个 内置的(中间件)
express.static('静态资源目录'), 来快速托管指定目录下的所有静态资源文件; - 用法:
app.use(express.static('public'));
- 其中,
express.static是一个express的内置中间件; app.use()方法,是专门用来注册 中间件;
当使用 第二步中的方法,把指定目录托管为静态资源目录之后,那么,这一层被托管的目录,不应该出现在 资源访问的 URL地址中;
在一个Web项目中,我们可以多次调用
app.use(express.static())在多次调用 express.static 的时候,如果文件名称有重复的,则以先注册的中间件为主!
如果项目要部署了,推荐大家配置一个叫做
compression的中间件,它能够开启服务器的GZip压缩功能;1
var compression = require('compression') app.use(compression())
为 express 框架配置模板引擎渲染动态页面
- 安装 ejs 模板引擎
npm i ejs -S - 使用 app.set() 配置默认的模板引擎
app.set('view engine', 'ejs') - 使用 app.set() 配置默认模板页面的存放路径
app.set('views', './views') - 使用 res.render() 来渲染模板页面
res.render('index.ejs', { 要渲染的数据对象 }),注意,模板页面的 后缀名,可以省略不写! - 使用res.send()来解决中文乱码问题
使用 express 框架中提供的路由来分发请求
- 什么叫做路由:前端请求的URL地址,都要对应一个后端的处理函数,那么 这种URL地址到 处理函数之间的对应关系,就叫做后端路由;
- 在Express中,路由主要负责 分发请求处理的;
在Express中,如何 定义并使用路由呢?
1
2
3
4
5
6
7
8
9
10
11// 1. 封装单独的 router.js 路由模块文件
const express = require('express')
// 创建路由对象
const router = express.Router()
router.get('/', (req, res)=>{})
router.get('/movie', (req, res)=>{})
router.get('/about', (req, res)=>{})
// 导出路由对象
module.exports = routerexpress创建的 app 服务器,如何使用 路由模块呢?
1
2
3
4// 导入自己的路由模块
const router = require('./router.js')
// 使用 app.use() 来注册路由
app.use(router)
Express 框架里 中间件的概念
- 什么是中间件
- 中间件是一个函数
- 中间价这个函数是一个路由的处理函数
- 中间件这个函数,不但是一个路由处理函数,而且在参数列表中,还有一个很重要的形参,叫做next
- 这个next是一个函数, 因此,我们在中间件中,调用这个next()函数
- 中间件的表现形式: 只要这个函数的形参列表中,有一个next函数,它就是中间件
- 中间件的作用是
- 中间件表示每一个处理环节,这些处理环节,只负责单独的处理,每当上一个中间件处理完毕后,必须把处理的原材料交给下一个中间件来继续处理
- 中间件之间,共享的是 req和res这两个对象
- 如何注册中间件
- app.use(中间件的函数)
- 中间件里
next函数的作用
- 自己模拟中间件的时候,需要使用
req.on('data', (chunk)=>{})和req.on('end', ()=>{})来获取表单,并使用querystring模块的parse方法来解析成对象; - 我们在开发中,推荐大家直接使用第三方的
body-parser中间件来解析表单;Express 框架中对中间件的5种分类
- 应用级别中间件:挂载到 app 对象身上的 中间件(函数 )
- 路由级中间件: 挂载到 router 对象上的中间件
- 错误处理中间件:参数列表中要有四个形参,从前到后分别是
err,req,res,next - 内置中间件: Express 中唯一的内置中间件
express.static(root, [options]) - 第三方中间件: 通过
npm安装的中间件,叫做 第三方中间件!
Express 中进行数据库操作
- 用的数据库是MySql
- 大家是直接安装的MySql
- 安装了一个叫做 Navicat 的软件,来方便我们操作数据库
配置 MySql 数据库环境
mysql 第三方模块的介绍和基本配置
使用 mysql 第三方模块实现 CRUD
- C: Create
- R: Read
- U: Update
- D: Delete
模块加载机制
- 只要使用
require来加载指定的模块了,那么,必然会执行被加载模块中的代码!优先从缓存中加载
- Node中,默认会把曾经加载过的模块,缓存到内存中,这样,当下次再使用相同的模块,就直接从缓存中加载就行了,能够提高模块的运行效率;
核心模块的加载机制
- 先从缓存中查找,如果有,则直接使用;
- 如果缓存中没有,则加载本地的核心模块并缓存起来,供下次使用;
用户模块的加载机制
- 先从缓存中查找,如果有,则直接使用;
- 如果缓存中没有,则根据路径标识符,加载本地的用户模块并缓存起来,供下次使用;
- 用户模块的查找规则:
index -> index.js -> index.json -> index.node第三方模块的加载机制
- 先从缓存中查找,如果有,则直接使用;
- 如果缓存中没有,则根据第三方模块的标识符,加载第三方模块并缓存起来,供下次使用;
通过学习
moment这个第三方模块来了解第三方模块的加载规则 - 会根据包的名称,直接在当前项目的根目录中,去查找一个叫做
node_modules的文件夹; - 如果有,则在
node_modules中,继续查找,一个叫做 模块引用名称的文件夹; - 如果有,则 在 模块对应的文件夹中,查找一个叫做
package.json的文件; - 如果有
package.json, 则查找 其中的main属性,并尝试加载 main 指定的文件作为入口; - 如果能正常加载
main属性中指定的文件,则模块/包加载成功! - 如果 在
package.json文件中,没有 main 属性,则会依次尝试加载 包根目录中的index.js,index.json,index.node - 如果在 包的根目录中,根本没有
package.json文件,或者 在 node_modules 目录中没有 index 相关的文件,或者,根本没有对应的包文件夹,或者在项目根目录中根本没有node_modules, 则会向上层目录中,去查找node_modules, 查找规则同上; - 如果在上一层目录中还是没有找到对应的模块,则继续向上翻,直到翻到项目所在的磁盘根目录位置;
- 如果翻到了磁盘的根目录中,还没有找到,此时,会报错!
cannot find module ***express中获取参数的几种形式
从URL地址中获取查询参数
通过 URL 地址栏中,? 形式传递的参数,可以直接通过req.query来获取;从URL地址中获取路径参数
直接通过路径标识符来传递参数,/userinfo/10/zs, 可以通过req.params来获取参数从post表单中获取提交的数据
- 借助于
body-parser来解析表单数据 npm i body-parser -Sconst bodyParser = require('body-parser')app.use(bodyParser.urlencoded({ extended: false }))
Web 开发模式
混合模式(传统开发模式)
- 以后端程序员为主,基本上不需要前端程序员,或者,前端程序员只负责画页面、美化样式、写JS特效,前端程序员不需要进行数据的交互;
- 这种开发模式,在前几年比较常见;
- 他们用的最多的是 Jquery + 模板引擎
- 后端页面 .php .jsp .aspx .cshtml
前后端分离(趋势)
- 后端负责操作数据库、给前端暴露接口
- 前端负责调用接口,渲染页面、前端就可以使用一些流行的前端框架 Vue, React, Angular
需求分析
- 后端项目运行地址:http://127.0.0.1:5000
- 前端项目运行地址:http://127.0.0.1:4000
- 前后端分离开发模式的注意点:
- 跨域问题
- 如果不考虑 表单的 Post 提交,则 可以使用 JSONP的形式来请求接口
- 但是,我们的项目中,涉及到了 英雄表单的 提交,表单提交一般都是Post
- 经过分析,由于JSONP,不支持Post,所以,我们的后端接口,无法设计成JSONP的接口;
JSONP 和 CORS 的区别
- JSONP的原理:动态创建script标签;
- JSONP发送的不是Ajax请求
- 不支持 Post 请求;
- CORS中文意思是
跨域资源共享, 本质,就是使用 XHR 对象,发送Ajax请求,来进行跨域的资源共享;- CORS 发送的是真正的Ajax请求
- CORS 支持Ajax的跨域
- 如果要启用 CORS 跨域资源共享,关键在于 服务器端,只要 服务器支持CORS跨域资源共享,则 浏览器肯定能够正常访问 这种 CORS 接口;而且,客户端在 发送 Ajax的时候,就像发送普通AJax一样,没有任何代码上的变化;
- 对于Node来说,如果想要开启 CORS 跨域通信,只需要安装
cors的模块即可;HTTP协议的无状态性
- HTTP协议的通信模型:基于
请求 - 处理 - 响应的! - 由于这个通信协议的关系,导致了HTTP每个请求之间都是没有关联的,每当一个请求完成之后,服务器就忘记之前谁曾经请求过!
- 如果纯粹基于HTTP通信模型,是无法完成登录状态保持的!每次请求服务器,服务器都会把这个请求当作新请求来处理!
- 我们可以通过 cookie 技术,实现状态保持,但是由于cookie是存储在客户端的一门技术,所以安全性几乎没有,因此不要使用cookie存储敏感的数据!
cookie介绍
什么是cookie,作用是什么
- 由于Http协议是无状态的,且传统服务器只能被动的响应请求,所以,当服务器获取到请求的时候,并不知道当前请求属于哪个客户端!
- 服务器为了能够明确区分每个客户端,需要使用一些小技术,来根据不同的请求区分不同的客户端;
- 只要有请求发生,那么必然对应一个客户端,那么,我们可以在每次客户端发起请求的时候,向服务器自动发送一个标识符,告诉服务器当前是哪个客户端正在请求服务器的数据;
- 如何提供这个标识符呢?我们可以在请求头(Request Headers)中添加一个标签,叫做
cookie,这样,每次发送请求,都会把这个cookie随同其他报文一起发送给服务器,服务器可以根据报文中的cookie,区分不同的客户端浏览器。 - 如何在客户端请求头中添加标识符?
- 在Node中可以在
writeHeader的时候,通过Set-Cookie来将cookie标识通过响应报文发送给客户端! - 客户端也可以通过一些方式来操作自己的cookie,比如通过
jquery.cookie这个插件!
- 在Node中可以在
cookie的基本使用
1 | var http = require('http'); |
通过expires设置Cookie的过期时间
1 | // 设置 过期时间 为60秒之后 |
GMT和UTC有什么区别?格林尼治标准时(GMT)与世界时(UTC)是怎么回事
cookie可以被伪造,不安全
使用谷歌插件edit this cookie,就能伪造cookie数据!所以不要使用cookie存储敏感的数据!比如登录状态和登录信息;
一些敏感的数据,应该存储都服务器端!
什么是Cookie的应用场景
- 对安全性要求不高
- 不需要存储大量的数据
- 主要应用场景,是用来做 客户端 与 服务器之间的 状态保持技术;
使用express-session来保存登录状态
什么是session
由于HTTP是无状态的,所以服务器在每次连接中持续保存客户端的私有数据,此时需要结合cookie技术,通过session会话机制,在服务器端保存每个HTTP请求的私有数据;
session原理
在服务器内存中开辟一块地址空间,专门存放每个客户端私有的数据,每个客户端根据cookie中保存的私有sessionId,可以获取到独属于自己的session数据。
在express中使用session
安装session模块
1
npm install express-session -S
导入session模块
1
var session = require('express-session')
在express中使用
session中间件:1
2
3
4
5
6// 启用 session 中间件
app.use(session({
secret: 'keyboard cat', // 相当于是一个加密密钥,值可以是任意字符串
resave: false, // 强制session保存到session store中
saveUninitialized: false // 强制没有“初始化”的session保存到storage中
}))将私有数据保存到当前请求的session会话中:
1
2
3
4// 将登录的用户保存到session中
req.session.user = result.dataValues;
// 设置是否登录为true
req.session.islogin = true;通过
destroy()方法清空session数据:1
2
3
4
5
6req.session.destroy(function(err){
if(err) throw err;
console.log('用户退出成功!');
// 实现服务器端的跳转,这个对比于 客户端跳转
res.redirect('/');
});