Nodejs基础知识
此时不学 更待何时,不想毕业就失业的我 (っ °Д °;)っ
Nodejs基础
浏览器是JavaScript的前端运行环境
Node.js 是JavaScript的后端运行环境、无法调用DOM和BOM等浏览器内置API
Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine
- 基于 Express 框架 ,可以快速构建 Web 应用
- 基于 Electron 框架,可以构建跨平台的桌面应用
- 基于 restify 框架,可以快速构建 API 接口项目
- 读写和操作数据库、创建实用的命令行工具辅助前端开发、etc…
nodejs学习路径:
JavaScript 基础语法 + Node.js内置API模块(fs、path、http等)+ 第三方API模块(express + mysql等)
fs文件系统模块
读取文件
语法格式:
fs.readFile(path[, options], callback)
path
:文件路径options:配置选项,若是字符串则指定编码格式
encoding
:编码格式flag
:打开方式
callback:回调函数
err
:如果读取成功,err == nulldata
:如果读取成功,读取的数据,如果未指定编码格式则返回一个 Buffer;如果读取失败,data = undefined
写入数据
语法格式:
fs.writeFile(file, data[, options], callback)
file
:文件路径data
:写入内容options
:配置选项,包含encoding, mode, flag
;若是字符串则指定编码格式callback
:回调函数
// 示例:
fs.readFile("C:/Users/笔记.mp3", function(err, data) {
if(!err) {
console.log(data);
// 将data写入到文件中
fs.writeFile("C:/Users/hello.jpg", data, function(err){
if(!err){
console.log("文件写入成功");
}
} );
}
});
流式文件读取
- 简单文件读取的方式会一次性读取文件内容到内存中,若文件较大,会占用过多内存影响系统性能,且读取速度慢
- 大文件适合用流式文件读取,它会分多次将文件读取到内存中
var fs = require('fs')
var rs = fs.createReadStream('C:/Users/lilichao/Desktop/笔记.mp3')
var ws = fs.createWriteStream('b.mp3')
// pipe()可以将可读流中的内容,直接输出到可写流中
rs.pipe(ws)
路径动态拼接问题
在使用 fs 模块操作文件时,如果提供的操作路径是以
./
或../
开头的相对路径时,容易出现路径动态拼接错误的问题原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径
解决方案:在使用 fs 模块操作文件时,直接提供完整的路径,从而防止路径动态拼接的问题
直接用绝对路径 ,移植性会很差,不利于维护
__dirname
获取文件所处的绝对路径
fs.readFile( __dirname + '/files/1.txt', 'utf8', function(err, data) {
...
})
path路径模块
path 模块是 Node.js 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。
路径拼接 path.join()
const path = require('path')
const fs = require('fs')
// 注意 ../ 会抵消前面的路径
// ./ 会被忽略
const pathStr = path.join('/a', '/b/c', '../../', './d', 'e')
console.log(pathStr) //reslut: \a\d\e
获取路径文件名 path.basename()
使用 path.basename()
方法,可以获取路径中的最后一部分,常通过该方法获取路径中的文件名
- path: 文件路径
- ext: 文件扩展名
const path = require('path')
// 定义文件的存放路径
const fpath = '/a/b/c/index.html'
const fullName = path.basename(fpath)
console.log(fullName) // index.html
const 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) //result: .html
http模块
http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。
了解即可,后面主要用封装更好的express
Node.js中 不需要使用IIS、Apache这些服务器软件,仅通过几行代码,就可以对外提供web服务。
域名服务器就是提供IP地址和域名之间的转换服务的服务器,在实际应用中,URL的80端口可以被省略
创建最基本的web服务器
//1. 导入http模块
const http = require('http')
//2. 创建web服务器实例
const server = http.createServer()
//3. 为服务器实例绑定request事件,监听客户端请求
// req 请求对象 、res 响应对象
server.on("request",function(req,res){
// req.url是客户端请求的URL地址
const url = req.url
// req.method 是客户端请求的method类型 eg GET、POST
const method = req.method
const str = `Your request url is ${url}, and request method is ${method}`
// 设置 Content-Type 响应头,解决中文乱码的问题
res.setHeader('Content-Type', 'text/html; charset=utf-8')
//res.end 向客户端发送指定的内容,并结束这次请求的过程
res.end(str)
})
// 4. 启动服务器
server.listen(8080,function(){
console.log('server running at http://127.0.0.1')
})
不同的header标头传输不同的数据类型,具体可上网搜索!
查漏补缺:js 语法, 等于 ===,常量 const,变量 let
模块化
模块化概念
- 模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程,模块是可组合、分解和更换的单元。
- 好处:提高代码的复用性和可维护性,实现按需加载
Node.js 中模块的分类
- 内置模块(fs, path, http等)
- 自定义模块(用户自定义模块)
- 第三方模块(第三方开发,使用前需要先下载)
Node.js 中的模块作用域
- 和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域
- 防止全局变量污染
module对象
- 每个自定义模块中都有一个module对象,里面存储了和当前模块有关的信息。
- 在自定义模块中,可以使用
module.exports
对象,将模块内的成员共享出去,供外界使用。 - 导入自定义模块时,得到的就是
module.exports
指向的对象。
module中的exports
exports 和 module.exports 指的是同一个对象。
最终共享的结果,以 module.exports
指向的对象为准。
模块加载机制
模块第一次加载后会被缓存,即多次调用 require()
不会导致模块的代码被执行多次,提供模块加载效率。
npm与包
包的版本号是以“点分十进制”形式进行定义的,总共有三位数字,例如2.24.1
第一位数字:大版本
第二位数字:功能版本
第三为数字:Bug修复版本
node_modules 用来存放已安装的包
package-lock.json 配置文件记录每一个包的详细信息
在项目根目录中,package.json 配置文件,用来记录项目中安装了哪些包,从而方便剔除 node_modules,缩小上传体积
在项目开发中,需要把node_modules文件夹,添加到.gitignore忽略文件中
dependencies节点,开发和上线都需要用到,记录安装了哪些包;devDependencies节点,项目上线后不会用到,记录只在项目开发中用到的包,安装命令 npm - i 包名 –save-dev OR npm - i 包名 -D
当拿到一个提出了 node_modules 的项目后,需要先把所需的包下载到项目中,才能成功运行
使用 npm install 可以一次性安装所有依赖包
几个关于包下载的命令
npm config get registry 查看当前下载包源
npm config set registry=https://registry.npmmirror.com/ 将官方源切换为淘宝镜像源
为了更方便切换下载镜像源,可以安装 nrm,利用nrm快速查看和切换下载镜像源
# 全局可用的下载nrm
npm i nrm -g
# 查看所有可用镜像源
nrm ls
# 切换镜像源
nrm use taobao
在发布自己的开发包之前,需要把下载包源切换为npm官方服务器,而不是镜像源
npm unpublish –force 只可以删除72小时内发布的包
发布包的时候要慎重,不要发布没意义的包
Express
基于 Node.js 平台,快速、开放、极简的 Web 开发框架
Express 是用于快速创建服务器的第三方模块,Express 是基于内置的http模块进一步封装出来的。
Express 初体验
创建服务器,监听客户端请求,并返回内容:
const express = require('express')
// 创建 web 服务器
const app = express()
// 监听客户端的 GET 和 POST 请求,并向客户端响应具体的内容
app.post('/user', (req, res) => {
res.send('请求成功')
})
app.get('/', (req, res) => {
// 通过 req.query 可以获取到客户端发送过来的查询参数
// http://127.0.0.1/?name=cyc&age=20
console.log(req.query)
res.send(req.query)
})
// 这里的 :id 是一个动态的参数
app.get('/user/:ids/:username', (req, res) => {
// req.params 是动态匹配到的 URL 参数,默认是一个空对象
// http://127.0.0.1/user/1/cyc
console.log(req.params)
res.send(req.params)
})
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
POST和GET 区别:
GET把参数包含在URL中,POST通过request body传递参数
GET 获取参数、 POST 传递参数
托管静态资源
- 通过
express.static()
方法可创建静态资源服务器,向外开放访问静态资源。 - Express 在指定的静态目录中查找文件,并对外提供资源的访问路径,存放静态文件的目录名不会出现在 URL 中
- 如果托管多个静态资源目录,多次调用
express.static()
函数 - 访问静态资源时,会根据托管顺序查找文件
app.use(express.static('public'))
app.use(express.static('files'))
app.use('/bruce', express.static('bruce'))
/*
可直接访问 public, files 目录下的静态资源
http://localhost:3000/images/bg.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/login.js
通过带有 /bruce 前缀的地址访问 bruce 目录下的文件
http://localhost:8080/bruce/images/logo.png
*/
nodemon 监听调试工具
在编写nodejs代码时,如果修改代码,需要频繁的手动重启,非常繁琐
nodemon可以监听项目文件的变动,当代码被修改时,自动重启项目,方便开发调试
使用方法:
Express路由
路由 就是一种映射关系
路由 由三部分组成,请求的类型、请求的URL地址、处理函数
// 这就是两个最简单的路由
// 监听客户端的 GET 和 POST 请求,并向客户端响应具体的内容
app.get('/user', (req, res) => {
res.send({ name: 'zs', age: 20, gender: '男' })
})
app.post('/user', (req, res) => {
res.send('请求成功')
})
实际使用中,不能将路由直接挂载到 express 中,需要 模块化路由
how to do it ?
- 创建路由模块:
// router.js
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
注册路由模块:
const express = require('express')
5. 导入路由模块
const router = require('./router')
const app = express()
// 6. 使用app.use注册路由模块 (添加访问前缀,可有可无)
app.use('/api', router)
app.listen(80, () => {
console.log('http://127.0.0.1')
})
app.use() 函数的使用,就是用来注册 全局中间件
Express 中间件
- 中间件是指流程的中间处理环节,有输入,有输出
- Express 中间件 本质就是一个function处理函数,包含
req, res, next
三个参数,next()
参数把流转关系交给下一个中间件或路由 - next函数 就是多个中间件连续调用的关键,表示将流转关系转交给下一个中间价或路由
中间件注意事项;
- 在注册路由之前注册中间件(错误级别中间件除外)
- 中间件可连续调用多个
- 别忘记调用
next()
函数 next()
函数后别写代码- 多个中间件共享
req
、res
对象,在上游的中间件中为req和res添加自定义属性和方法,可供下游中间件或路由使用
全局中间件
客户端发起的任何请求,都会触发的中间件,即全局中间件,通过app.use来调用
const express = require('express')
const app = express()
// 定义第一个全局中间件
app.use((req, res, next) => {
console.log('调用了第1个全局中间件')
next()
})
// 定义第二个全局中间件
app.use((req, res, next) => {
console.log('调用了第2个全局中间件')
next()
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
局部中间件
不适用 app.use() 定义的中间件,叫做局部生效的中间件
const express = require('express')
const app = express()
// 定义中间件函数
const mw1 = (req, res, next) => {
console.log('调用了第一个局部生效的中间件')
next()
}
const mw2 = (req, res, next) => {
console.log('调用了第二个局部生效的中间件')
next()
}
// 两种定义局部中间件的方式
app.get('/hello', mw2, mw1, (req, res) => res.send('hello page.'))
app.get('/about', [mw1, mw2], (req, res) => res.send('about page.'))
app.get('/user', (req, res) => res.send('User page.'))
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
中间件分类
应用级别的中间件
通过
app.use()
或app.get()
或app.post()
,绑定到app
实例上的中间件路由级别的中间件
绑定到
express.Router()
实例上的中间件,叫做路由级别的中间件。用法和应用级别中间件没有区别。应用级别中间件是绑定到app
实例上,路由级别中间件绑定到router
实例上。
const app = express()
const router = express.Router()
router.use(function (req, res, next) {
console.log(1)
next()
})
app.use('/', router)
- 错误级别的中间件
- 用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
- 错误级别中间件的处理函数中,必须有 4 个形参,形参顺序从前到后分别是
(err, req, res, next)
。 - 错误级别的中间件必须注册在所有路由之后
const express = require('express')
const app = express()
app.get('/', (req, res) => {
throw new Error('服务器内部发生了错误!')
res.send('Home page.')
})
// 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err, req, res, next) => {
console.log('发生了错误!' + err.message)
res.send('Error:' + err.message)
})
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
Express 内置中间件
自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:
express.static
快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)express.json
解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)express.urlencoded
解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
app.use(express.json()) app.use(express.urlencoded({ extended: false }))
第三方中间件
CORS 跨域资源共享
CORS 中间件
CORS(Cross-Origin Resource Sharing,跨域资源共享)解决跨域,是通过 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源
浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可解除浏览器端的跨域访问限制
CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。
CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。
CORS 如何使用
- 安装中间件:
npm install cors
- 导入中间件:
const cors = require('cors')
- 配置中间件:
app.use(cors())
CORS 常见响应头
Access-Control-Allow-Origin
:制定了允许访问资源的外域 URLres.setHeader('Access-Control-Allow-Origin', 'http://bruceblog.io') res.setHeader('Access-Control-Allow-Origin', '*')
Access-Control-Allow-Headers
默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头:
Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type (值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 A
ccess-Control-Allow-Headers
对额外的请求头进行声明,否则这次请求会失败!res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')
Access-Control-Allow-Methods
默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过
Access-Control-Alow-Methods
来指明实际请求所允许使用的 HTTP 方法res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD') res.setHEader('Access-Control-Allow-Methods', '*')
CORS 请求分类
简单请求
- 请求方式:GET、POST、HEAD 三者之一
- HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三个值 application/x-www-formurlencoded、multipart/form-data、text/plain)
预检请求
- 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
- 请求头中包含自定义头部字段
- 向服务器发送了 application/json 格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据
数据库与身份认证
web开发模式
基于服务端渲染的传统web开发模式
概念:服务器发给客户端的HTML页面,是在服务器完全生成的,客户端不需要Ajax这样的技术去额外请求页面的数据
优点:
1. 前端耗时少 2. 有利于SEO
缺点:
1. 占用服务器端资源 2. 不理由前后端分离,开发效率低
基于前后端分离的新型web开发模式
概念:依赖于Ajax技术的前后端分离开发,后端只提供API借口,前端使用Ajax 调用接口的开发模式
优点:
1. 开发体验好 2. 用户体验好,实现页面局部刷新 3. 减轻服务端渲染压力
缺点:
1. 不利于SEO,爬虫无法爬取页面的有效信息,解决方案:利用react、vue前端框架的SSR技术
如何选择:
不谈业务场景盲选开发模式都是不合理的
在企业展示网站或个人博客网站里,主要功能是展示而没有复杂交互,就使用服务器端渲染
在后台管理项目中,交互性强,使用前后端分离开发模式
在一些项目中,会采用首屏服务端渲染+其他页面前后端分离的组合开发模式
身份认证
Session 认证机制
服务端渲染推荐使用 Session 认证机制
HTTP协议的无状态性
HTTP协议的无状态性,指的是客户端的额每次HTTP请求都是独立的,服务器不会主动保留每次HTTP请求的状态
Cookie ,突破这种无状态性
Cookie,身份认证的标识,类比于现实生活中的会员卡身份认证
Cookie是 存储在浏览器中一段不超过4kb的字符串,不同域名下Cookie 各自独立,每当客户端发送请求,会自动将当前域名下未过期的Cookie一同发送给服务器
Cookie 是服务器在第一次接受请求后发送给客户端后,之后客户端将Cookie存在浏览器中,在之后的访问中,浏览器自动将Cookie以请求头的形式发送给服务器,已表明身份。
Cookie 不具有安全性,不建议将重要的隐私数据通过Cookie的形式发送