ajax 封装解析
- 封装方案
- 选择回调函数 将来使用的时候需要按照回调函数的语法使用 但是容易出现回调地狱
- 选择 promise 的形式 按照 Promise 的形式来使用 后续可以利用 async/await 语法进一步简化代码
- 决定参数
- 请求地址(url): 必填
- 请求方式(method): 选填, 默认 GET
- 是否异步(async): 选填, 默认 true 异步
- 携带的参数(data): 选填, 默认 '' 空字符
- 决定返回值
- 需要
- 返回一个 promise 对象
- 因为需要依赖返回值来决定使用 then 还是 catch
- 参数书写顺序
function ajax(url, method = "GET", async = true, data = "") {}
ajax("qwert", "GET", true, "name=123");
function ajax(options) {}
ajax 封装参数验证
function ajax(options = {}) {
// 1.1 验证参数---url 必传
if (options.url == undefined) throw new Error("url 为必填项, 请填写后再试");
// 1.2 验证参数---method 可以不传, 可以为 GET, 可以是 POST, 但不能是其他的
if (
!(options.method === undefined || /^(GET|POST)$/i.test(options.method))
) {
throw new Error("mehtod 目前仅支持 GET 或 POST");
}
// 1.3 验证参数---async 可以不传, 可以为 true, 可以是 false, 但不能是其他的
if (!(options.async === undefined || typeof options.async === "boolean")) {
throw new Error("async 目前仅支持 布尔值(Boolean)");
}
// 1.4 验证参数---data 可以不传, 可以为一个字符串, 可以是一个对象
if (
!(
options.data === undefined ||
typeof options.data === "string" ||
options.data.constructor === Object
)
) {
throw new Error("data 目前仅支持 字符串(String) 或 对象(Object)");
}
// console.log('这里执行说明参数的 url 已经传递了')
}
ajax({
url: "qwer",
// method: 'POST',
// async: false,
data: "name=QF666&age=18",
});
ajax 封装默认值
function objToStr(obj) {
let str = "";
for (let k in obj) {
str += `${k}=${obj[k]}&`;
}
return str.slice(0, str.length - 1);
}
function ajax(options = {}) {
// 1.1 验证参数---url 必传
// 1.2 验证参数---method 可以不传, 可以为 GET, 可以是 POST, 但不能是其他的
// 1.3 验证参数---async 可以不传, 可以为 true, 可以是 false, 但不能是其他的
// 1.4 验证参数---data 可以不传, 可以为一个字符串, 可以是一个对象
// 2.1 处理默认值
const _options = {
url: options.url,
// 代码执行到这里, method 要么是 undefined 要么是 GET或者POST
method: options.method || "GET",
// 代码执行到这里, async 要么是 undefined 要么是 true 或者 false
// async: typeof options.async === 'boolean' ? options.async : true,
// ?? 空值运算符, 只有在 左侧为 undefined 或者 null 时才会执行右边的
async: options.async ?? true,
// 代码执行到这里, data 要么是 undefined 要么是 '' 或者 {}
data: options.data || "",
};
// 2.2 如果当前 data 是对象, 需要转换为 查询字符串
if (typeof _options.data !== "string") {
_options.data = objToStr(_options.data);
}
// 2.3 如果当前 data 有值, 且当前是 GET 方式, 那么可以提前 把 url 后拼接上 data
if (_options.data && /^(GET)$/i.test(_options.method)) {
_options.url += "?" + _options.data;
}
console.log("原始参数: ", options);
console.log("默认参数: ", _options);
// console.log('这里执行说明参数的 url 已经传递了')
}
ajax({
url: "qwer",
method: "POST",
// async: false,
// data: "name=QF666&age=18",
data: {
name: "QF666",
age: 18,
abc: 123,
},
});
ajax 封装发送请求
function objToStr(obj) {
let str = "";
for (let k in obj) {
str += `${k}=${obj[k]}&`;
}
return str.slice(0, str.length - 1);
}
function ajax(options = {}) {
// 1.1 验证参数---url 必传
// 1.2 验证参数---method 可以不传, 可以为 GET, 可以是 POST, 但不能是其他的
// 1.3 验证参数---async 可以不传, 可以为 true, 可以是 false, 但不能是其他的
// 1.4 验证参数---data 可以不传, 可以为一个字符串, 可以是一个对象
// 2.1 处理默认值
// 2.2 如果当前 data 是对象, 需要转换为 查询字符串
// 2.3 如果当前 data 有值, 且当前是 GET 方式, 那么可以提前 把 url 后拼接上 data
// 3. 封装 ajax 请求
const p = new Promise((resolve, reject) => {
// 3.1 创建 ajax
const xhr = new XMLHttpRequest();
// 3.2 配置 ajax 请求信息
xhr.open(_options.method, _options.url, _options.async);
// 3.3 配置接收响应的事件
xhr.onload = function () {
resolve(xhr.responseText);
};
// 3.4 发送本次请求
xhr.send();
});
return p;
}
ajax({
url: "http://localhost:8888/test/third",
// method: "POST",
async: false,
data: "name=QF666&age=18",
// data: {
// name: "QF666",
// age: 18,
// abc: 123,
// },
}).then((res) => {
console.log(res);
});
ajax 封装请求头信息
function ajax(options = {}) {
// code...
// 1.5 验证参数---headers 可以不传, 可以为一个对象
if (
!(
options.headers === undefined ||
options.headers.constructor === Object
)
) {
throw new Error("herder 目前仅支持对象(Object)");
}
// 2.1 处理默认值
const _options = {
// code...
// 代码执行到这里, headers 要么是 undefined 要么是 {}
headers: {
"content-type": "application/x-www-form-urlencoded",
...options.headers,
},
};
// 3. 封装 ajax 请求
const p = new Promise((resolve, reject) => {
// 3.1 创建 ajax
// 3.2 配置 ajax 请求信息
// 3.3 配置接收响应的事件
// 如果当前的请求方式为 POST, 那么需要配置上对应的 请求头
if (/^(POST)$/i.test(_options.method)) {
xhr.setRequestHeader(
"content-type",
_options.headers["content-type"]
);
}
// 如果 authorization 内有值, 需要带上 authorization
if (_options.headers.authorization) {
xhr.setRequestHeader(
"authorization",
_options.headers.authorization
);
}
// 3.4 发送本次请求
/^(POST)$/i.test(_options.method)
? xhr.send(_options.data)
: xhr.send();
});
return p;
}
/**
* 思考: 请求头的设置
* + 当你是 post 的时候, 需要设置 content-type 字段
* + 有的时候还需要设置 authorization 字段
* + 有的时候还需要设置 abcd 字段
*
* 例子:
* 1. 登录
* + POST 方式
* => 需要 content-type
* => 不需要 authorization
* 2. 获取商品列表
* + GET 方式
* => 不需要 content-type
* => 不需要 authorization
* 3. 获取购物车列表
* + GET 方式
* => 不需要设置 content-type
* => 需要 authorization
* 4. 修改密码
* + POST 方式
* => 需要 content-type
* => 需要 authorization
*/
ajax({
url: "http://localhost:8888/test/third",
// method: "POST",
// async: false,
// data: "name=QF666&age=18",
data: {
name: "QF666",
age: 18,
abc: 123,
},
headers: { authorization: "123" },
}).then((res) => {
console.log(res);
});
ajax 封装解析参数
function ajax(options = {}) {
// 1.6 验证参数---dataType 可以不传, 可以为 'string' 可以为 'json'
if (
!(
options.dataType === undefined ||
/^(string|json)$/i.test(options.dataType)
)
) {
throw new Error("dataType 目前仅支持 'string' 或者 'json' ");
}
// 2.1 处理默认值
const _options = {
// 代码执行到这里, dataType 要么是 undefined 要么是 string 要么是 json
dataType: options.dataType || "string",
};
// 3. 封装 ajax 请求
const p = new Promise((resolve, reject) => {
// 3.3 配置接收响应的事件
xhr.onload = function () {
if (_options.dataType === "string") {
resolve({
code: 1,
info: xhr.responseText,
msg: "成功",
});
return;
}
try {
const res = JSON.parse(xhr.responseText);
resolve({
code: 1,
info: res,
msg: "成功",
});
} catch (error) {
resolve({
code: 0,
info: error,
msg: "失败",
});
}
};
});
return p;
}
ajax 封装基准地址
function outer(url) {
let baseUrl = url;
function ajax(options = {}) {
// 2.1 处理默认值
const _options = {
url: baseUrl + options.url,
};
}
return ajax;
}
const res = outer("http://localhost:8888");
res({
url: "/test/first",
}).then((res) => {
console.log(res);
});
res({
url: "/test/second",
dataType: "json",
}).then((res) => {
console.log(res);
});
设计模式-单例模式
- 设计模式:
- 为了实现某一类功能给出的简洁而优化的解决方案
- 设计模式 - 单例模式
- 核心意义: 一个构造函数(类) 一生只有一个实例
- 思路:
- 实例化一个类的时候
- 需要先判断, 如果当前类以前被实例化过, 不需要再次实例化了
- 而是直接拿到原先的实例化对象
- 如果没有被实例化过, 那么新实例化一个实例对象
- 解决:
- 单独找一个变量, 初始化为 null
- 每一次创建实例的时候, 都判断一下该变量的值
- 如果为 null, 那么创建一个实例, 赋值给该变量
- 如果不为 null, 那么我们不再创建实例, 直接给出该变量的值
class Dialog {
constructor() {
console.log("在 页面中 创建了一个 弹出层");
}
}
let instance = null;
function newDialog() {
if (instance == null) {
instance = new Dialog();
}
return instance;
}
// 第一次调用, 创建一个 实例化对象然后给到 instance 中, 并返回 instance
let d1 = newDialog();
// 第二次调用, 直接返回 instance
let d2 = newDialog();
console.log(d1);
console.log(d2);
console.log(d1 === d2);
单例模式变形
-
单例模式问题
- 向外部写了一个全局变量 instance
- 多次调用时, 无法传递不一样的参数
- 构造函数的类名与创建实例的函数名是两个东西
-
解决:
- 利用闭包
let newDialog = (function fn() { let instance = null; return function () { if (instance == null) { instance = new Dialog(); } return instance; }; })();- 创建一个 init 方法
class Dialog { constructor() { console.log("在 页面中 创建了一个 弹出层, 文本为: "); this.title = ""; } setTitle(newTitle) { this.title = newTitle; console.log("将 title 属性 重新修改为了: ", this.title); } } let newDialog = (function outer() { let instance = null; return function inner(str) { // 没有接收到不同参数的原因在于, 这个 if 只有第一次才会执行, 后续永远不会执行 if (instance == null) { instance = new Dialog(); } // 但是, 这个位置却可以多次执行, 所以我们可以在这里, 给这个 类传递不同的参数 instance.setTitle(str); return instance; }; })();- 将类放在闭包函数内, 然后将函数名修改为类名
let Dialog = (function outer() { // 因为 Dialog 这个 类, 永远就使用一次, 所以函数局部 更合适 class Dialog { constructor() { console.log("在 页面中 创建了一个 弹出层, 文本为: "); this.title = ""; } setTitle(newTitle) { this.title = newTitle; console.log("将 title 属性 重新修改为了: ", this.title); } } let instance = null; return function inner(str) { if (instance == null) { instance = new Dialog(); } instance.setTitle(str); return instance; }; })();
设计模式-策略模式
- 设计模式 - 策略模式
- 核心: 减少 if...else...
- 作用: 把多种形式的内容罗列出来
- 案例:
- 已知一个购物总价 1376
- 需要根据折扣计算最终价格
- 折扣种类: 8 折, 7 折, 300-30, 500-50
- 当前案例核心:
- 用一个数据结构, 把各种折扣记录下来
- 值存储的就是该折扣和总价的计算方式
const calcPrice = (function () {
const calcList = {
"80%": (total) => (total * 0.8).toFixed(2),
"70%": (total) => (total * 0.7).toFixed(2),
};
// 任何引用数据类型, 都可以当作 对象去使用
function inner(type, total) {
return calcList[type](total);
}
inner.add = function (type, fn) {
calcList[type] = fn;
};
inner.sub = function (type) {
delete calcList[type];
};
inner.getList = function () {
return calcList;
};
return inner;
})();
const res = calcPrice("80%", 1000);
console.log(res);
const res1 = calcPrice("70%", 1000);
console.log(res1);
calcPrice.add("300-30", (total) => total - Math.floor(total / 300) * 30);
const res2 = calcPrice("300-30", 1000);
console.log(res2);
calcPrice.sub("70%");
console.log(calcPrice.getList());
发布订阅模式
- 设计模式-发布订阅
- 首先明确 这个模式和一个叫做观察者模式类似但不是一个东西
- 目前一批开发人员认为这两个是一个东西, 另外一批认为是两个东西
- 案例: 买一本书
- 以前
- 去到书店, 问店员有没有 JS 从入门到入土
- 没有, 回去
- 一会再回去问, 有没有
- 没有, 回去
- 一会再回去问, 有没有
- ...
- 一会再回去问, 有没有
- 有了, 购买
- 现在
- 去到书店, 问店员有没有 JS 从入门到入土
- 没有, 给店员留下一个联系方式
- 直到店员给你打电话, 你触发技能(去买回来)
- 以前
- 在 JS 内 发布订阅 使用最多的地方 (addEventListener)
- xxx.addEventListener('click', fn)
- 思考: 以什么数据格式来记录
店员 ===
{
a: [fn1, fn2],
b: [fn3, fn4],
};
class Observer {
constructor(name) {
this.name = name; // 这步模拟店员名字, 可以不要
// 店员的记录手册
this.messages = {};
}
add(type, fn) {
if (this.messages[type] === undefined) this.messages[type] = [];
this.messages[type].push(fn);
}
remove(type, fn) {
if (this.messages[type] === undefined) return;
// if (fn === undefined) return this.messages[type] = []
if (fn === undefined) return delete this.messages[type];
this.messages[type] = this.messages[type].filter((item) => item !== fn);
}
trigger(type) {
// console.log(type)
// console.log(this.messages[type])
this.messages[type].forEach((item) => item());
}
}
const ob = new Observer("小李");
const A1 = () => {
console.log("我是 张三, 我要买这本书");
};
const A2 = () => {
console.log("我是 李四, 我要买这本书");
};
const B1 = () => {
console.log("我是 王五, 我要买这本书");
};
ob.add("A1", A1);
ob.add("A1", A2);
ob.add("B1", B1);
// ob.remove('A1', A1)
// ob.remove('A1')
ob.remove("C1");
ob.trigger("A1");
console.log(ob);
认识 前后端交互
- 认识前后端交互
- 前端 和 后端的 通讯方式
- 主要使用的技术栈就是 ajax (async javascript and xml)
- 注意:
- 前后端交互只能交互 字符串
- 并且有自己的固定步骤
- 前后端交互基本步骤
- 创建 ajax 对象
- 语法: const xhr = new XMLHttpRequest()
- 配置本次请求的信息
- 语法: xhr.open(请求方式, 请求地址, 是否异步)
- 请求方式: 按照接口文档书写
- 请求地址: 按照接口文档书写
- 是否有异步: 自己决定, 默认是异步(true)
- 语法: xhr.open(请求方式, 请求地址, 是否异步)
- 把本次请求发送出去
- 语法: xhr.send()
- 配置一个请求完成事件
- 语法: xhr.onload = function () {}
- 使用 xhr.responseText 来获取服务端给出的相应信息
- 创建 ajax 对象
// 1. 创建 ajax 对象
const xhr = new XMLHttpRequest();
// 2. 配置 ajax 对象
xhr.open("GET", "http://localhost:8888/test/first", true);
// 3. 发送请求
xhr.send();
// 4. 接收响应
xhr.onload = function () {
console.log("请求回来了~~~~~~~~");
console.log(xhr.responseText);
};
ajax 的异步问题
- 创建 ajax 对象(同步代码)
const xhr = new XMLHttpRequest() - 配置请求信息(同步代码)
xhr.open(请求方式, 请求地址, 是否异步) - 发送本次请求(同步发送, 异步接收)
xhr.send() - 注册完成事件(同步注册事件)
xhr.onload = function () {}
- 异步 1 2 3 4
- 创建一个 ajax 对象
- 配置请求信息
- 发出去
- 注册一个接收事件
- 收回来
- 触发接收事件, 打印信息
- 异步 1 2 4 3
- 创建 ajax 对象
- 配置请求信息
- 注册一个接收事件
- 发送请求
- 收回来
- 触发接收事件, 打印信息
- 同步 1 2 3 4
- 创建一个 ajax 对象
- 配置请求信息
- 发送请求
- 注册一个接收事件
- 因为 注册事件之前, 本次请求已经完成
- 所以再也不会有完成行为发生了
- 同步 1 2 4 3
- 创建 ajax 对象
- 配置请求信息
- 注册一个接收事件
- 发出去, 等待收回来
- 过了一会: 收回来了
- 因为已经注册过事件, 所以可以触发事件
- 打印响应信息
ajax 的状态码
- 用一个数字表明 当前 ajax 运行到那一步了
- 语法: xhr.readyState
- 0: 创建成功
- 1: 当前 ajax 配置成功
- 2: 当前 ajax 发送成功(响应已经回到浏览器了)
- 3: 表示浏览器当前正在解析本次响应, 但可能还没完成
- 4: 表示浏览器当前已经完成解析本次响应, 可以正常使用 responseText
const xhr = new XMLHttpRequest();
xhr.open("GET", "地址", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 2) console.log(xhr.responseText);
if (xhr.readyState === 3) console.log(xhr.responseText);
if (xhr.readyState === 4) console.log(xhr.responseText);
};
xhr.send();
http 传输协议(了解)
- http(s) 协议规定了, 只能由前端主动发起, 并且, 在传输过程中, 只能传递 字符串
- 建立连接
- 浏览器和服务器建立连接
- 发送请求
- 要求前端必须以 请求报文的形式发送
- 报文由浏览器组装, 我们只需要提供对应的信息即可(请求方式, 请求地址, 传输协议)
- 接收响应
- 要求后端必须以 响应报文的形式返回
- 内部有一个东西叫做 响应状态码, 关于响应的简单描述
- 断开连接
- 浏览器和服务器断开连接
- 响应状态码
- 100~199 表明连接继续
- 200~299 表明各种成功
- 300~399 表明重定向
- 400~499 表明客户端错误
- 500~599 表明服务端错误
请求方式
不同的方式, 携带参数的位置不同
- 常见的请求方式
- GET: 偏向获取的语义
- DELETE: 偏向获取的语义(删除)
- POST: 偏向提交的语义
- PUT: 偏向提交的语义(修改账号密码)
- ...
- 最常见的两个请求方式
- GET
- PUST
- 请求方式的区别
- 携带参数的位置
- GET: 直接在地址后面写(参数与地址之间用问好间隔)
- PUST: 在请求体内书写(其实就是 xhr.send() 的小括号内)
- 携带参数的大小
- GET: 2kb 左右
- PUST: 原则上不限制大小, 但是服务器可以做出限制
- 携带参数的格式
- GET: 只能携带查询字符串格式
- PUST: 原则上不限制格式, 但是需要在请求报文的 content-type 做配置说明
- 安全性(相对)
- GET: 明文发送, 相对不安全
- PUST: 暗文发送, 相对安全
- 携带参数的位置
测试请求
// 测试请求 2
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:8888/test/second");
xhr.send();
xhr.onload = function () {
let res = JSON.parse(xhr.responseText);
console.log(res);
};
/**
* 测试请求 3
* 请求方式 GET, 需要携带参数
* GET 方式直接在 地址栏后面携带参数
* GET 方式只能携带查询字符串格式 'key=value&key2=value2'
*
* 注意: 参数与地址之间使用 问号间隔
*/
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:8888/test/third?name=QF666&age=18");
xhr.send();
xhr.onload = function () {
let res = JSON.parse(xhr.responseText);
console.log(res);
};
/**
* 测试请求 4
* 请求方式 POST, 需要携带参数
* POST 方式在请求体携带参数(其实就是 send小括号内部)
* POST 方式在携带参数的时候, 需要设置请求头内部的 content-type 通过 xhr.setRequestHeader
*
* 查询字符串: application/x-www-form-urlencoded
* JSON 字符串: application/json
*/
const xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:8888/test/fourth");
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
xhr.send("name=QF666&age=18");
xhr.onload = function () {
let res = JSON.parse(xhr.responseText);
console.log(res);
};
分页
// 更改渲染函数
function myFn(list) {
var str = "";
list.forEach(function (item) {
str += `...`;
});
oUl.innerHTML = str;
totalBox.innerHTML = currentNum + "/" + totalNum;
// ...;
}
// 在数据请求成功的时候, 调用渲染函数
function getList() {
const xhr = new XMLHttpRequest();
xhr.open(
"GET",
`http://localhost:8888/goods/list?current=${currentNum}&pagesize=${pageSize}`
);
xhr.onload = function () {
const data = JSON.parse(xhr.responseText);
totalNum = data.total;
myFn(data.list);
};
xhr.send();
}
getList();
认识 SPA
- SPA: single page application
- 所有的内容, 都在一个 HTML 中渲染
- 大部分用于: 移动端; PC 后台管理
- 切换依赖于:
- 依赖 hash 的改变(锚点)
- 依赖历史记录(history)
- 开发过程: 组件化开发(框架内基本都是组件化开发)
- 组件: 一段完整的 HTML+CSS+JS 结构
- 面向过程开发 => 所有代码一步一步写
- 面向对象开发 => 将面向过程的一个封装
- 函数式开发 => 所有东西都封装在函数中
- 模块化开发 => 所有的 JS 功能都拆分为模块
- 组件化开发 => 将一个 HTML+CSS+JS 合并在一起做为一个组件, 每一个组件就是一个独立的功能
- 前端路由:
- 在页面内同一个位置, 根据不同的 hash 值显示不同的页面结构
<div class="box">
<div class="top"> 顶部通栏 </div>
<div class="bottom">
<div class="slide">
<a href="#/pageA">pageA</a>
<a href="#/pageB">pageB</a>
<a href="#/pageC">pageC</a>
<a href="#/pageD">pageD</a>
</div>
<div class="content router-view">内容区</div>
</div>
</div>;
const templateA = `<div>templateA</div>`;
const templateB = `<div>templateB</div>`;
const templateC = `<div>templateC</div>`;
const templateD = `<div>templateD</div>`;
const rw = document.querySelector(".router-view");
window.onhashchange = function () {
const { hash } = window.location;
if (hash === "#/pageA") {
rw.innerHTML = templateA;
} else if (hash === "#/pageB") {
rw.innerHTML = templateB;
} else if (hash === "#/pageC") {
rw.innerHTML = templateC;
} else if (hash === "#/pageD") {
rw.innerHTML = templateD;
}
};
SPA 应用
-
路由重定向
// router.js { // 如果当前的 路由是 /, 那么就重定向到 /pageA name: "/", redirect: "/pageA", } // index.js if (info.redirect) return (window.location.hash = info.redirect); -
路由懒加载
- 因为上述引入的方式会导致 页面首次加载时直接全部加载
- 所以我们应该更改引入方式为 异步引入模块
- import(地址) 按照 promise 的语法封装的函数
- 返回值不是我们默认导出的内容, 而是一个 promise 对象
// router.js { name: "/pageA", compoment: () => import("./components/templateA.js"), } // index.js const res = info.compoment(); if (res === undefined) return; res.then((res) => res.default());