策略模式(Strategy 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>Document</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
section {
display: flex;
padding: 10px;
}
button {
margin: 0 10px;
background-color: slateblue;
outline: none;
color: #fff;
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
}
div {
width: 100px;
height: 100px;
margin: 50px auto;
background-color: gray;
}
</style>
</head>
<body>
<section>
<button id="blue">蓝色 高度30</button>
<button id="red">红色 高度40</button>
<button id="green">绿色 高度50</button>
<button id="purple">紫色 高度60</button>
<button id="yellow">黄色 高度70</button>
</section>
<div>div</div>
<script>
const buttons = document.querySelectorAll("button");
const div = document.querySelector("div");
buttons.forEach(button => button.addEventListener('click', function (e) {
const idType = button.id;
// 重点代码=======================
if (idType === "blue") {
div.style.backgroundColor = "blue";
div.style.height = "30px";
}
if (idType === "red") {
div.style.backgroundColor = "red";
div.style.height = "40px";
}
if (idType === "green") {
div.style.backgroundColor = "green";
div.style.height = "50px";
}
if (idType === "purple") {
div.style.backgroundColor = "purple";
div.style.height = "60px";
}
if (idType === "yellow") {
div.style.backgroundColor = "yellow";
div.style.height = "70px";
}
// 重点代码=======================
}))
</script>
</body>
</html>
以上代码,明显存在冗余、不方便维护的问题。也就是违背了 开放-封闭原则 (Open-Close Principle,OCP)
以上问题就很适合使用 策略模式
在JavaScript中,策略模式可以通过以下方式理解:
根据以上的分析,其实我们只需要换一个优雅的方式来替代高频率的 if-else即可。因为以上过程只需要表示为
在JavaScript中,对象 object 天然具备 判断哪种策略 - 使用策略能力
对象[策略]();
obj[key]();
// 定义策略对象
const strategy = {
blue(dom) {
dom.style.backgroundColor = "blue";
dom.style.height = "30px";
},
red(dom) {
dom.style.backgroundColor = "red";
dom.style.height = "40px";
},
green(dom) {
dom.style.backgroundColor = "green";
dom.style.height = "50px";
},
purple(dom) {
dom.style.backgroundColor = "purple";
dom.style.height = "60px";
},
yellow(dom) {
dom.style.backgroundColor = "yellow";
dom.style.height = "70px";
},
}
buttons.forEach(button => button.addEventListener('click', function (e) {
const idType = button.id;
// 重点代码=======================
// 判断和使用策略
strategy[idType](div);
// 重点代码=======================
}))
以上代码,可以实现 es5基于构造函数的面向对象的思想来实现
// 定义策略对象
const StrategyBlue = function () { }
const StrategyRed = function () { }
const StrategyGreen = function () { }
const StrategyPurple = function () { }
const StrategyYellow = function () { }
StrategyBlue.prototype.setStyle = function (dom) {
dom.style.backgroundColor = "blue";
dom.style.height = "30px";
}
StrategyRed.prototype.setStyle = function (dom) {
dom.style.backgroundColor = "red";
dom.style.height = "40px";
}
StrategyGreen.prototype.setStyle = function (dom) {
dom.style.backgroundColor = "green";
dom.style.height = "50px";
}
StrategyPurple.prototype.setStyle = function (dom) {
dom.style.backgroundColor = "purple";
dom.style.height = "60px";
}
StrategyYellow.prototype.setStyle = function (dom) {
dom.style.backgroundColor = "yellow";
dom.style.height = "70px";
}
const mapStrategyType = {
blue() {
return new StrategyBlue()
},
red() {
return new StrategyRed()
},
green() {
return new StrategyGreen()
},
purple() {
return new StrategyPurple()
},
yellow() {
return new StrategyYellow()
},
}
// 负责使用策略的对象
function DomElement() {
this.dom = "";
this.strategy = "";
}
DomElement.prototype.setDom = function (dom) {
this.dom = dom;
}
DomElement.prototype.setStrategy = function (strategy) {
this.strategy = strategy;
}
DomElement.prototype.executeStrategy = function (strategy) {
this.strategy.setStyle(this.dom);
}
<!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>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
section {
display: flex;
padding: 10px;
}
button {
margin: 0 10px;
background-color: slateblue;
outline: none;
color: #fff;
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
}
div {
width: 100px;
height: 100px;
margin: 50px auto;
background-color: gray;
}
</style>
</head>
<body>
<section>
<button id="blue">蓝色 高度30</button>
<button id="red">红色 高度40</button>
<button id="green">绿色 高度50</button>
<button id="purple">紫色 高度60</button>
<button id="yellow">黄色 高度70</button>
</section>
<div>div</div>
<script>
const buttons = document.querySelectorAll("button");
const div = document.querySelector("div");
// 定义策略对象
const StrategyBlue = function () { }
const StrategyRed = function () { }
const StrategyGreen = function () { }
const StrategyPurple = function () { }
const StrategyYellow = function () { }
// 定义策略映射关系
const mapStrategyType = {
blue() {
return new StrategyBlue()
},
red() {
return new StrategyRed()
},
green() {
return new StrategyGreen()
},
purple() {
return new StrategyPurple()
},
yellow() {
return new StrategyYellow()
},
}
StrategyBlue.prototype.setStyle = function (dom) {
dom.style.backgroundColor = "blue";
dom.style.height = "30px";
}
StrategyRed.prototype.setStyle = function (dom) {
dom.style.backgroundColor = "red";
dom.style.height = "40px";
}
StrategyGreen.prototype.setStyle = function (dom) {
dom.style.backgroundColor = "green";
dom.style.height = "50px";
}
StrategyPurple.prototype.setStyle = function (dom) {
dom.style.backgroundColor = "purple";
dom.style.height = "60px";
}
StrategyYellow.prototype.setStyle = function (dom) {
dom.style.backgroundColor = "yellow";
dom.style.height = "70px";
}
// 负责使用策略的对象
function DomElement() {
this.dom = "";
this.strategy = "";
}
DomElement.prototype.setDom = function (dom) {
this.dom = dom;
}
DomElement.prototype.setStrategy = function (strategy) {
this.strategy = strategy;
}
DomElement.prototype.executeStrategy = function (strategy) {
this.strategy.setStyle(this.dom);
}
// 负责消费策略的实例
const domelement = new DomElement();
buttons.forEach(button => button.addEventListener('click', function (e) {
const idType = button.id;
const strategy = mapStrategyType[idType]();// 根据type返回对应策略实例
// 重点代码=======================
domelement.setDom(div);// 设置要操作的dom
domelement.setStrategy(strategy);// 设置策略
domelement.executeStrategy();// 调用策略
// 重点代码=======================
}))
</script>
</body>
</html>
该版本使用 es6的class来替换面向对象的语法
<!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>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
section {
display: flex;
padding: 10px;
}
button {
margin: 0 10px;
background-color: slateblue;
outline: none;
color: #fff;
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
}
div {
width: 100px;
height: 100px;
margin: 50px auto;
background-color: gray;
}
</style>
</head>
<body>
<section>
<button id="blue">蓝色 高度30</button>
<button id="red">红色 高度40</button>
<button id="green">绿色 高度50</button>
<button id="purple">紫色 高度60</button>
<button id="yellow">黄色 高度70</button>
</section>
<div>div</div>
<script>
const buttons = document.querySelectorAll("button");
const div = document.querySelector("div");
// 定义策略对象
class StrategyBlue {
setStyle(dom) {
dom.style.backgroundColor = "blue";
dom.style.height = "30px";
}
}
class StrategyRed {
setStyle(dom) {
dom.style.backgroundColor = "red";
dom.style.height = "40px";
}
}
class StrategyGreen {
setStyle(dom) {
dom.style.backgroundColor = "green";
dom.style.height = "50px";
}
}
class StrategyPurple {
setStyle(dom) {
dom.style.backgroundColor = "purple";
dom.style.height = "60px";
}
}
class StrategyYellow {
setStyle(dom) {
dom.style.backgroundColor = "yellow";
dom.style.height = "70px";
}
}
// 定义策略映射关系
const mapStrategyType = {
blue() {
return new StrategyBlue()
},
red() {
return new StrategyRed()
},
green() {
return new StrategyGreen()
},
purple() {
return new StrategyPurple()
},
yellow() {
return new StrategyYellow()
},
}
// 负责使用策略的对象
class DomElement {
constructor() {
this.dom = "";
this.strategy = "";
}
setDom(dom) {
this.dom = dom;
}
setStrategy(strategy) {
this.strategy = strategy;
}
executeStrategy = function (strategy) {
this.strategy.setStyle(this.dom);
}
}
// 负责消费策略的实例
const domelement = new DomElement();
buttons.forEach(button => button.addEventListener('click', function (e) {
const idType = button.id;
const strategy = mapStrategyType[idType]();// 根据type返回对应策略实例
// 重点代码=======================
domelement.setDom(div);// 设置要操作的dom
domelement.setStrategy(strategy);// 设置策略
domelement.executeStrategy();// 调用策略
// 重点代码=======================
}))
</script>
</body>
</html>
可以看到,而已根据自身项目情况来考虑使用哪个版本的策略模式 以下提供优化后的代码
<!DOCTYPE html>
<html>
<head>
<title>Canvas Demo</title>
<style>
button {
border-radius: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
overflow: hidden;
user-select: none;
outline: none;
border: none;
padding: 16px;
background-color: #1d93ab;
color: #fff;
}
button:focus {
background-color: #e88f21
}
</style>
</head>
<body>
<div>
<button data-type="gray">反转</button>
<button data-type="blackwhite">黑白</button>
<button data-type="brightness">亮度</button>
<button data-type="sepia">复古</button>
<button data-type="redMask">红色</button>
<button data-type="greenMask">绿色</button>
<button data-type="blueMask">蓝色</button>
<button data-type="opacity">透明</button>
<button data-type="mosaic">马赛克</button>
<button data-type="linearGradient">渐变</button>
<button id="takePhoto">拍摄</button>
</div>
<video id="videoElement" autoplay></video>
<canvas id="canvasElement"></canvas>
<script>
// 获取视频元素和画布元素
const video = document.getElementById('videoElement');
const canvas = document.getElementById('canvasElement');
const ctx = canvas.getContext('2d');
const buttons = document.querySelectorAll("button[data-type]");
const takePhoto = document.querySelector("#takePhoto")// 截图 按钮
let drawType = ""
// 当视频元素加载完成后执行
video.addEventListener('loadedmetadata', function () {
// 设置画布大小与视频尺寸相同
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
});
// 操作类型
const editType = {
dataTypeList: ["gray", "blackwhite", "brightness", "sepia", "redMask", "greenMask", "blueMask", "opacity", "linearGradient"],
// 后续继续补充
}
const handleData = {
gray(data) { // 反转
for (let i = 0; i < data.length; i += 4) {
data[i + 0] = 255 - data[i + 0];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
return data
},
blackwhite(data) {
for (let i = 0; i < data.length; i += 4) {
const average = (data[i + 0] + data[i + 1] + data[i + 2] + data[i + 3]) / 3;
data[i + 0] = average;//红
data[i + 1] = average; //绿
data[i + 2] = average; //蓝
}
return data
},
brightness(data) {
for (let i = 0; i < data.length; i += 4) {
const a = 50;
data[i + 0] += a;
data[i + 1] += a;
data[i + 2] += a;
}
return data
},
sepia(data) {
for (let i = 0; i < data.length; i += 4) {
const r = data[i + 0];
const g = data[i + 1];
const b = data[i + 2];
data[i + 0] = r * 0.39 + g * 0.76 + b * 0.18;
data[i + 1] = r * 0.35 + g * 0.68 + b * 0.16;
data[i + 2] = r * 0.27 + g * 0.53 + b * 0.13;
}
return data
},
redMask(data) {
for (let i = 0; i < data.length; i += 4) {
const r = data[i + 0]
const g = data[i + 1]
const b = data[i + 2]
const average = (r + g + b) / 3
data[i + 0] = average
data[i + 1] = 0
data[i + 2] = 0
}
return data
},
greenMask(data) {
for (let i = 0; i < data.length; i += 4) {
const r = data[i + 0]
const g = data[i + 1]
const b = data[i + 2]
const average = (r + g + b) / 3
data[i + 0] = 0
data[i + 1] = average
data[i + 2] = 0
}
return data
},
blueMask(data) {
for (let i = 0; i < data.length; i += 4) {
const r = data[i + 0]
const g = data[i + 1]
const b = data[i + 2]
const average = (r + g + b) / 3
data[i + 0] = 0
data[i + 1] = 0
data[i + 2] = average
}
return data
},
opacity(data) {
for (let i = 0; i < data.length; i += 4) {
data[i + 3] = data[i + 3] * 0.3;
}
return data
},
linearGradient(data) {
for (let i = 0; i < data.length; i += 4) {
const x = (i / 4) % canvas.width; // 当前像素的 x 坐标
const y = Math.floor(i / (4 * canvas.width)); // 当前像素的 y 坐标
// 计算当前像素的颜色值
const r = x / canvas.width * 255; // 红色分量
const g = y / canvas.height * 255; // 绿色分量
const b = 128; // 蓝色分量
const a = 100; // 不透明度
// 设置当前像素的颜色值
data[i] = r; // 红色分量
data[i + 1] = g; // 绿色分量
data[i + 2] = b; // 蓝色分量
data[i + 3] = a; // 不透明度
}
return data
},
mosaic(ctx, canvas) {
ctx.imageSmoothingEnabled = false; // 禁用图像平滑处理
const tileSize = 10; // 马赛克块的大小
// 缩小马赛克块
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, canvas.width / tileSize, canvas.height / tileSize);
// 放大回原来的大小
ctx.drawImage(canvas, 0, 0, canvas.width / tileSize, canvas.height / tileSize, 0, 0, canvas.width, canvas.height);
},
}
// 在每一帧绘制视频画面到画布上
function drawFrame() {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const imageObj = ctx.getImageData(0, 0, canvas.width, canvas.height);
if (editType.dataTypeList.includes(drawType)) {
imageObj.data = handleData[drawType](imageObj.data);
ctx.putImageData(imageObj, 0, 0);
} else if (drawType === "mosaic") {
// 马赛克
handleData[drawType](ctx, canvas);
}
requestAnimationFrame(drawFrame);
// setTimeout(drawFrame, 1000);
}
// 检查浏览器是否支持 getUserMedia API
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
// 请求访问摄像头
navigator.mediaDevices.getUserMedia({ video: true })
.then(function (stream) {
// 将视频流绑定到视频元素上
video.srcObject = stream;
// 开始绘制视频画面到画布上
requestAnimationFrame(drawFrame);
})
.catch(function (error) {
console.error('无法访问摄像头:', error);
});
} else {
console.error('浏览器不支持 getUserMedia API');
}
buttons.forEach(button => {
button.addEventListener("click", function (e) {
drawType = e.target.dataset.type;
})
})
takePhoto.addEventListener('click', function (e) {
// 绘制原始 Canvas 的内容到新的 Canvas 上
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height);
// 将内容转换为数据 URL
const dataURL = canvas.toDataURL();
// 创建一个 <a> 元素并设置属性
const link = document.createElement('a');
link.href = dataURL;
link.download = 'screenshot.png'; // 设置要保存的文件名
// 模拟点击 <a> 元素来触发下载
link.click();
})
</script>
</body>
</html>