基础概念
1 什么是Node.js
Node.js是一个基于Chrome V8 引擎(V8引擎的运行效率更高)的JavaScript运行环境。
V8引擎负责解析和执行js代码,内置API提供了一些能力,让我们可以做后端的一些事情。
注意:
- 浏览器是JavaScript的前端运行环境;
- Node.js是JavaScript的后端运行环境;
- Node.js中无法调用DOM和BOM等浏览器内置API。
官网地址:
https://nodejs.org/zh-cn/
2 Node.js可以做什么
- 可以基于Express框架,可以快速构建Web应用
- 基于Electron框架,可以构建跨平台的桌面应用
- 基于restify框架,可以快速构建API接口项目
- 读写和操作数据库、创建使用的命令行工具辅助前端开发
内置API
fs文件系统模块
- 读取文件
fs.readFile(path, options, callback); /**第一个参数为路径(必须) 第二个参数是可选参数,以什么编码格式来读取文件 第三参数是回调函数(必须)**/
code:
// 1.导入模块
const fs = require("fs");
// 2.调用fs.readFile()
// 参数1:文件存放路径
// 参数2:读取文件采用的编码格式
// 参数3:回调函数,拿到读取失败和成功的结构 err dataStr
fs.readFile("./file/data.txt", "utf-8", function (err, dataStr) {
// 如果读取成功, 则err的值为null
// 如果读取失败,则err的值为错误对象
console.log(err);
console.log("------");
console.log(dataStr);
});
- 写文件
fs.writeFile(path, data, options, callback); /**第一个参数为路径(必须) 第二个参数是写入的数据, 第三个参数以什么编码格式来读取文件(可选) 第四个参数是回调函数(必须)**/
code:
const fs = require("fs");
// 参数1:文件存放路径
// 参数2:要写入的内容
// 参数3:可选参数以什么样的格式写入默认utf-8
// 参数4:回调函数
fs.writeFile("./file/data.txt", "hello node!!!", function (err) {
// 注意:
// 默认是直接覆盖原文件的数据
// 只能创建文件,不能创建目录
console.log(err);
});
- 动态路径
- __dirname : 表示当前目录
fs.readFile(__dirname + "/file/data.txt", function (err, data) {
console.log(err);
if (!err) {
console.log(data);
}
});
- path.join() 方法
注意他可以解析…/也就是如果拼接这个路径会向上返回一层路径,所以我们在拼接路径是尽量使用join方法,加号是直接拼接, 不会解析…/也不会解析./
const path = require("path");
path.join("/file", "/data.txt");
- path.basename() 获取文明名,含后缀
const path = require("path");
const filePath = "demo/index.html"
const fullName = path.basename(filePath)
console.log(fullName);
// 如果移除后缀名
path.basename(filePath, ".html"); // 输出结果为index
- path.extname()
const path = require("path");
const filePath = "demo/index.html"
const fullName = path.extname(filePath) // 输出结果为.html
http模块
用于创建web服务器的模块,通过http模块提供的http.createServer()方法,就能方便的把一台普通的电脑,变成一台web服务器,从而对外提供web资源服务。
code:
// 导入http 模块
const http = require("http");
// 创建服务器
const server = http.createServer();
// 为服务器绑定一个事件处理方法,监听客户端的请求
server.on("request", (req, res) => {
// req 为请求对象,它包含了与客户端相关的数据和属性例如:req.url 是客户端请求的URL地址
// req.method 是客户端 method 请求类型
console.log(req.url, req.method);
const str = `hello server ${req.method}`
// res 响应对象,可以响应请求给用户端响应数据
res.end(str);
})
// 服务器占用端口
// 占用端口 启动成功的回调函数
server.listen("8888", () => {
console.log("server is running at 127.0.0.1:8888");
})
这里我没响应的英文字符如果有中文会乱码,需要在响应对象上设置编码格式。
res.setHeader("Content-Type", "text/html;charset=utf-8")
根据客户端不同请求路径返回不同的数据:
code:
// 导入http 模块
const http = require("http");
const { url } = require("inspector");
// 创建服务器
const server = http.createServer();
// 为服务器绑定一个事件处理方法,监听客户端的请求
server.on("request", (req, res) => {
const url = req.url;
console.log(url);
// 根据不同的url返回不同的内容
let content = "<h1>404 not found</h1>";
if (url === "/" || url === "/index.html") {
content = "<h1>首页</h1>"
} else if (url === "/about") {
content = "<h1>关于页面</h1>"
}
res.setHeader("Content-Type", "text/html;charset=utf-8")
res.end(content);
})
server.listen("8888", () => {
console.log("server is running at 127.0.0.1:8888");
})
将请求路径映射为文件路径:
// 导入http 模块
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;
// 拼接文件路径
const filePath = path.join(__dirname, url);
console.log(filePath);
fs.readFile(filePath, "utf-8", function (err, data) {
if (!err) {
// 这边暂时不设置请求头,也可以判断请求url设置
res.end(data);
}
})
})
server.listen("8888", () => {
console.log("server is running at 127.0.0.1:8888");
})
文件结构如下:
如下图果结果可以看到在index.html引用的外部css文件和js文件会自动发请求去请求
Node 模块化
1 node.js中模块分为三大类:
- 内置模块(例如fs,http, path)
- 自定义模块(用户创建的每个js文件, 都是自定义模块)
- 第三方模块(非官方提供,第三方开发的模块)
2 加载模块
const fs = require("fs") // 内置模块
const custom = require("./custom.js") // 自定义模块
const moment = require("moment"); // 第三方模块
注:使用require()方法加载其它模块时,会执行被加载模块中的代码。
3 模块作用域
在js中如果两个文件定义了同一个var变量,引入的时候会造成变量污染。在Node.js中模块作用域可以避免这个问题。
var uerName = "ly";
function syHello() {
console.log(uerName);
}
引入自定义模块
const custom = require('./other.js')
console.log(custom)
运行结果可见模块内的变量不能被外部使用:
那么这时候就要问了,外面怎么使用模块总的属性和方法呢?
4 模块化的使用
- module
在每个.js自定义模块中都有一个module对象,里面存储了和当前模块有关的信。
console.log(module); // 所以变量名最好不要用这个名字
exports : 向外共享成员
const age = 20;
module.exports.userName = "ly"
module.exports.syHello = function syHello() {
console.log(uerName);
}
// 暴露的另一种方式
module.exports.age = age;
引入自定义模块
const custom = require('./other.js')
console.log(custom)
简写形式
// 可以省略module
const age = 20;
exports.userName = "ly"
exports.syHello = function syHello() {
console.log(uerName);
}
// 暴露的另一种方式
exports.age = age;
运行结果如下,现在就可以访问自定义模块的成员了:
注意:
暴露的成员永远是module.exports指向的对象
const age = 20;
module.exports.userName = "ly"
module.exports.syHello = function syHello() {
console.log(uerName);
}
// 暴露的另一种方式
module.exports.age = age;
module.exports = {
newName : 'zwb',
syHi: function(){
console.log("zwb nb!");
}
}
运行结果,可见上面的暴露被覆盖,真正暴露的是下面的对象:
CommonJS规范:
- 每个模块,module变量代表当前模块。
- module变量是一个对象,它的exports属性是对外的接口。
- 加载某个模块其实是加载了该模块的module.exports属性。
NPM与包
Node.js中的第三方模块就是包。npm官网有很多开源包https://www.npmjs.com/
安装包,安装相关信息会记录到package.json文件里
npm i 包名 // 安装包
npm i 包1 包2 // 一次性安装多个包
npm i // 安装所有包
npm i 包名 -D // 开发依赖包,会被记录到devDependencies
npm i -g // 全局包,一般是安装工具包
初始化,包管理配置文件package.json,注意路径不要有中文。
npm init -y
express框架
1 基础
它是一个基于nodejs开发的web服务器开发框架。
官方:
安装
npm i express@4.17.1
2 实战
- 创建一个服务器
// 导入express
const express = require("express")
// 创建web服务器
const app = express()
// 启动服务器
app.listen(8888, () => {
console.log("server is running at 8888");
})
- 响应客户端的请求
// 导入express
const express = require("express")
// 创建web服务器
const app = express()
// 监听客户端的请求
// get
app.get("/usr", (req, res) => {
// 调用express提供的res.send()方法响应一个方json对象,res.send()可以发送一个json也可以发送普通的文本
res.send({
name: "ly",
age: "25"
})
})
// post
app.post("/usr", (req, res) => {
// 调用express提供的res.send()方法响应一个方json对象
res.send("请求成功!!!")
})
// 启动服务器
app.listen(8888, () => {
console.log("server is running at 8888");
})
- 获取URL携带的参数
1)获取?参数
app.get("/", (req, res) => {
// 通过req.query 可以获取到客户端发送过的参数
console.log(req.query);
res.send(req.query);
});
请求127.0.0.1:8888/?name=ly&age=18, 结果为:
2)获取动态参数
app.get("/demo/:id", (req, res) => {
// 通过req.query 可以获取到客户端发送过的参数
console.log(req.params);
res.send(req.params);
});
// 可以携带多个parms参数/demo/:user/:age/:salary
请求127.0.0.1:8888/demo/2,运行结果:
- 静态资源托管
expres.static()
// 导入express
const express = require("express")
// 创建web服务器
const app = express()
// 将static作为静态资源向外提供
app.use(express.static("./static"))
// 启动服务器
app.listen(8888, () => {
console.log("server is running at 8888");
})
目录结构:
运行结果:
注:Express在指定的静态目录总查找文件,并对外提供资源的访问路径。因此平台文件的目录不会出现在URL中,可以看到我们请求资源的时候并没有加static路径。托管多个静态资源目录,多次调用app.use(express.static(“./static”))即可,他会按照目录的加载顺序查找所需的文件。
// 导入express
const express = require("express")
// 创建web服务器
const app = express()
// 将static和file作为静态资源向外提供
app.use(express.static("./static"))
app.use(express.static("./file"))
// 启动服务器
app.listen(8888, () => {
console.log("server is running at 8888");
})
目录结构:
访问http://127.0.0.1:8888/index.html,结果:
如果交换file和static的顺序:
如果需要添加访问路径,按下面的方式即可:
// 如果需要加访问路径
app.use("static", express.static("./static")); // 这样就需要访问127.0.0.1:8888/static/index.html
- express 路由
express中的路由3部分组成,分别是请求类型、请求URL地址、处理函数。
1)简单用法
// 挂载路由,直接挂载到app上,不推荐
app.get("/usr", (req, res) => {
res.send("demo");
})
2)模块化路由
定义路由模块
const express = require("express");
// 创建路由对象
const router = express.Router();
// 挂载路由
router.get("/usr", (req, res) => {
res.send("getUserList")
})
router.post("/usr/add", (req, res) => {
req.send("post add user sucess")
})
// 导出路由
module.exports = router
使用路由
// 导入express
const express = require("express");
// 导入router
const router = require("./router");
// 创建web服务器
const app = express()
// 注册路由
app.use(router)
// 如果需要加访问路径127.0.0.1:8888/api/usr
// app.use("/api",router) 这样访问的时候就需要
// 启动服务器
app.listen(8888, () => {
console.log("server is running at 8888");
})
- express中间件
可以对请求进行预处理,其本质就是一个function处理函数。
// 导入express
const express = require("express");
// 导入router
const router = require("./router");
// 创建web服务器
const app = express()
// 中间件函数
const mw = function (req, res, next) {
console.log("中间件函数!!!");
// 一定要放行
next();
}
// mw注册为全局的中间件,也可以直接放一个函数在use里面不用定义额外的变量
// 如果定义了多个中间件函数,会按照use的顺序执行
app.use(mw)
// 注册路由
app.use(router)
// 启动服务器
app.listen(8888, () => {
console.log("server is running at 8888");
})
这样每个请求都会先走中间件函数,在这个函数里可以对请求进行一些处理。比如我们需要每个请求到达服务器的时间:
// 导入express
const express = require("express");
// 导入router
const router = require("./router");
// 创建web服务器
const app = express()
// 中间件函数
app.use( (req, res, next) => {
const time = Date.now();
// 将时间赋值到req中
req.startTiem = time;
})
// 注册路由
app.use(router)
// 启动服务器
app.listen(8888, () => {
console.log("server is running at 8888");
})
如果需要定义一个局部的中间件,即只对某些特定请求生效。
// 导入express
const express = require("express");
// 导入router
const router = require("./router");
// 创建web服务器
const app = express()
// 局部中间件函数
const jubu = (req, res, next) => {
console.log("调用局部中间件!!");
const time = Date.now();
req.startTiem = time;
next()
}
// 这样这个中间件就只会在当前请求生效
app.get("/test", jubu, (req, res) => {
res.send("200");
});
// 启动服务器
app.listen(8888, () => {
console.log("server is running at 8888");
})
在路由中使用多个局部中间件
定义中间件
const m1 = (req, res, next) => {
console.log("我是中间件1!!");
next();
}
const m2 = (req, res, next) => {
console.log("我是中间件2!!");
next();
}
module.exports = {
m1,
m2
}
使用
const express = require("express");
const { m1, m2 } = require("./media")
// 创建路由对象
const router = express.Router();
// 挂载路由,中间件的写法也可以直接写m1,m2
router.get("/usr", [m1, m2], (req, res) => {
res.send("getUserList")
})
router.post("/usr/add", [m1, m2], (req, res) => {
req.send("post add user sucess")
})
// 导出路由
module.exports = router
注:
一定要在路由之前使用中间件函数
一定要调用next函数,并且在next后面不要在写额外的代码
中间件的分类
1)应用级别的中间件;通过app.use(),app.get(), app.post()绑定到app实例上。
2)路由级别的中间件;绑定到express.Router()实例上的中间件。
3)错误级别中间;错误级别的中间件必须有4个参数分别是(err, req, res, next)。
// 导入express
const express = require("express");
// 导入router
const router = require("./router");
// 创建web服务器
const app = express()
app.use(router);
// 注册全局错误级别中间件,注意它必须注册在路由之后,其它的中间件要注册到路由之前
app.use((err, req, res, next) => {
console.log(err);
if (err) {
res.send("发生错误");
}
next();
})
// 启动服务器
app.listen(8888, () => {
console.log("server is running at 8888");
})
4)内置中间件express.static();express.json()解析JSON格式的请求体数据(在4.16.0+的版本可用);express.urlencoded()解析URL-encoded格式的请求体数据(在4.16.0+的版本可用)
// 配置表单解析的中间件,如果不配置req.body默认为undifined
app.use(express.json());
// 解析express.urlencoded()数据
app.use(express.urlencode({extended:false});
5)第三方中间件
安装,require,app.use()三步走即可。
自定义中间件
// 导入express
const express = require("express");
// 导入router
const router = require("./router");
const qs = require("querystring")
// 创建web服务器
const app = express()
// 自定义中间件,做一下数据的处理,中间件都是共享req和res
app.use((req, res, next) => {
let str = ""
// 监听data事件每次接收到的数据,乳沟数据过多可能会触发多次
req.on("data", chunk => {
str += chunk
})
// 监听end事件,数据已经发送完毕,接着进一步处理
req.on("end", () => {
console.log(str);
// 解析数据
const body = qs.parse(str)
// 将数据挂载到body
req.body = body;
console.log(body);
// 流转到下一步骤(中间件或路由)继续处理
next();
})
})
- express跨域问题
1)安装cros
npm i cros
2)配置
// 导入express
const express = require("express");
// 路由之前配置跨域
app.use(cros())
// 创建web服务器
const app = express()
// 启动服务器
app.listen(8888, () => {
console.log("server is running at 8888");
})
msql模块
1)安装模块
npm i mysql
2)crud操作
查询数据
const mysql = require("mysql");
const db = mysql.createConnection({
host: 'ip',
user: 'root',
password: '654321',
database: "user_center",
port: "3306"
})
db.connect();
db.query("select * from user", (err, results) => {
if (err) return console.log(err.message);
console.log(results);
})
插入数据
const mysql = require("mysql");
const db = mysql.createConnection({
host: 'ip',
user: 'root',
password: '123456',
database: "user_center",
port: "3306"
})
db.connect();
// ?占位后面执行sql语句的时候赋值
const sql = "insert into stu (name,age) values (?, ?)"
// 执行语句的时候赋值
db.query(sql, ["ly", 25], (err, results) => {
if (err) return console.log(err.message);
console.log(results);
})
更新数据
const mysql = require("mysql");
const db = mysql.createConnection({
host: 'ip',
user: 'root',
password: '654321',
database: "user_center",
port: "3306"
})
db.connect();
// ?占位后面赋值
const sql = "update stu set age=? where name=?"
// 执行语句的时候赋值
db.query(sql, [18, 'ly'], (err, results) => {
if (err) return console.log(err.message);
console.log(results);
})
注意:在插入和更新操作的时候?插入或更新的时候,如果对象和表一一对应的话就可直接写对象。
插入
const stu = { name: 'ly', age: 20 };
const sql = "insert into stu set ?"
// ?用对象
db.query(sql, stu, (err, results) => {
if (err) return console.log(err.message);
console.log(results);
})
更新
const stu = { name: 'wb', age: 20 };
const sql = "update stu set ? where name=?"
// ?用对象
db.query(sql, [stu, 'ly'], (err, results) => {
if (err) return console.log(err.message);
console.log(results);
})
删除数据
const stu = { name: 'wb', age: 20 };
const sql = "delete from stu where name=?"
// ?用对象
db.query(sql, 'ly', (err, results) => {
if (err) return console.log(err.message);
console.log(results);
})