浏览器中的js的运行环境
浏览器里面有引擎与浏览器自带的内置api。我们编写js代码以后,js代码会调用浏览器的相关内置api(由运行环境提供的特殊接口),然后将代码提交给引擎进行解析和执行。**不调用api也可以执行代码 **
nodejs作用
相当于使用js里面的nodejs模块来做后端开发。浏览器是js的前端运行环境,则nodejs是浏览器的后端运行环境。基于express框架可以快速构建web应用。基于Electron框架可以构建跨平台的桌面应用。基于restify框架可以快速构建api接口项目。可以读写和操作数据库,创建使用的命令行工具进行前端开发。
fs文件系统模块
导入模块:
const fs=require('fs')
不可以用import导入模块,因为import是es6的内置模块,该方法不在node模块中,所以无法导入
fs模块是nodejs官方提供的,用来操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求。
fs.readFile(path[,options],callback)
path是要读取的文件的所在路径
options是你要是用什么样的字符集编码来解析,通常默认为utf-8
callback一般都为函数调用,通常为function(err,data) {}
fs.writeFile(file,data[,options],callback)
data 表示需要写入的内容
文件路径
对于文件路径的问题,我们不建议采用./或者…/的路径来编写。我们可以采用绝对路径来写文件。或者采用__dirname方法进行拼接路径。
在path中,我们可以这样写: __dirname+‘当前文件路径’
path路径模块
导入模块
const path =require('path')
path.join(字符串,多个字符串可以用逗号分隔,数组用扩展运算符)方法,用来拼接路径
const pathStr=path.join('/a','/b/c','../')
console.log(pathStr) //\a
console.log(path.join(__dirname))
path.basename(路径字符串,文件后缀名) 获取路径中的文件名
const pathh='/a/b/c/d/index.html'
var fullname=path.basename(pathh)
console.log(fullname) //index.html
var outname=path.basename(pathh,'.html') //如果调用此方法加上了文件后缀名,这么输出的就只有该文件的名字
cosnole.log(outname) //index
path.extname(文件路径字符串) 获取路径中的文件拓展名
const pathh='/a/b/c/d/index.html'
console.log(path.extname(pathh))//.html
http模块
http模块就是用于创建web服务器的模块,通过内置的http.createServer()方法,就能方便的把一台电脑变成一台web服务器,从而对外提供web服务。服务器和电脑的区别在于服务器上安装了服务器软件,例如iis,apache等。
可以通过ping方法来查看当前域名的ip地址,127.0.0.1为本机ip地址,对应的域名为localhost。
导入模块
const http=require('http')
创建web服务器基本操作
1. 导入http模块
const http=require('http')
2. 创建web服务器实例
const server=http.createServer()
3. 为服务器绑定request事件,监听客户端的请求。
server.on('request',(req,res)=>{})
4. 启动服务器
server.listen(80,()=>{})//80为端口号
req请求对象:用于访问与客户端的相关数据与属性
url这里会返回除了baseUrl以外的url,从端口号后面开始的url
method返回请求方法
res请求对象:用于访问跟服务器相关的数据或属性
end属性:向客户端发送指定的内容(展示到页面上),并结束这次请求的处理过程
最终呈现的结果如图:
const http = require('http')
const server = http.createServer()
server.on('request', (req, res) => {//一旦有用户输入127.0.0.1,就会在终端打印出有人访问
console.log(req.url) //这里会返回除了baseUrl以外的url,从端口号后面开始的url
console.log(req.method) //返回请求方法
res.setHeader('Content-Type', 'text/html;charset=utf-8') //解决中文显示乱码的问题
res.end("dguauwqjlwnbdss") //向网页展示指定的内容
console.log("有人访问")
})
server.listen(80, function() {
console.log("监听成功")
})
模块化
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立的许多小模块。使用什么方法来引用模块,在模块中使用什么语法格式向外暴露成员
模块的分类
内置模块: 如fs,path,http
自定义模块: 用户自己创建的js文件并对外暴露。调用:require(‘相对路径’)
第三方模块: 第三方库,需要下载,如moment模块
向外共享成员
module对象,它里面存储了和当前模块有关的信息
module.exports对象,向外暴露自身js文件的对象。别人可以通过require引入该文件从而访问这些共享成员
暴露对象常用方法
1. module.exports.属性名或者函数方法名=属性名或者函数方法名
2. module.exports.属性名或者函数方法名=值 //定义即共享
3. exports.属性名或者函数方法名=属性名或者函数方法名
4. module.exports={里面写方法或者函数名} //如果同时定义了123方法和4方法,统一以方法3中的暴露对象为准(指针会指向此暴露对象)
使用误区
exports.username='zs'
module.exports.gender='男'
//此段代码暴露出去会得到两个方法,因为两个暴露方法都没有创建新的对象(开辟{}),所以这个对象里面同时存储着两个方法且两个暴露方法均指向同一对象。
模块化规范
nodejs遵循了commonjs的规范:每个模块内部,module代表当前的魔魁啊。module变量是一个对象,它的exports属性是对外的接口。加载某个模块实际上是加载模块的module.exports属性,require方法用于加载该模块
npm与包
**包:第三方模块 **
第三方包:npm i 包名称@具体的版本号 ->require导入
node_modules文件夹用来存放所有已经安装到项目中的包,require()导入第三方包时,就是从这个目录中查找并加载包
package-lock.json配置文件用来记录node_modules目录下的每一个包的下载信息。
包的版本号:第一位数字:大版本,第二位数字:功能版本(没有对包进行重构,为功能版本),第三位数字:bug修复版本
包管理配置文件:package.json,记录项目有关的配置信息
创建package:npm init -y
dependencies节点:记录你使用npm i命令安装了多少包,项目开发和上线都会用到的包放在这里
一次性安装所有包:npm i
卸载包:npm uninstall 包名称
devDependence节点:针对于某些包只会在项目开发阶段会用到,项目上线之后不会用到,便可以把这些包记录到devDependencies节点中(例如webpack包)
具体操作为:npm i 包名 -D
npm install 包名 --save-dev
镜像下载包
npm config get registry 查看自己电脑的下载路径
npm config set registry=镜像源 用于切换镜像源
nrm与包
下载:npm i nrm -g
查看所有镜像源:nrm ls
将下载的包切换为其他镜像:nrm use 镜像名称
自己手写第三方包
文件中要包含:该包的js文件,该包的readme文件,该包的package.json文件。其中,package.json文件中:name属性代表的是该包的名称,version代表的是版本,main代表的是核心的js文件,description代表的是该包的描述,keywords代表的是该包所涉及的关键词,license:ISC代表的是开放源代码许可证
在json文件中,只能使用双引号
模块的加载机制
模块在第一次加载的时候就会被缓存,这意味着多次调用require模块,代码不会被执行多次。模块总是优先从缓存中加载。
在使用require方式导入文件时,若不不全文件的拓展名,则nodejs会分别尝试加载以下文件:先按照文件名字进行加载,再依次按照.js,.json,.node文件形式进行加载。若还没发现文件,则终端报错。
如果传递给require的模块标识符不是一个内置模块,也不是以路径开头的模块,nodejs会从当前父目录开始查找模块,直到路径的根目录为止。
将目录作为模块 在被加载的目录下查找package.json文件,并寻找main属性,作为require的入口
如果目录里没有package.json文件,或者main入口不存在,则nodejs会加载目录下的index.js文件。
如果以上两步都失败了,nodejs会在终端打印错误信息。
express模块
express的作用与http模块类似,是专门用来创建web服务器的。不过express是第三方包,express是http进一步封装出来的
web网站服务器:对外提供web资源的服务器(部署到云服务器上,其他主机可以通过域名访问到你的网站)
api接口服务器:对外提供api接口,前端向后端拿数据
使用express,我们可以创建这两者
创建简单的express服务器
//导入包
const express = require('express')
//创建express对象实例
const app = express()
//创建get请求
app.get('请求URL(从端口号后面开始写)',(req,res)=>{
res.send("xxxxxxxx")//res.send方法,可以将内容发送到客户端上响应
req.query方法:可以获取到客户端发送过来的查询参数(就是在url上编写的?a=1&b=2之类的参数)
req.params对象,可以访问到url中,通过:匹配到的动态参数
js文件里写/:id,请求url写/2,实际上返回的参数便是{id:'2'}
req.body可以接收客户端发送过来的请求体数据,通常接收的是post请求发过来的json或者www数据
})
//创建post请求
app.post('请求URL(从端口号后面开始写)',(req,res)=>{})
//创建监听
app.listen(80, () => {
console.log("运行在")
})
express.static()
可以用来创建一个静态资源服务器,通过如下代码便可以将public目录下的图片,css文件,javascrpit文件对外开放访问。如果需要托管多个静态资源目录,调用多个static即可
app.use(express.static('public'))//就可以访问public目录中的所有文件,url地址中不用写public文件的目录,直接写该目录下的子文件(目录即可)
app.use('/public',express.static('public'))//第一个参数为增加访问路径前缀
express中的路由
指的是客户端的请求与服务器处理函数之间的关系
路由例子:
app.方法名(路径,回调函数)
模块化路由
创建路由模块对应的js文件
调用express.Router()函数创建路由对象
const express=require('express')
const router=express.Router()
向路由对象上挂在具体的路由
app.get
app.post
用module.exports向外共享路由对象
module.exports=router
在主文件中使用app.use()函数注册模块
app.use(router)
express中的中间件
在客户端发送请求时,请求发送到服务器中,依次经过中间件处理,处理完毕后服务器相应此次请求,并发送数据到客户端 多个中间件之间共享同一分req和res,基于这样的特性,我们可以在上游的中间件中,统一为req或res对象添加自定义方法或属性,共下游的中间件使用
局部&全局生效中间件
- 全局生效中间件
express的中间件,本质上就是一个function处理函数,express中间件的格式如下:
app.use(url地址,(req,res,next)=>{//使用app.use()的中间件,客户端发起的任何请求,到达服务器之后,都会触发的中间件,是全局生效的中间件
next()//next的意思是指向了下一个中间件函数或者路由,具体情况要根据访问的url来决定
})//中间件函数的形参中必须包含next函数
- 局部生效中间件
let xx=function(){}//定义中间件函数
app.get(url地址,[中间件函数1,中间件函数2...],(req,res)=>{})//通过局部函数调用中间件函数。在请求这个地址时,我们会调用中间件进行请求,待中间件函数执行完毕后,再处理此路由请求函数
中间件的分类
应用级别中间件
绑定到app实例上的中间件,全局中间件和局部中间件都可以叫做应用级别中间件
路由级别中间件
绑定到express.Router()实例上的中间件,叫做路由级别的中间件
错误级别中间件
错误级别中间件的回调函数必须要有4个形参,分别为(err,req,res,next)
const express = require('express')
const app = express()
app.get('/', (req, res) => {
throw new Error('aaaaaa')
})
app.use('/', (err, req, res, next) => {//错误级别中间件函数必须写在所有路由器的后面,以用来捕获上一级路由所抛出的错误,这样一来程序便不会崩溃
console.log(err.message)
res.send("服务器内部发生了错误")
})
app.listen(80)
express内置中间件
express.static快速托管静态资源的内置中间件,例如html文件,图片,css样式等(无兼容性)
express.json 解析json请求体数据
app.use(express.json())//使用post发送json格式的数据时,需要先加上这一行对json格式数据进行解析,才能正确打印出来
express.urlencoded 解析url-encoded的数据
const parser=require('body-parser')//导入解析表单数据的中间件
app.use(parser.urlencoded({extended:false}))/使用post发送url-encoded格式的数据时,需要先加上这一行对url-encoded格式数据进行解析,才能正确打印出来
第三方中间件
上面的body-parser就是第三方中间件
自定义中间件
自定义解析请求体数据中间件
const express = require('express')
const app = express()
const qs = require('querystring') //导入处理qs的内置模块,解析请求体数据
app.use((req, res, next) => {
let str = ''
req.on('data', (chunk) => { //监听req对象的data事件,因为大量的数据无法一次性发送完毕,则客户端会把数据切割后分批发送到服务器。所以data事件可能会触发多次。每一次只拿到部分的data数据,所以要对接收到的数据进行拼接
str += chunk
})
req.on('end', () => { //当数据接收完毕后,我们通过end方法来接受完整的字符串
// console.log(str)
const body = qs.parse(str) //调用方法,将查询字符串转化成对象
console.log(body)
req.body = body
next() //通过next来传递到路由请求方法中
})
})
app.post('/user', (req, res) => {
res.send(req.body)
})
app.listen(80)
解决跨域
- cors解决跨域请求
cors(跨域资源共享)由一系列http响应头组成,这些http响应头决定浏览器是否阻止前端js代码跨域获取请求。一般来说,如果浏览器与接口服务器存在跨域问题的话,接口服务器的响应,浏览器是会拦截并且无法获取到相应的响应数据。因为浏览器的同源策略会默认阻止网页跨域获取资源,但如果接口服务器配置了cors相关的http请求头,就可以解决浏览器的跨域访问限制
导入:
const cors = require('cors')//在接口js文件中导入cors模块
app.use(cors())
cors响应头部Access-Control-Allow-Origin
res.setHeader('Access-Control-Allow-Origin','http://itcast.cn')//意思为下面的字段值只允许来自http://itcast.cn的请求。如果第二项写的是*则意思为允许来自任何域的请求
cors响应头部Access-Control-Allow-Headers
res.setHeader('Access-Control-Allow-Headers','Content-Type')//如果有客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers对额外的请求头进行证明,否则这次请求会失败
请求头分别有:Accept,Accept-Language,DPR,Downlink,Save-Data,Viewport-Width,Width,Content-Type (值仅限于text/plain,multipart/form-data,application/x-www-form-urlencoded三者之一)
cors响应头部Access-Control-Allow-Methods
res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE')
如果客户端希望通过什么方式来获取请求服务器的资源,则可以在服务器端,通过此请求头部来指明实际请求所允许使用的http方法
简单请求与预检请求
简单请求
简单请求为GET,POST,HEAD请求其中之一且请求头只有上述的请求头(不包含自定义请求头)
预检请求
预检请求为除了简单请求以外的其余请求类型,请求头包含自定义头部,向服务器发送了application/json格式的数据。满足其中之一编变为预检请求
服务器通信之前,会先发送option请求进行预检,服务器成功发送响应预检请求后,才会发送真正的请求(会发两次请求)
- jsonp解决跨域请求
浏览器通过
//发送jsonp请求实例
app.get('api/jsonp', (req, res) => {
const funcName = req.query.callback
const data = {
name: 'zs',
age: 22
}
const scriptStr = `${funcName}(${JSON.stringify(data)})`
res.send(scriptStr)
})
基于服务器渲染的web开发模式
服务端渲染的优缺点:
优点:前端耗时少,有利于seo优化,爬虫更容易爬取信息
缺点:占用服务器资源,不利于前后端分离,开发效率低,不利于复杂度高的项目进行开发
前后端开发的优缺点:
优点:开发体验,用户体验好,减轻了服务器端的渲染压力
缺点:不利于seo的优化
身份认证
服务器渲染:Session认证机制
前后端分离:JWT认证机制
突破http无状态限制:cookie
cookie(以键值对形式进行存储):不同域名下的cookie各自独立,每当服务器发起请求时,会自动把当前域名下所有未过期的cookie一同发送到服务器
服务器端认证cookie:在服务器端进行session认证机制:{
当从服务器登陆的时候,提交账号密码表单到服务器上,服务器会开辟一块内存存储cookie,同时把set-cookie发送到客户端。客户端第二次请求时需要带着这个cookie进行请求,服务器从内存中会验证此浏览器发送过来的cookie信息,若信息认证成功,服务器便可以像客户端发送响应内容
}
在express中使用session认证
1. 安装express-session中间件
npm install express-session
2. 配置session中间件
app.use(session({
secret:'keyboard cat', //secret属性的值可以为任意字符串
resave:false, //固定写法
saveUninitialized:true //固定写法
}))
局限性:session认证需要配合cookie才能实现,由于cookie默认不支持跨域访问,所以当涉及到前后端接口的时候要增加cors跨域请求等诸多请求
JWT认证机制
当需要跨域请求调用接口时,不推荐使用session身份认证机制,推荐使用jwt认证机制
与session认证机制不同的点在于,jwt在初次请求的时候,服务器端返回的是一个token字符串,当客户端再次发送请求时,客户端便发送authorization字段通过token发送到服务器以建立链接
jwt组成部分:Header(为了保证安全性).Payload(真正的用户信息).Signature(保证安全性)。携带用户信息的json文件经过加密后dedao上述jwt字符串
安装jwt相关的包:
npm install jsonwebtoken(用于生成字符传串)) express-jwt(用于将jwt字符串解析还原成json对象)
const express = require('express')
const jwt = require('jsonwebtoken')
var { expressjwt: expressJWT } = require('express-jwt')
const app = express()
const secret = '这里写自己的密钥' //用于对jwt字符串的加解密
app.use(express.urlencoded({ extended: false }))
//将jwt字符串还原成json对象的中间件,将解析出来的用户信息,挂载到req.user属性上(expressjwt是自动会把解析的用户信息放到req.user里面的)
app.use(expressJWT({ secret: secret, algorithms: ['HS256'] }).unless({ path: ['/api/jwt'] })) //unless方法可用于配置不需要访问权限的(不需要在请求中加上token字符串)
app.post('/api/jwt', (req, res) => {
const body = req.body
const tokenStr = jwt.sign({ username: body.username }, secret, { expiresIn: '100h' }) //调用jwt.sign方法,将用户信息加密成jwt字符串,响应给客户端
res.send({
status: 0,
msg: 'success',
token: tokenStr
})
})
app.get('/username', (req, res) => {
res.send({
status: 200,
msg: '获取用户身份信息成功',
data: req.auth
})
})
app.use((err, req, res, next) => {
console.log('报错了')
})
app.listen(80)
在app.get请求的时候:需要在请求头Headers里面加上Authorization去进行请求(因为在expressjwt中定义了请求所需要携带token,并且unless中一般都是登录路径,也就是第一次进行连接的路径。它的作用是获取token。)。此外,value值的jwt加密token需要在字符串前面加上’Bearer '字段,才可以正常进行请求。此外,data数据段中想要获取解密的数据要用req.auth属性进行访问与接收
下面分别看一下调用post请求和get请求的响应结果
post请求:
如下图:token的value值便是响应回来的密文
get请求:
如下图:因为在post请求加密生成token的时候,加密数据就指定了username属性,所以最终获取到的也只是username属性。另外的iat与exp属性是用来记录token的失效时间的