Nodejs基础知识

此时不学 更待何时,不想毕业就失业的我 (っ °Д °;)っ

Nodejs基础

浏览器是JavaScript的前端运行环境

Node.js 是JavaScript的后端运行环境、无法调用DOM和BOM等浏览器内置API

Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine

nodejs 官网传送门

  • 基于 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 == null
    • data:如果读取成功,读取的数据,如果未指定编码格式则返回一个 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与包

  1. 包的版本号是以“点分十进制”形式进行定义的,总共有三位数字,例如2.24.1

    • 第一位数字:大版本

    • 第二位数字:功能版本

    • 第三为数字:Bug修复版本

  1. node_modules 用来存放已安装的包

    package-lock.json 配置文件记录每一个包的详细信息

  2. 在项目根目录中,package.json 配置文件,用来记录项目中安装了哪些包,从而方便剔除 node_modules,缩小上传体积

    在项目开发中,需要把node_modules文件夹,添加到.gitignore忽略文件中

  3. dependencies节点,开发和上线都需要用到,记录安装了哪些包;devDependencies节点,项目上线后不会用到,记录只在项目开发中用到的包,安装命令 npm - i 包名 –save-dev OR npm - i 包名 -D

  4. 当拿到一个提出了 node_modules 的项目后,需要先把所需的包下载到项目中,才能成功运行

    使用 npm install 可以一次性安装所有依赖包

  5. 几个关于包下载的命令

npm config get registry 查看当前下载包源

npm config set registry=https://registry.npmmirror.com/ 将官方源切换为淘宝镜像源

为了更方便切换下载镜像源,可以安装 nrm,利用nrm快速查看和切换下载镜像源

# 全局可用的下载nrm
npm i nrm -g

# 查看所有可用镜像源
nrm ls 

# 切换镜像源
nrm use taobao

  1. 在发布自己的开发包之前,需要把下载包源切换为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 传递参数

GET和POST两种基本请求方法的区别 - 在途中# - 博客园 (cnblogs.com)

托管静态资源

  • 通过 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 ?

  1. 创建路由模块:
// 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() 函数后别写代码
  • 多个中间件共享 reqres对象,在上游的中间件中为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')
})

中间件分类

  1. 应用级别的中间件

    通过 app.use()app.get()app.post() ,绑定到 app 实例上的中间件

  2. 路由级别的中间件

    绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。用法和应用级别中间件没有区别。应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上。

const app = express()
const router = express.Router()

router.use(function (req, res, next) {
  console.log(1)
  next()
})

app.use('/', router)
  1. 错误级别的中间件
    • 用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
    • 错误级别中间件的处理函数中,必须有 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')
})
  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 }))
  2. 第三方中间件

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:制定了允许访问资源的外域 URL

    res.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 三者之一)

    如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-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开发模式

  1. 基于服务端渲染的传统web开发模式

    概念:服务器发给客户端的HTML页面,是在服务器完全生成的,客户端不需要Ajax这样的技术去额外请求页面的数据

    优点:

    1. 前端耗时少
    2. 有利于SEO
    

    缺点:

     1. 占用服务器端资源
     2. 不理由前后端分离,开发效率低
    
  2. 基于前后端分离的新型web开发模式

    概念:依赖于Ajax技术的前后端分离开发,后端只提供API借口,前端使用Ajax 调用接口的开发模式

    优点:

    1. 开发体验好
    2. 用户体验好,实现页面局部刷新
    3. 减轻服务端渲染压力
    

    缺点:

     1. 不利于SEO,爬虫无法爬取页面的有效信息,解决方案:利用react、vue前端框架的SSR技术
    

如何选择:

不谈业务场景盲选开发模式都是不合理的

在企业展示网站或个人博客网站里,主要功能是展示而没有复杂交互,就使用服务器端渲染

在后台管理项目中,交互性强,使用前后端分离开发模式

在一些项目中,会采用首屏服务端渲染+其他页面前后端分离的组合开发模式

身份认证

Session 认证机制

服务端渲染推荐使用 Session 认证机制

HTTP协议的无状态性

HTTP协议的无状态性,指的是客户端的额每次HTTP请求都是独立的,服务器不会主动保留每次HTTP请求的状态

Cookie,身份认证的标识,类比于现实生活中的会员卡身份认证

Cookie是 存储在浏览器中一段不超过4kb的字符串,不同域名下Cookie 各自独立,每当客户端发送请求,会自动将当前域名下未过期的Cookie一同发送给服务器

Cookie 是服务器在第一次接受请求后发送给客户端后,之后客户端将Cookie存在浏览器中,在之后的访问中,浏览器自动将Cookie以请求头的形式发送给服务器,已表明身份。

Cookie 不具有安全性,不建议将重要的隐私数据通过Cookie的形式发送

Session,提高身份认证的安全性

Session 认证机制