设计模式
- 定义:为了实现某一类功能给出的简洁而优化的解决方案
沙箱模式
- 利用了函数内间接返回了一个函数
- 外部函数返回一个对象,对象内书写多个函数
function outer () {
let a = 100
let b = 200
// 创建一个 沙箱, "间接的返回一个函数"
const obj = {
getA: function () {
return a
},
getB: function () {
return b
},
setA: function (val) {
a = val
}
}
return obj
}
// 得到一个沙箱
const res1 = outer()
console.log(res1.getA()) // 100
console.log(res1.getB()) // 200
res1.setA(999)
console.log(res1.getA()) // 999
// 重新得到一个沙箱
const res2 = outer()
console.log(res2.getA()) // 100
沙箱小案例
<button class="sub">-</button>
<input class="inp" type="text" value="1">
<button class="add">+</button>
<br>
<button class="sub1">-</button>
<input class="inp1" type="text" value="1">
<button class="add1">+</button>
// 准备一个沙箱
function outer() {
let a = 1
return {
getA() {
return a
},
setA(val) {
a = val
}
}
}
// 0. 获取元素
const subBtn = document.querySelector('.sub')
const addBtn = document.querySelector('.add')
const inp = document.querySelector('.inp')
// 0. 准备变量
// let count = 1
let res = outer()
subBtn.onclick = function () {
let count = res.getA()
res.setA(count - 1)
inp.value = res.getA()
}
addBtn.onclick = function () {
// count++
let count = res.getA()
res.setA(count + 1)
inp.value = res.getA()
}
// 0. 获取元素
const subBtn1 = document.querySelector('.sub1')
const addBtn1 = document.querySelector('.add1')
const inp1 = document.querySelector('.inp1')
// 0. 准备变量
let res1 = outer()
subBtn1.onclick = function () {
let count = res1.getA()
res1.setA(count - 1)
inp1.value = res1.getA()
}
addBtn1.onclick = function () {
let count = res1.getA()
res1.setA(count + 1)
inp1.value = res1.getA()
}
语法糖
- 尽可能的简化沙箱模式的语法
- 利用 get 和 set 进行操作数据
- 语法糖:
- 在不影响功能的情况下提供一点更适合操作的语法
function outer() {
let a = 100
let b = 200
return {
get a() { return a },
get b() { return b },
set a(val) { a = val }
}
}
let res = outer()
console.log(res.a)
console.log(res.b)
res.a = 999
console.log(res.a) // 999
单例模式
- 核心:一个构造函数一生只有一个实例化对象
- 思路:
- 实例化一个类的时候,先判断这个类是否被实例化过了
- 如果被实例化过,直接拿到实例化对象;如果没有,则正常进行实例化
- 实现:单独找一个变量, 初始化为 null
- 每一次创建实例化的时候, 都判断一下该变量的值;如果为 null, 那么创建一个实例, 赋值给该变量;如果不为 null, 那么我们不再创建, 直接给出该变量的值
const Fun = (() => {
let obj = null
class Fun {
constructor() {
this.text = '默认文本'
console.log(123);
}
setText(text) {
this.text = text
}
}
return function (text) {
if (obj === null) {
obj = new Fun()
}
obj.setText(text)
return obj
}
})()
const f1 = new Fun('普通文本')
console.log(f1);
const f2 = new Fun('警告文本')
console.log(f2);
const f3 = new Fun('成功文本')
console.log(f3);
策略模式
- 核心:减少
if...else
的应用,把多种形式的内容罗列出来 - 案例:
- 已知一个购物总价 1376,需要根据折扣计算最终价格
- 折扣种类: 8 折, 7 折, 300-30, 500-50
- 思路:
- 用一个数据结构, 把各种折扣记录下来
- 值存储的就是该折扣和总价的计算方式
const price = (() => {
const list = {
'80%': total => total * 0.8,
'70%': total => total * 0.7,
}
// 将 inner 函数当作对象使用
function inner (type,total) {
return list[type](total)
}
inner.add = (key, value) => {
list[key] = value
}
inner.del = key => {
delete list[key]
}
// 返回inner函数,price的值就是inner函数
return inner
})()
const res1 = price('80%', 1000)
console.log(res1);
price.add('300-30', (total) => total - Math.floor(total/300) * 30)
const res2 = price('300-30', 1000)
console.log(res2);
发布订阅模式
- 这个模式和一个叫做观察者模式类似但不是一个东西
/**
* 案例: 买一本书
* 1. 去到书店, 问店员有没有 JS 从入门到入土
* 2. 没有, 给店员留下一个联系方式
* 3. 直到店员给你打电话, 你触发技能(去买回来)
*/
class Supermarket {
constructor() {
this.waiter = '张三'
this.msg = {}
}
add(book, user) {
if (this.msg[book] === undefined) {
this.msg[book] = []
}
this.msg[book].push(user)
}
del(book, user) {
this.msg[book].forEach((item, index) => {
if (item === user) {
delete this.msg[book][index]
}
});
}
call(book) {
this.msg[book].forEach(item => {item()});
}
}
const res = new Supermarket()
const user1 = () => {console.log('快来买全球富婆联系方式');}
const user2 = () => {console.log('快来买全球富婆联系方式');}
const user3 = () => {console.log('快来买全球富婆联系方式');}
const user4 = () => {console.log('快来买葵花宝典');}
res.add('全球富婆联系方式', user1)
res.add('全球富婆联系方式', user2)
res.add('全球富婆联系方式', user3)
res.add('葵花宝典', user4)
res.del('全球富婆联系方式', user3)
res.call('全球富婆联系方式')
console.log(res);
懒加载(瀑布流类似)
- 作用:将一些数据延缓加载,加快页面初始请求速度
<div class="box" style="height: 2000px;"></div>
<img data-src="./樱花.jpeg" alt="" width="500px">
const imgEl = document.querySelector('img')
window.onscroll = function () {
// 加载成功后不在执行
if (imgEl.src !== '') return
// 浏览器可视窗口高度
const wH = innerHeight
// 页面卷曲高度
const scrollH = document.documentElement.scrollTop
// 图片顶部偏移量
const imgT = imgEl.offsetTop
if (wH + scrollH > imgT) {
imgEl.src = imgEl.dataset.src
}
}
数组扁平化
- 方法1:
const data = [1, 2, { child: [1, 2, 3] }, [4, 5, [undefined, true, 8, 9]]]
function setArr(arr) {
const target = []
function bian(origin) {
origin.forEach(item => {
if (Object.prototype.toString.call(item) !== '[object Array]') {
// 不是数组直接添加到target中
target.push(item)
} else {
// 是数组则进行递归重新调用扁平化函数
bian(item)
}
});
}
bian(arr)
return target
}
const newArr = setArr(data)
console.log(newArr);
- 方法2:数组的flat方法
- 当前方法如果不传参, 那么会帮我们执行 一层 扁平化
- Infinity 在 JS 中表示一个 无穷大的 数字
const data = [undefined,{ name: 'QF001' },function () { console.log(123) },null,true,false,1,2,3,[4,5,6,[7,8,9,[10,11,12]]]]
const newArr = data.flat(Infinity)
console.log(newArr);
- 方法3:
- 将数组中 所有的元素转成字符串
- 只适用于纯字符串,undefined 和 null 不能出现在数组中
const data = [undefined,{ name: 'QF001' },function () { console.log(123) },null,true,false,1,2,3,[4,5,6,[7,8,9,[10,11,12]]]]
const newData = data.toString().split(',')
console.log(newData)
回调函数
- 解释:
- 把 函数 A 当作参数传递到 函数 B 内
- 在 函数 B 内 以形参的方式调用函数 A
- 函数 A 是 函数 B 的回调函数
function fn (res,rej) {
let time = Math.floor(Math.random() * 3000)
if (time > 2000) {
console.log('defeat',time);
rej()
} else {
console.log('success',time);
res()
}
}
fn (
() => {console.log('请求成功');},
() => {console.log('请求失败');}
)
回调地狱
按照回调函数的语法进行封装, 只能通过 传递一个函数作为参数来调用;当你使用回调函数过多的时候, 会出现回调地狱的代码结构(太过可怕不做演示)
- 解决:
- ES6推出了一种新的封装异步代码的方式, 叫做 Promise (承诺期约)
Promise
- 定义:一种异步代码的封装方案;因为换了一种封装方案, 不需要按照回调函数的方式去调用, 需要按照 Peomise 的形式去调用
- Promise三种状态:
- 持续:pending
- 成功:fulfilled
- 失败:rejected
- promise 的两种转换
- 从持续转换为失败
- 从持续转换为成功
- 基本语法:
- ·const p = new Promise(function () {书写异步代码})·
- p是一个实例化对象
- promise 对象可以触发两个方法
- p.then(函数)
- p.catch(函数)
- 这两个方法只有Promise的状态为成功或者失败的时候执行
function fn() {
const p = new Promise(function (resolve, reject) {
const timer = Math.ceil(Math.random() * 3000) + 2000;
setTimeout(() => {
if (timer > 3500) {
reject("班长买水失败");
} else {
resolve("班长买水成功");
}
}, timer);
});
return p;
}
// 将来在使用的时候 res 得到的是 promise 的实例对象 p
const res = fn();
res.then(function (type) {
// 这个函数执行代码 promise 状态为成功状态!!!
console.log("因为", type, "谢谢班长, 我准备了20个bug, 回馈给你");
});
res.catch(function (type) {
// 这个函数执行代码
console.log("因为", type, "谢谢班长, 我准备了800个bug, 开心死你");
});
上述代码Promise中的函数的两个参数都是函数,调用第一个改变Promise的状态为成功,调用第二个改变状态为失败;resolve 和 reject 调用时可以传递一个参数, 这个参数会被传递给对应的 then catch
链式调用
- Promise的调用方式:
- 当在第一个then里面返回一个封装的Promise函数调用结果时,可以在第一个then后面继续写then
fn()
.then(function (type) {
console.log(
"第一次: 因为",
type,
"谢谢班长, 我准备了20个bug, 回馈给你"
);
return fn();
})
.then(function (type) {
console.log(
"第二次: 因为",
type,
"谢谢班长, 我准备了20个bug, 回馈给你"
);
return fn();
})
.then(function (type) {
console.log(
"第三次: 因为",
type,
"谢谢班长, 我准备了20个bug, 回馈给你"
);
return fn();
})
.catch(function (type) {
console.log("因为", type, "谢谢班长, 我准备了800个bug, 开心死你");
});
- Promise的其他方法(实例化对象上)
- then和catch都是
- 语法:
p.finally(函数)
- 不管promise是成功还是失败, 只要 promise 执行结束, 我都会执行
- promise构造函数上的方法
- 语法:
Promise.all(多个Promise)
- 作用: 可以同时触发多个 Promise 行为
- 只有所有的Promise都成功了才算成功
Promise.all([fn(), fn(), fn()]) .then(function () { console.log("所有的 参数 都返回 成功状态"); }) .catch(function () { console.log("这些参数中, 有一个 为 失败状态"); });
- 语法:
Promise.race(多个Promise)
- 作用: 可以同时触发多个 Promise 行为
- 按照速度计算, 当第一个结束的时候就结束了, 成功或失败取决于第一个执行结束的 promise
Promise.race([fn(), fn(), fn()]) .then(function () { console.log("速度最快的那个执行完毕, 并且是成功状态时 执行"); }) .catch(function () { console.log("速度最快的那个执行完毕, 并且是失败状态时 执行"); });
- 语法:
Promise.allSettled([多个Promise])
- 作用: 可以同时触发多个 Promise 行为
- 在结果内以数组的形式给你返回 每一个 Promise 行为的成功还是失败
Promise.allSettled([fn(), fn(), fn()]).then((res) => { console.log('执行结束', res) }).catch(() => { console.log('你看我会不会执行') })
- 语法:
Promise.resolve()
- 作用:强行返回一个成功状态的Promise对象
- 语法:
Promise.reject()
- 作用:强行返回一个失败状态的Promise对象
- 语法:
async 和 await
- 注意: 需要配合的必须是 Promise 对象
- 注意: Promise 的调用方案
- 意义: 把 异步代码 写的看起来 "像" 同步代码
- async 关键字的用法:
- 直接写在函数前面,表明是异步函数
- 意义: 表示在该函数内部可以使用 await 关键字
- await关键字的用法:
- 必须书写在一个有 async 关键字的函数内
- await的后面必须是一个Promise的实例化对象
- 原本用then接收的结果,可以使用变量接收
async function newFn() {
/**
* await 是等待的意思
* 在当前 fn 函数内, await 必须要等到后面的 Promise 结束以后, 才会继续执行后续代码
*/
const r1 = await fn();
console.log("第一次: ", r1);
const r2 = await fn();
console.log("第二次: ", r1);
const r3 = await fn();
console.log("第三次: ", r1);
}
newFn();
async 和 await 语法的缺点
await只能捕获到Promise成功的状态,否则会报错
- 解决方法1 :
- 使用 try...catch...
- 语法:try{ 执行代码 } catch(err) { 执行代码 }
- 首先执行 try 里面的代码, 如果不报错, catch 的代码不执行了
- 如果报错, 不会爆出错误, 不会终止程序, 而是执行 catch 的代码
async function newFu() {
try {
const r1 = await fn();
console.log(r1);
} catch (error) {
console.log("网络错误, 请检查网络并重新请求");
}
}
newFu();
- 解决方法2:
- 无论成功失败都只调用成功的函数resolve()
- 传参的时候通过传入的数据辨认成功或者失败
function fn() {
const p = new Promise(function (resolve, reject) {
const timer = Math.ceil(Math.random() * 3000) + 2000;
setTimeout(() => {
if (timer > 3500) {
resolve({ code: 0, msg: "班长买水失败" });
} else {
resolve({ code: 1, msg: "班长买水成功" });
}
}, timer);
});
return p;
}
async function newFn() {
const r1 = await fn();
if (r1.code === 0) {
console.log("第一次请求失败, 请检查您的网络信息");
} else {
console.log("第一次请求成功", r1.msg);
}
const r2 = await fn();
if (r2.code == 0) {
console.log("第二次请求失败, 请检查您的网络信息");
} else {
console.log("第二次请求成功", r2.msg);
}
}
newFn();