① 定义:是一套经过反复使用、多人知晓的、经过分类的、代码设计经验的总结
② 为什么使用设计模式?
1.为了代码的可重用性、更容易被他人理解、保证代码的可靠性
2.设计模式使代码的编写真正的工程化,是软件工程的基石脉络,如同大厦的结构
③ 有哪些?
构造器模式,模块化模式,暴露模块模式,单例模式,中介者模式,原型模式,命令模式,外观模式,工厂模式,Mixin模式,装饰模式,亨元(Flyweight)模式,MVC模式,MVP模式,MVVM模式,组合模式,适配器模式,外观模式,观察者模式,迭代器模式,惰性初始模式,代理模式,建造者模式
一、单例模式
特点:单个实例 每次new处理都是一个实例
利用new的原理,主动返回对象(不能用this和原型)
function Fn() {
if (!Fn.obj) {
Fn.obj = {};
Fn.obj.__proto__ = Fn.prototype; // 需要使用原型的时候,手动创建原型链
}
Fn.obj.abc = "ggg";
return Fn.obj; // 主动返回对象(上的obj属性 格式好点) 可以const Fn.obj为变量that
}
Fn.prototype.show = function() {
console.log(12345678);
}
var f1 = new Fn();
var f2 = new Fn();
console.log(f1 === f2); //true才是单例模式
f1.show(); // 手动创建原型链后才能获得show(new原理)
1. 创建了一个新对象
2. 修改了函数的this, 指向第一步创建的新对象
3. 修改了新对象的原型链(__proto__), 指向了函数的prototype
4. 检测函数是否主动返回对象, 如果没有, 主动返回新对象
栗子:弹出框(始终都是一个提示框 节约内存)
<!-- <dialog>提示框 默认隐藏 </dialog> -->
<input type="button" value="信息" id="btn1">
<input type="button" value="成功" id="btn2">
<input type="button" value="失败" id="btn3">
<input type="button" value="警告" id="btn4">
const obtn1 = document.getElementById("btn1");
const obtn2 = document.getElementById("btn2");
const obtn3 = document.getElementById("btn3");
const obtn4 = document.getElementById("btn4");
obtn1.onclick = function() {
new Toast({
message: "信息",
type: "info"
})
}
obtn2.onclick = function() {
new Toast({
message: "成功",
type: "success"
})
}
obtn3.onclick = function() {
new Toast({
message: "失败",
type: "error"
})
}
obtn4.onclick = function() {
new Toast({
message: "警告",
type: "warning"
})
}
function Toast(ops) {
if (!Toast.obj) {
Toast.obj = {};
Toast.obj.ele = document.createElement("dialog");
document.body.appendChild(Toast.obj.ele);
}
const that = Toast.obj;
that.ele.innerHTML = ops.message;
let color = "";
if (ops.type === "info") {
color = "#55f";
} else if (ops.type === "success") {
color = "#5f5";
} else if (ops.type === "error") {
color = "#f55";
} else if (ops.type === "warning") {
color = "yellowgreen";
}
// 统一设置css格式
that.ele.style.cssText = `border-color:${color};color:${color};display:block`;
clearTimeout(that.t);
that.t = setTimeout(() => {
that.ele.style.display = "none";
}, 2000);
return that;
}
二、观察者模式(发布订阅者模式)
1.发布:被观察者,主题对象
2.订阅:观察者
3.一个主题对象发布或更新信息,多个订阅者接收信息,并根据信息作出不同的功能处理
4.可以实现广播通信,一对多关系,耦合低
// 发布者
// 定义的时候接受参数
function stu(name) {
this.name = name;
this.send = function(type) {
this.type = type;
}
}
// 观察者
function 讲师() {
this.listen = function(t) {
switch (t) {
case "睡觉":
console.log("叫醒 揍一顿");
break;
case "学习":
console.log("好棒");
break;
case "玩游戏":
console.log("制止 揍俩顿");
break;
}
}
}
function 班主任() {
this.listen = function(t) {
switch (t) {
case "睡觉":
console.log("叫醒 去喝茶");
break;
case "学习":
console.log("好棒棒");
break;
case "玩游戏":
console.log("走 一起玩");
break;
}
}
}
function 校长() {
this.listen = function(t) {
switch (t) {
case "睡觉":
console.log("叫班主任");
break;
case "学习":
console.log("好棒!");
break;
case "玩游戏":
console.log("制止 叫班主任");
break;
}
}
}
function 行政(s) {
this.listen = function(t) {
console.log(s.name + t + "拍照");
}
}
// new的时候传入参数
const s = new stu("张三");
s.send("睡觉");
const ly = new 讲师();
ly.listen(s.type);
const clb = new 班主任();
clb.listen(s.type);
const xz = new 校长();
xz.listen(s.type);
const jz = new 行政(s);
jz.listen(s.type);
三、适配器模式
1.假如现有数据A 和功能B,功能B只能接受B类型的数据,可是现有数据类型是A
2.此时需要一个适配器,将数据A进行一层包装,让数据A看起来像 B类型的数据
// 手机:打电话 玩游戏
function Phone(n) {
this.name = n;
this.call = function() {
console.log("打电话");
}
this.game = function() {
console.log("玩游戏");
}
}
// 平板:玩游戏
function Pad(n) {
this.name = n;
this.game = function() {
console.log("玩游戏");
}
}
// 测试模块:打游戏 玩游戏
function test(o) {
o.call();
o.game();
}
const p1 = new Phone("大米");
const p2 = new Pad("橘子");
// 适配器包装
function fn(o) {
o.call = function() {
console.log("这是一个平板,不能打电话");
}
return o;
}
// phone可以直接测试
test(p1);
// pad需要经过适配器的包装之后才能测试
test(fn(p2));
四、代理模式
1.系统功能A在调用系统功能B的过程中,为了传输数据,我们需要记录数据、改写数据、拦截数据
2.先断开执行,获取系统功能B的执行权限,编写代理程序执行功能B,让功能A调用代理程序,将原有的数据,发给代理程序,代理程序将数据发给功能B(A-代理-B)
// B
function Girl(n) {
this.name = n;
}
// A
function Boy(girl) {
this.name = "张三";
this.girl = girl;
this.send = function(msg) {
console.log(`你好,${this.girl.name},这是我送你的${msg}`);
}
}
// 代理
function porxyLitterBrother(girl) {
// 小本本记录
this.messages = [];
this.girl = girl;
this.send = function(msg) {
// console.log(msg); 查看
// msg = "一束花"; 修改
const b = new Boy(this.girl);
// 拦截并记录
this.messages.push({
收件人: this.girl.name,
寄件人: b.name,
time: Date.now(),
msg: msg
})
b.send(msg);
}
}
const g = new Girl("翠花");
const p = new porxyLitterBrother(g);
p.send("一封信");
五、 策略模式
1.根据程序执行过程中产生的不同的状态或信息,决定后续的功能执行
2.利用不同的功能接口(方法)决定不同的功能(降低if-else)
const Obj = {
getBanner: function() {
return "获取到了轮播图数据";
},
getGoods: function() {
return "获取到了商品数据";
},
getMenu: function() {
return "获取到了菜单数据";
}
}
function getData(type) {
// if (type === "getBanner") {
// Obj.getBanner();
// }
// 减少使用if-else
return Obj[type]();
}
console.log((getData("getBanner")));
六、组合模式
1.按照一定的组织关系,将多个对象进行组合
- 树状关系(html结构)
2.组合模式就是为动态的html而生的
3.组合模式为了能实现批量操作,节省操作过程,使用了递归的思想,所以消耗比较大的性能
4.利用组合器将对象组合起来
- 枝对象:只要具有子对象,无论层级在哪 都是枝对象 (操作枝 里面所有都被操作)
- 叶对象:只要不具有子对象,无论层级在哪 都是叶对象
// 枝和叶 功能要一一对应
// 枝对象的类:div
class Team {
constructor(id) { //接收传入的id名 imgbox1,2
this.ele = document.createElement("div"); //ele指创建的div
this.ele.id = id;
this.children = []; //保存叶
}
add(child) { //接收叶传入 child就是Item
this.children.push(child);
// 叶从属于某个枝 让枝保存叶 叶的img插入到枝的div
this.ele.appendChild(child.ele);
}
remove(child) {
// 找到重复的叶
let i = 0;
this.children.some((val, idx) => {
i = idx;
return val === child;
})
// 数组中 从第i个删1位
this.children.splice(i, 1);
// 动态的html标签也删除
child.ele.remove();
}
border() {
this.ele.style.border = "solid 2px black";
// 拿到所有的叶 批量设置边框
this.children.forEach(val => {
val.border();
})
}
none() {
this.ele.style.border = "none";
this.children.forEach(val => {
// 拿到所有的叶 批量删除边框
val.none();
})
}
}
// 叶对象的类:img
class Item {
constructor(src) {
this.ele = document.createElement("img");
this.ele.src = src; //图片地址
}
add() {
console.log("这是一个叶对象,无法添加子对象");
}
remove() {
console.log("这是一个叶对象,无法删除子对象");
}
border() {
this.ele.style.border = "solid 2px black";
}
none() {
this.ele.style.border = "none";
}
}
// 创建叶对象img1和2和3
const img1 = new Item("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");
const img2 = new Item("https://fanyi-cdn.cdn.bcebos.com/static/translation/img/header/logo_e835568.png");
const img3 = new Item("https://inews.gtimg.com/newsapp_bt/0/0923142908664_4470/0");
// 创建枝对象imgbox1和2
const imgbox1 = new Team("imgbox1");
const imgbox2 = new Team("imgbox2");
// 将叶对象img1和2组合到枝对象imgbox1和2
imgbox1.add(img1);
imgbox2.add(img2);
// 又创建枝对象box
const box = new Team("box");
// 将枝对象imgbox1和2和叶对象img3都组合到枝对象box
box.add(imgbox1);
box.add(imgbox2);
box.add(img3);
// 将最大的枝对象box直接插入到页面 box实例上的属性ele(div元素)
document.body.appendChild(box.ele);
// 开始批量操作
imgbox1.border();
box.border();
imgbox2.none();
七、抽象工厂模式
在工厂模式的基础之上,再次对同一个实例的相同属性进行抽象,抽象成一个公共对象,再做具体的工厂模式的创建
function CreatePhone(id) {
this.id = id;
}
CreatePhone.prototype.call = function() {
console.log(this.color + this.id + "打电话");
}
// 抽象工厂1
function AbstractColorWhite(id) {
const p = new CreatePhone(id);
p.color = "白色";
return p;
}
// 抽象工厂2
function AbstractColorPink(id) {
const p = new CreatePhone(id);
p.color = "粉色";
return p;
}
const p1 = AbstractColorWhite("001");
const p2 = AbstractColorWhite("002");
const p3 = AbstractColorPink("003");
const p4 = AbstractColorPink("004");
console.log(p1);
console.log(p2);
console.log(p3);
console.log(p4);
p1.call();
p2.call();
p3.call();
p4.call();
八、MVC模式
M:model 模型层 只负责数据的管理
V:view 视图层 只负责将来数据展示的视图的管理
C:control 控制层 负责接收用户的指令,根据指令,选中不同的模型,将模型中的数据,寄给不同的视图渲染
-工作流程
- 控制器接收到指令后,根据指令读取的制定模型,拿到数据
- 控制器再根据指定获取指定的视图
- 将第一步读取到的数据,传给获取的视图
- 由视图负责将接收数据 渲染
// 模型层
class Model {
// constructor没有可以不写
m1() {
return "hello";
}
m2() {
return "world";
}
}
// 视图层
class View {
v1(d) {
console.log(d);
}
v2(d) {
document.write(d);
}
v3(d) {
alert(d);
}
}
// 控制层
class Control {
constructor() {
this.m = new Model();
this.v = new View();
}
c1() {
const d = this.m.m1();
this.v.v2(d);
}
c2() {
const d = this.m.m2();
this.v.v1(d);
}
}
const c = new Control();
c.c1();