前端常用设计模式
设计模式学习网站:https://refactoring.guru/
JavaScript 设计模式:让你的代码像个天才! - 掘金 (juejin.cn)
知识点
- 工厂模式及自定义事件
- 抽离英雄基类
- 设计原则
- 单例模式使用
- 建造者模式
- 装饰者模式使用
- 代理模式
- 观察者模式
- 适配器模式...
目标
- 学会各种设计模式的使用
- 学会面向对象中抽象使用
- 理解类中的继承
- oop 思想规划项目
设计原则
SOLID(稳定的)
单一职责原则(Single Responsibility Principle)
一个类应该只有一个发生变化的原因。简而言之就是每个类只需要负责自己的那部分,类的复杂度就会降低。
开闭原则(Open Closed Principle)
- 一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭
里氏替换原则(Liskov Substitution Principle)
所有引用基类的地方必须能透明地使用其子类的对象,也就是说子类对象可以替换其父类对象,而程序执行效果不变。
迪米特法则(Law of Demeter)
迪米特法则(Law of Demeter)又叫作最少知识原则(The Least Knowledge Principle),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。
接口隔离原则(Interface Segregation Principle) flow 、ts
- 多个特定的客户端接口要好于一个通用性的总接口
依赖倒置原则(Dependence Inversion Principle) 1、上层模块不应该依赖底层模块,它们都应该依赖于抽象。 2、抽象不应该依赖于细节,细节应该依赖于抽象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>设计原则</title>
</head>
<body></body>
<script>
// 设计模式 1.设计原则 2.设计模式
// 封装: 代码的复用。 设计模式:经验的复用
// 设计原则 :单一职责
// class Hero{
// constructor(){
// this.name = "";
// this.skillName1 = "";
// this.skillName2 = "";
// }
// }
// 开闭原则
// 里氏替换原则
// class Hero{
// constructor(name){
// this.name = name;
// }
// fly(){
// console.log("飞");
// }
// }
// class Yase extends Hero{
// constructor(){
// super("亚瑟");
// }
// // fly(){
// // console.log("不能飞");
// // }
// }
// let yase = new Yase();
// yase.fly();
// 迪米特法则
// 接口隔离原则
// 依赖倒置原则
</script>
</html>
案例内容扩展
扩展皮肤功能
抽离英雄基类
- 每个英雄的共有属性
- 共有方法及独特方法
抽离技能基类
设计模式
设计模式是软件开发人员在软件开发过程中面临的一些具有代表性问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的;
单例模式
单例模式 (Singleton Pattern)又称为单体模式,保证一个类只有一个实例,并提供一个访问它的全局访问点。也就是说,第二次使用同一个类创建新对象的时候,应该得到与第一次创建的对象完全相同的对象。
通过静态属性创建单例
jsclass Person { static instance = null; constructor(name) { if (Person.instance) { return Person.instance; } Person.instance = this; this.name = name; } }
通过函数创建单例
jslet instance; function createInstance(...arg) { if (!instance) { instance = new Game(...arg); } return instance; }
实现 game 类的单例
- 优:单例模式节约内存开支和实例化时的性能开支,节约性能;
- 缺:单例模式扩展性不强
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>单例模式</title>
</head>
<body>
<button>点击</button>
</body>
<script>
// window document store
// 单例模式
// class Person{
// static instance;
// constructor(name){
// if(!Person.instance){
// Person.instance = this;
// }else{
// return Person.instance;
// }
// this.name = name;
// }
// }
// let zhangsan = new Person("张三");
// let lisi = new Person("李四");
// console.log(zhangsan===lisi);
// let obj1 = {
// name:"张三",
// age:20
// }
// let obj2 = {
// name:"张三",
// age:20
// }
// console.log(obj1===obj2);
// class Perosn{
// constructor(name){
// this.name = name;
// }
// }
// class Animal{
// constructor(name){
// this.name = name;
// }
// }
// // ....
// // 通用单例
function createInstance(fn) {
let instance;
return function (...args) {
if (!instance) {
instance = new fn(...args);
}
return instance;
};
}
// let singlePerson = createInstance(Perosn);
// let zhansan = new singlePerson("张三");
// let lisi = new singlePerson("李四");
// console.log(zhansan===lisi);
// 单例应用
class Dialog {
constructor() {
let dialog = document.createElement("div");
this.dialog = dialog;
this.dialog.style.display = "none";
this.isShow = false;
}
showDialog() {
if (!this.isShow) {
this.dialog.innerHTML = "对话框";
document.body.appendChild(this.dialog);
this.dialog.style.display = "block";
this.isShow = true;
} else {
console.log("已经显示了一个");
}
}
}
let createInstanceFn = createInstance(Dialog);
let dialog1 = new createInstanceFn();
// let dialog2 = new createInstanceFn();
document.querySelector("button").onclick = function () {
dialog1.showDialog();
// dialog2.showDialog();
};
</script>
</html>
建造者模式
工厂模式
- 工厂模式 (Factory Pattern),封装具体实例创建逻辑和过程,外部只需要根据不同条件返回不同的实例。
- 优点:实现代码复用性,封装良好,抽象逻辑;
- 缺点:增加了代码复杂程度;
装饰者模式
- 装饰者模式 (Decorator Pattern)使用一种更为灵活的方式来动态给一个对象/函数等添加额外信息
- 扩展功能 和继承类似
- 扩展不同类的功能,和原始类并无关联;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>装饰者模式</title>
</head>
<body></body>
<script>
//装饰者模式: 功能扩展 extends
class Yase {
constructor() {
this.name = "亚瑟";
}
release() {
console.log("释放技能");
}
}
let yase = new Yase();
// yase.release();
function hurt() {
console.log("造成100点伤害");
}
function walk() {
console.log("走");
}
Function.prototype.Decorator = function (fn) {
let _this = this;
return function () {
_this();
fn();
};
};
// yase.release.Decorator(hurt)();
// 装饰者链
yase.release.Decorator(hurt).Decorator(walk)();
</script>
</html>
观察者模式(自定义事件)
- 观察者模式 (Observer Pattern) 定义一个对象与其他对象之间的一种依赖关系,当对象发生某种变化的时候,依赖它的其它对象都会得到更新
- 自定义事件绑定 addEvent
- 自定义事件触发 trigger
- 自定义事件移除 removeEvent
- 实现案例 GameEvent 类
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>观察者模式(自定义事件)</title>
</head>
<body>
<button>点击</button>
</body>
<script>
// 观察者模式 (自定义事件):定义一个对象与其他对象之间的一种依赖关系,当对象发生某种变化的时候,依赖它的其它对象都会得到更新。解耦 、延迟执行、一对多的依赖关系;
// 发布订阅 :三者关系。
// let obj ={
// fn(){
// console.log("fn");
// }
// }
// document.querySelector("button").addEventListener("click",function(){
// // console.log("fn1");
// obj.fn();
// })
// document.querySelector("button").addEventListener("click",function(){
// // console.log("fn1");
// obj.fn();
// })
class Event {
constructor() {
// 保存事件
this.handles = {};
}
// 添加事件,监听 、观察
addEvent(eventName, fn) {
// {'myevent':[fn1,fn2....],'myevent2':[fn1,fn2..]}
if (typeof this.handles[eventName] === "undefined") {
this.handles[eventName] = [];
}
this.handles[eventName].push(fn);
}
// 触发
trigger(eventName) {
this.handles[eventName].forEach((v) => {
v();
});
}
// 移除事件removeEvent (eventName,fn);
removeEvent(eventName, fn) {
if (!(eventName in this.handles)) {
return;
}
for (let i = 0; i < this.handles[eventName].length; i++) {
if (this.handles[eventName][i] === fn) {
this.handles[eventName].splice(i, 1);
break;
}
}
}
}
let obj1 = {
fn() {
console.log("fn111");
},
};
let obj2 = {
fn() {
console.log("fn222");
},
};
let myevent = new Event();
// 添加事件(没有执行)
myevent.addEvent("myevent", obj1.fn);
myevent.addEvent("myevent", obj2.fn);
// myevent.removeEvent("myevent",obj1.fn); //移除obj2.fn
setTimeout(() => {
myevent.trigger("myevent");
}, 1000);
// vue2 eventBus :总线模式。 // $bus = new Vue(); $bus.on() //添加事件 $bus.emit("eventName",fn); //触发
// 作业:1.移除事件removeEvent(eventName,fn);
</script>
</html>
自定义事件例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>自定义事件管理</title>
</head>
<body>
<div class="box">111</div>
</body>
<script>
// document.querySelector(".box").addEventListener("click",function(){
// console.log("click1");
// })
// document.querySelector(".box").addEventListener("click",function(){
// console.log("click2");
// })
let obj1 = {
fn1() {
console.log("fn1更新");
},
};
let obj2 = {
fn2() {
console.log("fn2更新");
},
};
// 管理事件类
class MyEvent {
constructor() {
this.handles = {};
}
addEvent(eventName, fn) {
// {myevent1:[fn1,fn2...],myevent2:[fn1,fn2...]}
if (typeof this.handles[eventName] === "undefined") {
this.handles[eventName] = [];
}
this.handles[eventName].push(fn);
}
trigger(eventName) {
if (!(eventName in this.handles)) {
return;
}
this.handles[eventName].forEach((fn) => {
fn();
});
}
}
let eventObj = new MyEvent();
eventObj.addEvent("myevent", obj1.fn1);
eventObj.addEvent("myevent", obj2.fn2);
// eventObj.removeEvent("myevent",obj2.fn2)
setTimeout(() => {
eventObj.trigger("myevent");
}, 1000);
// 作业:1.实现一个removeEvent 可以删除指定的指定以事件。2.通过自定义事件管理init方法,在登录时候延迟执行。
</script>
</html>
代理模式
- 代理模式 为其他对象提供一种代理以控制对这个对象的访问,类似于生活中的中介。
- 为 hero 添加代理模式 控制 伤害的输出
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>代理模式</title>
</head>
<body></body>
<script>
// 代理模式
let zhangsan = {
sellHouse(num) {
console.log("卖了" + num + "万元");
},
};
// 中介 代理模式
// zhangsan.sellHouse(100);
let proxySeller = {
sellHouse(hasSold, num) {
if (hasSold) {
zhangsan.sellHouse(num - 2);
} else {
zhangsan.sellHouse(num);
}
},
};
proxySeller.sellHouse(true, 100);
// proxy 服务器代理 转发请求
// Proxy 代理对象 (后面会讲)
// 创建图片
class CreateImage {
constructor() {
this.img = document.createElement("img");
document.body.appendChild(this.img);
}
setSrc(src) {
this.img.src = src;
}
}
// let img = new CreateImage();
let src =
"https://gimg2.baidu.com/image_search/src=http%3A//youimg1.c-ctrip.com/target/tg/035/063/726/3ea4031f045945e1843ae5156749d64c.jpg&refer=http%3A//youimg1.c-ctrip.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621318136&t=b1dc094a27bb4f111a5787a6d4ca21c5";
// img.setSrc(src);
function proxyImg(src) {
let myImg = new CreateImage();
let loadImg = new Image();
myImg.setSrc("./loading.gif");
loadImg.src = src;
loadImg.onload = function () {
myImg.setSrc(src);
};
}
proxyImg(src);
</script>
</html>
适配器模式
两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>适配器模式</title>
</head>
<body></body>
<script>
// 适配器模式
function getUsers() {
return [
{
name: "zhangsan",
age: 20,
},
{
name: "lisi",
age: 30,
},
];
}
// [{zhangsan:20},{lisi,30}]
function Adaptor(users) {
let arr = [];
for (let i = 0; i < users.length; i++) {
arr[users[i].name] = users[i].age;
}
return arr;
}
// let res = Adaptor(getUsers());
// console.log(res);
</script>
</html>
享元模式
享元模式:运用共享技术来有效地支持对象的复用,以减少创建的对象的数量。
- 通过共享对象节约内存资源,提高性能和效率
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>享元模式</title>
</head>
<body></body>
<script>
// 享元模式
// let userNumber = 10; //单数是大人 双数是小孩;
let horseNum = 0;
// class CreateHorse{
// constructor(type){
// horseNum++;
// this.horseNum = horseNum;
// this.type = type?'大马':'小马';
// }
// ride(uid){
// console.log(`人${uid}在骑${this.horseNum}号${this.type}`);
// }
// }
// 更多实例
// for(let i=1;i<=userNumber;i++){
// let horse;
// if(i%2===0){
// horse = new CreateHorse(false);
// }else{
// horse = new CreateHorse(true);
// }
// horse.ride(i);
// }
// console.log(horseNum);
// 享元模式
// let smallHorse = new CreateHorse(false);
// let bigHorse = new CreateHorse(true);
// for(let i=1;i<=userNumber;i++){
// if(i%2===0){
// smallHorse.ride(i);
// }else{
// bigHorse.ride(i);
// }
// }
// console.log(horseNum);
// 改进享元模式
class CreateHorse {
constructor(type) {
horseNum++;
this.horseNum = horseNum;
this.type = type ? "大马" : "小马";
this.finish = true;
}
ride(uid) {
return new Promise((resolve) => {
console.log(`人${uid}在骑${this.horseNum}号${this.type}`);
this.finish = false;
setTimeout(() => {
resolve(`人${uid}在骑${this.horseNum}号${this.type}骑行完毕!!`);
this.finish = true;
}, 2000);
});
}
}
class HorsePool {
constructor() {
this.horse = [
new CreateHorse(true),
new CreateHorse(true),
new CreateHorse(true),
];
this.people = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
}
rideHorse() {
this.people.forEach((uid) => {
let horse = this.getHorse();
if (horse) {
horse.ride(uid).then((res) => {
console.log(res);
this.people.shift() && this.rideHorse() && this.people.length;
});
}
});
}
getHorse() {
return this.horse.find((item) => item.finish);
}
}
let horsePool = new HorsePool();
horsePool.rideHorse();
</script>
</html>
混入模式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>混入模式</title>
</head>
<body></body>
<script>
// 混入模式
class Yase {
constructor() {
this.name = "亚瑟";
}
}
class Skills {
hurt() {
console.log("造成伤害");
}
walk() {
console.log("走路");
}
release() {
console.log("释放技能");
}
}
// let yase = new Yase();
// 混入模式
function mixin(receivingClass, givingClass) {
if (typeof arguments[2] !== "undefined") {
for (let i = 2; i < arguments.length; i++) {
receivingClass.prototype[arguments[i]] =
givingClass.prototype[arguments[i]];
}
}
}
mixin(Yase, Skills, "hurt", "walk", "release");
let yase = new Yase();
console.log(yase);
yase.release();
</script>
</html>
王者荣耀和游戏选择例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>王者荣耀登录和游戏选择例子</title>
<style>
body {
margin: 0;
padding: 0;
}
.username {
position: absolute;
left: 347px;
top: 250px;
border-radius: 5px;
height: 24px;
}
button {
background: transparent;
outline: none;
border: none;
color: white;
}
.heroShow {
width: 50px;
height: 50px;
}
.sub {
position: absolute;
left: 351px;
top: 288px;
width: 126px;
color: white;
font-weight: bold;
font-size: 18px;
}
.geme {
position: relative;
}
.heroBtn {
position: absolute;
left: 45px;
top: 8px;
font-size: 14px;
}
.skinBtn {
position: absolute;
top: 8px;
left: 105px;
font-size: 14px;
}
.userView {
position: absolute;
/* width: 80px; */
top: 34px;
left: 720px;
}
.userView img {
width: 40px;
height: 40px;
}
.chioseusername {
display: block;
color: rgb(255, 215, 0);
text-align: center;
font-size: 12px;
font-weight: bold;
}
.skillsView {
position: absolute;
display: flex;
flex-direction: column;
left: 660px;
top: 200px;
}
.skillsView img {
width: 40px;
height: 40px;
margin-bottom: 10px;
}
.heroView {
width: 120px;
position: absolute;
margin-top: 40px;
margin-left: 20px;
display: flex;
flex-wrap: wrap;
}
.skinView {
width: 130px;
position: absolute;
margin-top: 40px;
margin-left: 20px;
display: flex;
flex-wrap: wrap;
}
.heroView img {
width: 50px;
height: 50px;
}
.skinView img {
width: 50px;
height: 50px;
}
.heroItem {
margin-left: 10px;
margin-top: 10px;
display: flex;
flex-direction: column;
color: gray;
align-items: center;
font-size: 12px;
}
.skinItem {
margin-left: 10px;
margin-top: 10px;
display: flex;
flex-direction: column;
color: gray;
align-items: center;
font-size: 12px;
}
.skinShow {
position: absolute;
left: 320px;
top: 40px;
}
.login {
display: block;
}
.game {
display: none;
}
.heroContainer {
display: block;
}
.skinContainer {
display: none;
}
</style>
</head>
<body>
<!-- 登录页面 -->
<div class="login">
<input class="username" /> <button class="sub">登录</button>
<img src="./sources/login.JPG" alt="" />
</div>
<!-- 游戏选择页面 -->
<div class="game">
<button class="heroBtn">英雄</button>
<div class="heroContainer">
<div class="heroView">
<div class="heroItem">
<img src="./sources/heroes/yase1.png" />
<span>亚瑟</span>
</div>
<div class="heroItem">
<img src="./sources/heroes/yase1.png" />
<span>亚瑟</span>
</div>
</div>
</div>
<button class="skinBtn">皮肤</button>
<div class="skinContainer">
<div class="skinView">
<div class="skinItem">
<img src="./sources/heroes/yase1.png" />
<span>经典</span>
</div>
<div class="skinItem">
<img src="./sources/heroes/yase2.png" />
<span>死亡骑士</span>
</div>
</div>
</div>
<div class="skinShow">
<img src="./sources/skins/301660.png" alt="" />
</div>
<div class="userView">
<div class="heroShow">
<!-- <img src="./sources/heroes/yase1.png" /> -->
</div>
<span class="chioseusername">张三</span>
</div>
<div class="skillsView">
<img src="./sources/skills/11210.png" />
<img src="./sources/skills/11220.png" />
<img src="./sources/skills/11230.png" />
</div>
<div></div>
<img src="./sources/chiose.JPG" />
<script type="module" src="./index.js"></script>
</div>
</body>
</html>
index.js
// 和视图有关系的
/*
// 单一原则
鲁班 亚瑟 技能 皮肤 玩家 游戏管理 根据需求分析对象
对象--->抽象--》类 ---》抽象基类--》组织逻辑关系。。。(模块化)
game{
login(username){
player
}
}
player {
name:"张三",
heroes:[
luban:{
name:"鲁班",
skills:[],
skins:[],
ico:''
}
yase:{
name:"yase",
skills:[],
skins:[],
ico:''
}
]
}
*/
import Game from "./game/game.js";
let game = new Game();
// console.log(game);
document.querySelector(".sub").onclick = function () {
let username = document.querySelector(".username").value;
game.login(username);
console.log(game);
renderHeroes(game.player.heroes);
document.querySelector(".login").style.display = "none";
document.querySelector(".game").style.display = "block";
document.querySelector(".chioseusername").innerHTML = username;
};
// 渲染英雄
function renderHeroes(heroes) {
document.querySelector(".heroView").innerHTML = "";
heroes.forEach((hero) => {
let heroDiv = document.createElement("div");
heroDiv.classList.add("heroItem");
heroDiv.innerHTML = `<img src="${hero.ico}" />
<span>${hero.name}</span>`;
document.querySelector(".skillsView").innerHTML = "";
document.querySelector(".skinView").innerHTML = "";
heroDiv.onclick = function () {
renderSkills(hero.skills);
renderSkins(hero.skins);
document.querySelector(
".heroShow"
).innerHTML = `<img src="${hero.ico}" />`;
};
document.querySelector(".heroView").appendChild(heroDiv);
});
}
// 渲染技能
function renderSkills(skills) {
// console.log(skills)
document.querySelector(".skillsView").innerHTML = "";
// 渲染技能
skills.forEach((skill) => {
let img = document.createElement("img");
img.src = skill.ico;
document.querySelector(".skillsView").appendChild(img);
});
}
// 渲染皮肤
function renderSkins(skins) {
document.querySelector(".skinView").innerHTML = "";
skins.forEach((skin) => {
let skinItem = document.createElement("div");
skinItem.classList.add("skinItem");
skinItem.innerHTML = `<img src="${skin.ico}" />
<span>${skin.name}</span>`;
skinItem.onclick = function () {
document.querySelector(
".skinShow"
).innerHTML = `<img src='${skin.img}' />`;
};
document.querySelector(".skinView").appendChild(skinItem);
});
}
// 切换
document.querySelector(".heroBtn").onclick = function () {
document.querySelector(".heroContainer").style.display = "block";
document.querySelector(".skinContainer").style.display = "none";
};
document.querySelector(".skinBtn").onclick = function () {
document.querySelector(".heroContainer").style.display = "none";
document.querySelector(".skinContainer").style.display = "block";
};
// 作业: 实现一个鲁班英雄 实现鲁班渲染和 技能渲染。
....具体看例子项目代码
其他
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body></body>
<script>
class Luban {
constructor() {
this.name = "鲁班";
}
}
class Yase {
constructor() {
this.name = "亚瑟";
}
}
function Factory(heroName) {
switch (heroName) {
case "luban":
return new Luban();
break;
case "yase":
return new Yase();
break;
default:
console.log("无英雄");
break;
}
}
let yase = Factory("yase");
let luban = Factory("luban");
console.log(yase, luban);
</script>
</html>
总结
- 工厂模式及观察者模式
- 抽离英雄基类
- 抽离技能基类
- 扩展皮肤类
- 单例模式使用
- 装饰者模式使用
- 代理模式