目录
四、思考:如何不借助url.parse方法,解析地址得到的结果和使用该方法相同?
后端的实现不仅仅只有JAVA、Python、PHP等语言,前端也有对应的语言,那就是
Node.js
。Node号称前端中的后端,作用和其他后端语言一样,可以搭建服务器、连接数据库、返回数据等。并且Node也有对应的框架,例如
Express、Remix
等等,能够快速搭建服务器,让程序员更加轻松。
******注意,在Node中,我们之前使用的部分JS方法是不存在的,例如:元素节点的获取、localStorage、location等等,如果使用了就会报错。
一、Node下载
在这就不多说如何下载了,不懂下载和安装的朋友自行去其他帖子查看,在这提供一篇个人认为比较详细的博客:
Node的下载与安装
。提醒一句,建议下载长期维护版本,不要下载新版,新版可能出现一些未知的Bug,到时候会比较麻烦。
二、模块的引入与js文件的运行
什么是模块?
模块其实就是一个个已经被他人封装好的js文件,能够直接被我们使用。在Node中,可以使用require来引入
全局模块、三方模块以及自定义模块
。
var 变量=require("模块路径")
- 全局模块:就是下载Node时自带的模块;
- 三方模块:就是Node中没有,但已经被别人写好并开源在某个网站上的模块,我们可以通过某种方式去下载;
- 自定义模块:顾名思义,就是自己定义、封装的js文件,能够在其他文件内引入并使用;
如何启动已经引入模块的JS文件
?
终端进入保存着该文件的文件夹后,使用
node 文件名.js
运行文件。
我的app.js保存在某个文件夹中,进入该文件夹后直接使用上述命令运行指定文件。
三、Node常用全局模块
1、querystring模块
querystring模块作用是拆解参数,变为键值对形式。
例如:
var qs = require("querystring");
var url = "name=txl&age=18";
console.log(qs.parse(url)); //{ name: 'txl', age: '18' }
可以看出,
querystring.parse
根据&符号进行分割。
但该模块有很大的局限性:
只能解析传递的参数部分
,如果传递的是一个完整的url,那么解析的结果就不是我们想要的,所以不推荐使用该模块。
var qs = require("querystring");
var url = "https://localhost:8080?name=txl&age=18";
console.log(qs.parse(url));
/*
{
'https://localhost:8080?name': 'txl',
age: '18'
}
*/
2、url模块
url模块可以解析完整的网址,即
可以得到网址的各个部分
,例如:协议、域名、端口号、请求路径、请求参数等。
url中有很多的方法,但我们最常用的就是
url.parser()
。该方法能帮助我们得到上述的所有东西。至于其他方法,有兴趣的小伙伴自行查阅官方文档。
Node.js官方中文文档
。
语法:
url.parse(地址,true/false)
。
- 第一个参数就是要解析的地址;
-
第二个参数是一个布尔值,表示是否要调用
querystring.parse()
解析地址中的参数部分,true表示需要,false表示不需要,
默认是false
。
var url = require("url");
var urlstr = "https://localhost:8080/s/index.html?name=txl&age=18";
console.log(url.parse(urlstr, true));
/*
Url {
protocol: 'https:',
slashes: true,
auth: null,
host: 'localhost:8080',
port: '8080',
hostname: 'localhost',
hash: null,
search: '?name=txl&age=18',
query: [Object: null prototype] { name: 'txl', age: '18' },
pathname: '/s/index.html',
path: '/s/index.html?name=txl&age=18',
href: 'https://localhost:8080/s/index.html?name=txl&age=18'
}
*/
3、fs模块
fs模块:FileSystem,文件系统,提供了可以操作文件的API,例如:
读取、写入、追加
。
1)、写入
语法:
fs.readFile(“文件路径”,”可选参数:设置读取结果的字符编码”,回调函数)
,异步的读取文件。
-
第二个参数是可选参数
,如果不设置字符编码,那么读取的结果就是十六进制的; -
回调函数
有两个形参
,第一个是err,也就是当读取错误时,err才有值,否则为null,第二个是data,也就是读取的结果,所以是一个错误优先的回调函数;
const fs = require("fs");
// 读取文件
fs.readFile("./public/css/style.css", "utf8", (err, data) => {
if (err) throw err;
console.log(data)
});
2)、读取
语法:
fs.writeFile(“文件路径”,”要写入的内容”,”可选参数:设置字符编码”,回调函数)
,异步的写入文件,
会把原本的内容覆盖
。
fs.writeFile("./public/css/style.backup.css", "123456", () => {
console.log("写入完毕");
});
注意:当文件的读取和写入同时存在于相同作用域时,会先执行写入方法,后执行读取方法。
3)、追加
语法:
fs.appendFile(“文件路径”,”要写入的内容”,”可选参数:设置字符编码”,回调函数)
,异步追加文件内容,不会把原本的内容覆盖。
fs.appendFile("./public/css/style.backup.css", "123456", () => {
console.log("追加完毕");
});
常用模块肯定不止这些,但目前这三个(可用的就两个)其实已经够初学者使用了。
4、http模块
该模块用于
使用代码搭建服务器
。
前端的一切action、href、src,所有引入路径的地方,全都被node.js当作了是一个路由请求,解析请求,读取对应的文件给用户看,所以需要我们手动的搭建服务器,返回这些用户所需要的资源。
在我们开发时,搭建的都是本地服务器,即只有自己和同一局域网中的人才可以访问。
- 如果自己想要访问自己,打开浏览器,可以使用127.0.0.1(或者localhost)+端口号进行访问;
- 访问别人(前提:防火墙要关闭),需要别人把自己的ipv4地址给你,地址栏输入别人的ip+端口号进行访问;
使用http模块搭建服务器的固定步骤:
- 第一步:引入http模块
const http=require("http")
-
第二步:使用
createServer方法
创建服务器
const a=http.createServer()
-
第三步:使用
listen方法
为服务器指定一个端口进行监听
const http=require("http")
const a=http.createServer()
app.listen(端口号)
-
第四步:使用
on方法
给服务器绑定请求事件
const http=require("http")
const a=http.createServer()
app.listen(端口号)
app.on("request",(req,res)=>{})
-
app.on()的第一个参数
必须传入
“request”; -
app.on()的
第二个参数是一个回调函数
,该回调函数有两个参数,第一个参数是前端发来的请求,第二个参数是后端返回给前端的东西;-
req:request,保存着
请求对象
,有一个
req.url属性
,我们拿到这个属性值之后,就需要进行解析操作(url.parse方法),获取有用的信息; -
res:response,保存响应对象,提供了一个方法:
res.end(“要响应的东西”)
,可以把数据返回给前端;
-
5、http、fs、url的综合运用
// 引入http模块
var http = require("http");
var url = require("url");
var fs = require("fs");
// 创建服务器
var app = http.createServer();
// 为服务器绑定请求事件,回调函数内执行操作
app.on("request", (req, res) => {
/*
req:request,保存着请求对象,有一个req.url属性,
我们拿到这个属性值之后,就需要进行解析操作,获取有用的信息
*/
var urlobj = url.parse(req.url, true);
// 设置响应头,可以设置返回的编码格式
// res.writeHead(200, { "content-type": "text/plain;charset=utf-8" });
if (urlobj.pathname == "/" || urlobj.pathname == "/index.html") {
/*
res:response,保存响应对象,提供了一个方法:res.end("要响应的东西")
*/
fs.readFile("./public/html/index.html", (err, data) => {
res.end(data);
});
} else if (urlobj.pathname.match(/html/)) {
console.log(urlobj.pathname);
fs.readFile("./public" + urlobj.pathname, (err, data) => {
res.end(data);
});
} else if (urlobj.pathname.match(/jpg|png|webp|js|css/) != null) {
console.log(urlobj.pathname);
fs.readFile("./public" + urlobj.pathname, (err, data) => {
res.end(data);
});
}
});
// 为服务器设置监听的端口号
app.listen(3000, () => {
console.log("服务器启动成功");
});
四、思考:如何不借助url.parse方法,解析地址得到的结果和使用该方法相同?
咱就是说,直接上代码吧~注释什么的都有。
function UrlParse(url) {
// 以 ftp://www.baidu.com:443/index.html?name=dy&pwd=123123 为例
// 创建解析后保存值的空对象
var urlobj = {};
// 根据 : 截取字符串,第一个:前的是协议
if (url.indexOf(":") >= 8) {
// 如果获取到的第一个:下标超过了8,则说明网址不是从从浏览器复制的,默认添加协议http
urlobj.protocol = "http";
} else {
urlobj.protocol = url.substring(0, url.indexOf(":"));
// 在获取到协议后,应该将原本的路径改变,从第一个:开始,+3是因为:后还有两个/,要保存的后续路劲不需要 协议:// 这些东西
url = url.slice(url.indexOf(":") + 3); //此时的路径变为 www.baidu.com:443/index.html?name=dy&pwd=123123
}
// 提前设置好主机名
var hostname = null;
// 如果除去 协议:// 后,还存在 / ,表示请求的是当前网站下的子网页
if (url.indexOf("/") != -1) {
// 那么就截取到 / 之前就是主机名
hostname = url.slice(0, url.indexOf("/"));
} else {
// 没有 / 就代表只是www.baidu.com:443,即没有子路由,直接赋值
hostname = url;
}
// 根据 : 分割,即将域名和端口号分隔开,不论是否存在端口号,都至少能找到域名
var host = hostname.split(/:/);
urlobj.hostname = host[0];
// host[1]就是表示端口号,如果端口号不为false,则说明存在端口号
if (host[1]) {
urlobj.port = host[1];
} else if (urlobj.protocol == "http" && !host[1]) {
// 如果不存在端口号,但是协议为http的话,说明使用的是默认端口号80
urlobj.port = "80";
} else if (urlobj.protocol == "https" && !host[1]) {
// 如果不存在端口号,但是用的协议是https的话,则说明使用的是默认端口号443
urlobj.port = "443";
} else {
// 既不是http,也不是https,也没有端口号的话,表示的是使用的其他协议的默认端口,我这设置为/
urlobj.port = "/";
}
// 提前设置好urlobj中的路径属性
urlobj.pathname = null;
// 提前设置好参数对象
urlobj.query = {};
// 如果除去 协议:// 后,还存在 / ,表示请求的是当前网站下的子网页
if (url.indexOf("/") != -1) {
// 从 / 开始截取,即舍去主机名,只要子路由和参数部分
url = url.slice(url.indexOf("/")); //此时的url为:/index.html?name=dy&pwd=123123
// 根据?分割,不论是否存在?,都至少会出现一个数组,只是说当没有?时,该数组的长度为1,里面保存的是子路由,长度为2表示的是子路由和请求的参数。
urlobj.pathname = url.split("?")[0];
} else {
// 没有 / 代表没有子路由,所以设置pathname为null
urlobj.pathname = "null";
// 没有子路由就代表着没有参数,之前已经设置好了query为空对象了,所以可以直接返回解析好的路径对象urlobj
return urlobj;
}
// 进入到这说明是有子路由的,就判断有没有参数,如果url.split("?")[1]为true,就说明是有参数
if (url.split("?")[1]) {
// 有参数的话,就根据&进行分割
var queryarr = url.split("?")[1].split("&");
for (var i = 0; i < queryarr.length; i++) {
// 根据=分割,分割之后只会出现长度为2的数组
// 下标为0代表键
var key = queryarr[i].split("=")[0];
// 下标为1代表值
var item = queryarr[i].split("=")[1];
urlobj.query[key] = item;
}
}
return urlobj;
}
var urlobj = UrlParse(
"ftp://www.jd.com:443/index.html/a/b.html?name=dy&pwd=123123&age=18"
);
console.log(urlobj);
五、三方模块的下载
之前有提到过除了官方模块之外,还有第三方模块的存在。那么第三方模块是存在于哪里呢?所有的三方模块(这里说的是JavaScript的第三方模块)都存在于
npm官网
上。
那么如何下载三方模块?答案是通过npm命令。
1、npm命令的了解与使用
npm 为你和你的团队打开了连接整个 JavaScript 天才世界的一扇大门。它是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个
包(package)
(即,代码模块)。来自各大洲的开源软件开发者使用 npm 互相分享和借鉴。包的结构使您能够轻松跟踪依赖项和版本。–来自npm中文文档
说白了就是专门管理第三方模块的,用于模块的下载、安装、删除、更新、维护包的依赖关系 等等。
npm的使用方式需要在终端界面下才可以,windows系统可以使用
“win”键+R键,然后输入“cmd”回车打开终端
。
在打开终端后,需要使用cd命令进入到你想把三方模块下载到的文件夹中 ,cd命令不知道的朋友,可以自行查找相关资料,或者查看我这提供的别的博客:
Window系统 cd命令
。
或者直接在存储三方模块的文件夹中,在顶部地址栏输入cmd+回车也可以直接进入终端界面。
2、npm的常见命令
- npm下载命令:npm install 包名。
例如我们想要下载一个mysql包:
也可以使用简写形式:npm i 包名。
- npm删除命令:用于删除已经安装好的包,npm uninstall 包名
也可以使用简写形式:npm un 包名。
- npm更新命令:npm update 包名。
六、mongoose:连接与使用mongodb数据库
以mongodb数据库为例,通过使用mongoose对mongodb进行操作(增删改查等)。
注意:使用mongoose之前,要先下载mongoddb数据库才行:
Mongodb的下载与安装
。
后续内容会出现Promise的语法,我主要使用了then和catch回调,不了解的同学可以简单的理解一下:
then和catch都是回调函数:
- then只会在方法成功使用时被调用,并打印成功的结果;
- catch只会在方法调用失败是被调用,并能够输出错误结果;
xxxx().then(res=>{
//有一个res形参,如果方法调用后有返回值,那么res就能够接收这个返回值
console.log("then回调会在xxx方法执行成功时被调用")
}).catch(err=>{
//有一个err形参,如果方法调用后报错,那么errr就能够接收这个错误信息
console.log("catch回调会在xxx方法执行失败时被调用",err)
})
1、mongoose的下载
进入终端并进入指定文件夹后,使用
npm install mongoose
进行下载。
出现以上结果表示下载成功!
在文件夹中会多出一个名为:node_modules的目录,保存的就是下载的文件。
2、使用mongoose创建集合
- 创建一个JS文件,在其中引入mongoose模块。
const mongoose = require("mongoose");
1)、连接 mongodb
//在 MongoDB 中,不需要显示的去创建数据库,如果正在使用的数据库不存在,MongoDB 会自动创建
mongoose
.connect("mongodb://localhost/数据库名")
.then(() => {
console.log("数据库连接成功");
})
.catch((err) => {
console.log(err);
});
//这里的then和catch都是使用Promise封装后出现的回调函数,不了解Promise的可以去学习一下
//当然,这里的then和catch都不是必要的,可以省略不写
2)、创建集合
在mongodb中,数据表被叫做
集合
,数据被叫做
文档
。
创建集合分为两步:
-
设定集合规则:
设定规则的目的就是为了限制插入集合中的文档类型要相同,如果不同的话则会很混乱,到时候前端渲染会很麻烦
。通过mongoose中的Schema方法,传入一个对象,该对象就是集合的规则。
const cursorSchema=new mongoose.Schema({
name:String, //name列为字符串类型
author:String, //author列为字符串类型
isPublished:Boolean //isPublish列为布尔类型
})
- 创建集合并应用规则:使用model方法应用集合规则
const Course=mongoose.model("Course",courseSchema)
model方法可以传入三个参数:
- 第一个参数:模型名,后面就是根据模型名来对数据库进行操作,并且建议将模型名和变量名取相同的名字;
- 第二个参数:就是创建的规则变量名;
- 第三个参数:要将规则应用到哪个集合上。可以不填,不填的话就默认是模型名的小写复数形式,如courses;
2、创建集合规则的另外一些语法
集合规则也可以叫做
mongoose验证
。上面说到创建集合规则时,只是简单的限制了集合中每一列的数据类型,其实还有一些更加高级的限定,当要给某一项设置多个验证时,就要
使用对象写法
了。
1)、必填字段
语法:
required:[true/false,’文本’]
- 第一个参数:表示是否必填,true为必填,false表示不是必填项,不过一般设置required就是为了设置成必填;
- 第二个参数是自定义错误信息,当没有传入该字段时,会显示你在这设置的错误信息;
const Schema=mongoose.Schema({
//设置多项验证,采用对象的写法
name:{
type:String, //设置数据类型
required:[true,"name不能为空!"]
}
})
2)、限制字符串的最小长度和最大长度
语法:
-
最小长度:
minlength:[num,”文本”]
; -
最大长度:
maxlength:[num,”文本”]
;
注意:
- 第一个参数是Number类型的数字;
- 第二个参数是自定义错误信息;
const Schema = mongoose.Schema({
name: {
type: String,
required: [true, "name不能为空"],
// 最小长度
minLength: [2, "name不能小于2个字符"],
// 最大长度
maxlength: [5, "name不能大于5个字符"]
},
});
3)、去除两边的空格
有时候前端传过来的数据没有做数据处理,比如去除数据左右的空格,那么这时候就可以通过集合规则来实现最后的一道防线。
语法:
trim:true/false
;
const Schema = mongoose.Schema({
title: {
type: String,
required: [true, "标题不能为空"],
// 最小长度
minLength: [2, "标题不能小于2个字符"],
// 最大长度
maxlength: [5, "标题不能大于5个字符"],
// 去除字符串两边空格
trim: true,
},
});
4)、限制数字的最大和最小值
之前的
maxlength和minlength都是针对字符串类型的数据
,数值类型的数据有专门的限制属性。
语法:
-
min:[num,”文本”]
; -
max:[num,”文本”]
;
注意:
- 第一个参数是一个数值类型的参数;
- 第二个参数是自定义错误文本;
const Schema = mongoose.Schema({
price: {
type: Number,
// 最小长度
min: [2, "价格不能低于2元"],
// 最大长度
max: [11, "价格不能高于11元"]
},
});
5)、设置默认值
如果没有给某一项传值,那么可以设置一个默认值,用于解决数据缺失问题。
语法:default:xxx
const Schema = mongoose.Schema({
publishDate: {
type: Date,
//default 默认值
default: Date.now,
}
});
6)、指定该字段能够拥有的值
可以通过enum属性,设置当前字段能够有哪些值,如果传入的值不在列举的这些值当中,那么就会报错。
语法:enum:{values:[“值1″,”值2″,”值3″…],message:”文本”}
注意:
- enum是一个对象;
-
可以设置两个属性:
- 第一个属性是values:是一个数组类型的数据,用于列举当前字段能够传入哪些值,传入不在values中的值则会报错 ;
- 第二个属性是message:也就是自定义报错信息;
const Schema = mongoose.Schema({
category: {
type: String,
// enum 只能是指定的值,即可以列举去当前字段的可以拥有的值
enum: {
values: ["html", "css", "javascript", "vue", "react", "node", "mongoose"],
message: "请输入正确的分类",
},
}
})
7)、自定义验证
除了可以设置已经规定好的验证之外,还可以设置自定义验证,用自己的想法去验证文档是否是自己想要的 。
语法:
validate:{
validator:(v)=>{
return 要执行的操作
},
message:"文本"
}
注意:
- validate是一个对象;
-
validate中的validator是一个函数类型的属性;
- validator有一个形参v,v表示要被验证的值;
-
validator中写验证v的代码,如
v&&v>10
; - 返回值是一个布尔值;
- message就是自定义错误信息;
const Schema = mongoose.Schema({
// 自定义验证信息
author: {
type: String,
validate: {
validator: (v) => {
// 返回布尔值,true验证成功,false验证失败,v就是要被验证的值
// v不为空且长度大于4
return v && v.length > 4;
},
// 自定义错误信息
message: "作者名称不能为空且长度大于4",
},
},
});
注意:以上部分方法的第二个参数不是必须的,可以只传第一个参数。如果只传一个参数的话,那就不需要写中括号的形式了。
const Schema = mongoose.Schema({
title: {
type: String,
required: true,
// 最小长度
minLength:2,
// 最大长度
maxlength:5,
},
});
3、对文档的操作(增删改查)
1)、创建文档
创建文档,其实就是向数据库中插入文档,常用方法有两种:
方法一:创建实例对象并调用save方法
方法一分两步:
- 创建集合实例,集合就是在应用集合规则时创建的集合;
const Course=mongoose.model("Course",courseSchema)
- 调用实例下的save方法将文档保存到集合中;
// 创建集合实例,并在构造函数中传入对象,对象的内容就是要插入的文档
const course = new Course({
name: "Node.js course",
author: "xxxx",
isPublished: true,
});
//将数据保存到数据库中
course.save();
方法二:调用集合模型下的create方法
给create方法传入一个对象,对象中的内容就是要插入的文档。
Course.create({
name: "HTML",
author: "xxxx",
isPublished: false,
})
注意,
这里的Course不是实例对象
,是使用集合规则时的那个变量名,也就
是创建实例对象时的那个构造函数
。(实例对象按照规定一般首字母是小写的,如果是构造函数的话,首字母要大写。)
2)、查找文档
-
find方法
。
Course.find().then((result) => {
console.log(result);
});
find方法无论找到几条文档,
返回的都是一个数组
,如果没有返回的文档,则是一个空数组。
-
findOne方法
Course.findOne();
返回一条文档,默认返回当前集合中的第一条文档。也可以给条件。
-
可以指定范围查找文档
Course.find({ age: { $gt: 18, $lt: 50 } }).then((result) => {
console.log(result);
});
使用表达式查询某一范围的文档,find方法内传入一个对象类型的参数,对象内表示要根据哪一项来查找。
$gt表示大于,$lt表示小于
。
-
可以查找是否包含指定值
Course.find({ hobies: { $in: ["足球"] } }).then((result) => {
console.log(result);
});
同样的,也是在find方法内传入一个对象,$in表示要包含什么值。
-
查看指定字段
Course.find()
.select("name author -_id")
.then((result) => {
console.log(result);
});
在find方法后使用
.select
方法,传入一个字符串类型的参数,字符串内表示想查看哪些字段,各字段使用空格隔开。字段前面加上一个
短横线(-)
表示不想查看该字段,即最后的结果不会出现_id字段的值。
-
将查找出来的结果升序/降序排列
Course.find()
.sort("age")
.then((result) => {
console.log(result);
});
在find方法后使用
.sort
方法就可以实现排序,sort方法中传入一个字符串类型的参数,表示要根据哪个字段进行排序,默认是升序排序;
如果想要降序排序,就在传入的字段前加上
一个负号(-)
。
Course.find()
.sort("-age")
.then((result) => {
console.log(result);
});
此时的结果就是降序排序了。
-
跳过指定数量的文档以及限制查询文档结果的数量
Course.find()
.skip(2)
.limit(2)
.then((result) => {
console.log(result);
});
- skip方法是跳过指定数量的文档;
- limit方法是限制查找的结果为指定数字;
-
一般这两个方法搭配使用,进而实现
数据的分页效果
;
3)、删除文档
-
findOneAndDelete()
Course.findOneAndDelete({}).then((result) => {
console.log(result);
});
该方法是
找到一条文档并删除
,并返回删除的文档,then的形参接收的就是被删除的文档。如果不使用条件,那么默认找到的是第一条文档。
-
deleteMany()
Course.deleteMany({}).then((result) => {
console.log(result);
});
返回的结果是一个对象,表示删除了几条文档。同样的可以传入条件,根据条件删除多条文档。
4)、更新文档
-
updateOne()
Course.updateOne({ 查询条件 }, { 要修改的值 }).then((result) => {
console.log(result);
});
该方法只会找到符合条件的第一个文档并修改。
传入两个参数,每个参数都是对象类型的:
- 第一个参数:是指定查找的条件;
- 第二个参数:是要修改值;
Course.updateOne({ name: "Node.js基础" }, { name: "Python" }).then((result) => {
console.log(result);
});
-
updateMany()
Course.updateMany({ 查询条件 }, { 要修改的值 }).then((result) => {
console.log(result);
});
同updateOne,只是该方法是查找多个结果并修改。
Course.updateMany({}, { name: "Python" }).then((result) => {
console.log(result);
});
七、模块化开发
本篇博客的最开始就提到过一个概念:模块。现在正式的说一下模块
在程序开发中
的概念:
为完成某一功能所需的一段程序或子程序;或指能由编译程序、装配程序等处理的独立程序单位;或指大型软件系统的一部分。
个人理解:
也就是将我们所写的部分代码封装成一个独立的JS文件,该文件能够被其他文件引入并使用
。
那么什么是模块化开发?
- 在之前我们使用JS代码写页面功能时,都是讲所有的代码写在一个JS文件中,当代码量很多时,就不便于查看和维护;
-
模块划开发就是
将各个功能部分的代码分别封装成不同的JS文件
,然后
引入同一个主JS文件中
,当某一部分功能出现Bug时,就可以直接找到代码的位置了;
1、一个简单的模块开发项目目录
-
文件夹1
:数据库相关文件,有的数据库代码只能执行一次,如果多次执行服务器会报错,所以需要单独的建立JS文件保存相关代码; -
文件夹2
:该文件夹是使用npm命令
第一次下载
三方模块后会
自动出现的文件夹
,里面保存着已下载模块; -
文件6和7
:这两个文件是
第一次使用
npm命令下载三方模块时
自动出现的文件
,保存着一些配置信息,这里不详细说明; -
文件夹3
:保存着前端的相关文件,例如HTML、CSS、JS等; -
文件夹4
:保存着一些
自定义工具模块
,例如手机号的正则验证等;
2、模块的导入和导出
导入很简单:require(“要导入模块的路径”)。如果是全局模块,直接写模块名就好;
导出自定义模块:
-
exports.方法名=方法名
; -
module.exports={方法1,方法2…}
; - 其实exports和module.exports是同一个,但如果两个导出的内容发生了冲突,那么以module.exports为准;
当然模块的导入和导出形式不止这一种,还有其他方式,有兴趣的自行去了解。
根据前面的所有内容,已经可以实现:
后端连接数据库、对数据库进行操作、将数据返回给前端
,但还有一个点没有实现,那就是
前端如何主动的向后端索要数据?
八、AJAX的使用
前端要想主动的找后端要数据,那就需要通过发送网络请求才可以,这时候ajax就闪亮登场了。
1、什么是AJAX
ajax全名
async javascript and XML
,是
能够实现前后端交互
的能力的途径,也是我们客户端
给服务端发送消息以及接受响应
的工具,是一个
默认异步
执行机制的功能。
2、AJAX的优缺点
优点
:
-
不需要插件的支持,原生 js 就可以使用;
-
用户体验好(不需要刷新页面就可以更新数据);
-
减轻服务端和带宽的负担;
缺点
:
-
搜索引擎的支持度不够,因为数据都不在页面上,搜索引擎搜索不到;
3、AJAX的基本使用
-
在 js 中有内置的构造函数来创建 ajax 对象
-
创建 ajax 对象以后,我们就使用 ajax 对象的方法去发送请求和接受响应
1)、创建一个AJAX对象
// IE9及以上
const xhr = new XMLHttpRequest()
// IE9以下
const xhr = new ActiveXObject('Mricosoft.XMLHTTP')
2)、配置连接信息
const xhr = new XMLHttpRequest()
// xhr 对象中的 open 方法是来配置请求信息的
// 第一个参数是本次请求的请求方式 get / post / put / ...
// 第二个参数是本次请求的 url
// 第三个参数是本次请求是否异步,默认 true 表示异步,false 表示同步
// xhr.open('请求方式', '请求地址', 是否异步)
xhr.open('get', './data.php')
3)、发送请求
const xhr = new XMLHttpRequest()
xhr.open('get', './data.php')
// 使用 xhr 对象中的 send 方法来发送请求
xhr.send()
4、一个基本的AJAX请求
-
一个最基本的 ajax 请求就是上面三步
-
但是光有上面的三个步骤,我们确实能把请求发送的到服务端
-
如果服务端正常的话,响应也能回到客户端
-
但是我们拿不到响应
-
如果想拿到响应,我们有两个前提条件
-
本次 HTTP 请求是成功的,也就是我们之前说的 http 状态码为 200 ~ 299
-
ajax 对象也有自己的状态码,用来表示本次 ajax 请求中各个阶段
-
1)、ajax状态码
-
ajax 状态码 –
xhr.readyState
-
是用来表示一个 ajax 请求的全部过程中的某一个状态
-
readyState === 0
: 表示未初始化完成,也就是
open
方法还没有执行 -
readyState === 1
: 表示配置信息已经完成,也就是执行完
open
之后 -
readyState === 2
: 表示
send
方法已经执行完成 -
readyState === 3
: 表示正在解析响应内容 -
readyState === 4
: 表示响应内容已经解析完毕,可以在客户端使用了
-
-
这个时候我们就会发现,当一个 ajax 请求的全部过程中,只有当
readyState === 4
的时候,我们才可以正常使用服务端给我们的数据 -
所以,配合 http 状态码为 200 ~ 299
-
一个 ajax 对象中有一个成员叫做
xhr.status
-
这个成员就是记录本次请求的 http 状态码的
-
-
两个条件都满足的时候,才是本次请求正常完成
2)、readyStateChange
-
在 ajax 对象中有一个事件,叫做
readyStateChange
事件 -
这个事件是专门用来监听 ajax 对象的
readyState
值改变的的行为 -
也就是说只要
readyState
的值发生变化了,那么就会触发该事件 -
所以我们就在这个事件中来监听 ajax 的
readyState
是不是到 4 了
const xhr = new XMLHttpRequest()
xhr.open('get', './data.php')
xhr.send()
xhr.onreadyStateChange = function () {
// 每次 readyState 改变的时候都会触发该事件
// 我们就在这里判断 readyState 的值是不是到 4
// 并且 http 的状态码是不是 200 ~ 299
if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {
// 这里表示验证通过
// 我们就可以获取服务端给我们响应的内容了
}
}
3)、xhr.onload事件
该事件只会在
readyState
为4时触发,相比于
readystatechange
,可以直接判断status。
<body>
<script>
const xhr=new XMLHttpRequest()
xhr.open("get","./data.php")
xhr.send()
xhr.onload=function(){
console.log(xhr.readyState)
}
</script>
</body>
4)、responseText
-
ajax 对象中的
responseText
成员 -
就是用来记录服务端给我们的响应体内容的
-
所以我们就用这个成员来获取响应体内容
const xhr = new XMLHttpRequest()
xhr.open('get', './data.php')
xhr.send()
xhr.onreadyStateChange = function () {
if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {
// 我们在这里直接打印 xhr.responseText 来查看服务端给我们返回的内容
console.log(xhr.responseText)
}
}
5、AJAX案例
<body>
<div id="div"></div>
<script>
// http://xiongmaoyouxuan.com/api/tabs
//创建ajax实例化对象
const xhr = new XMLHttpRequest();
//配置请求信息
xhr.open("get", "http://xiongmaoyouxuan.com/api/tabs");
//发送请求
xhr.send();
//请求完成后
xhr.onload = function () {
//获取到的数据是JSON格式字符串,需要JSON.parse解析为JSON格式的数据
var resObj = JSON.parse(xhr.responseText).data;
render(resObj);
};
function render(obj) {
console.log(obj);
//获取到的obj.lis是一个数组,所以通过map将数组内的每一项都进行模板字符串操作并用新的变量接收
var str = obj.list.map(
(item) =>
`<li>
<img src="${item.imageUrl}">
<div>${item.name}</div>
</li>`
);
console.log(str);
div.innerHTML = str.join("");
}
</script>
</body>
6、使用 AJAX 发送请求时携带参数
-
我们使用 ajax 发送请求也是可以携带参数的
-
参数就是和后台交互的时候给他的一些信息
-
但是携带参数 get 和 post 两个方式还是有区别的
1)、发送一个带有参数的 get 请求
get 请求的参数就直接在 url 后面进行拼接就可以。
const xhr = new XMLHttpRequest()
// 直接在地址后面加一个 ?,然后以 key=value 的形式传递
// 两个数据之间以 & 分割
xhr.open('get', './data.php?a=100&b=200')
xhr.send()
2)、发送一个带有参数的 post 请求
post 请求的参数是携带在请求体中的,所以不需要再 url 后面拼接。
const xhr = new XMLHttpRequest()
xhr.open('get', './data.php')
// 如果是用 ajax 对象发送 post 请求,必须要先设置一下请求头中的 content-type
// 告诉一下服务端我给你的是一个什么样子的数据格式
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
// 请求体直接再 send 的时候写在 () 里面就行
// 不需要问号,直接就是 'key=value&key=value' 的形式
xhr.send('a=100&b=200')
九、总结
-
Node是前端中的后端语言,能够帮助我们成为全栈开发工程师,实现前端与后端的交互;
-
前端只要做出这些行为:地址栏输入地址并按下回车、src、href、form表单提交等,后端都会认为是在发起一个请求,然后进行响应,返回对应的资源;
-
后端通过http模块搭建好服务器,通过请求事件中回调函数的两个形参来进行请求的获取与响应:
1)、req:能够获取请求的路径以及参数等;
2)、res:能够将数据返回给前端;
-
前端通过创建ajax请求,向后端主动索要数据;
-
日后建议使用模块化的开发形式,保证各功能块之间的代码独立,以便日后维护;