- 结构型设计模式,关注于如何将类或对象,组合成更大、更复杂的结构
外观模式
核心
- 目的:为复杂的子系统的访问,提供统一的、简便的接口
- 应用:API的兼容性封装,代码库的功能封装(简化操作)
- 重点:二次封装,接口具体实现和上层代码调用解耦
实现
function addEvent(dom, type, fn) {
if (dom.addEventListener) {
dom.addEventListener(type, fn, false);
}
else if (dom.attachEvent) {
dom.attachEvent("on" + type, fn);
}
else {
dom["on" + type] = fn;
}
}
function getEvent(e) {
return e || window.event;
}
function getTarget(e) {
var event = getEvent(e);
return event.target || event.srcElement;
}
function preventDefault(e) {
var event = getEvent(e);
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
[...document.getElementsByTagName("a")].forEach((item) => {
addEvent(item, "click", function (e) {
preventDefault(e);
console.log(getTarget(e));
});
});
var A = {
g(id) {
return document.getElementById(id);
},
css(id, key, value) {
document.getElementById(id).style[key] = value;
},
};
适配器模式
核心
- 定义:将一个类的接口,转化为另外一个接口(兼容不同结构的数据或方法)
- 重点:重新包装对象,了解适配对象的内部结构(与外观模式的区别)。对象解耦
适配同类框架
- 框架迁移兼容
- A 框架 迁移到 jQuery
- 让 A 的调用代码,适配 jQuery
- A 框架使用(方法的调用类似 jQuery)
A(function(){
A('button').on('click' , function(){
})
})
window.A = A = jQuery
适配异类框架
var A = A || {};
A.g = function (id) {
return document.getElementById(id);
};
A.on = function (id, type, fn) {
var dom = typeof id === "string" ? this.g(id) : id;
if (dom.addEventListener) {
dom.addEventListener(type, fn, false);
} else if (dom.attachEvent) {
dom.attachEvent("on" + type, fn);
} else {
dom["on" + type] = fn;
}
};
A.on(window, 'load', function(){
A.on('btn', 'click', function(){
})
})
var A = A || {};
A.g = function (id) {
return $(id).get(0);
};
A.on = function (id, type, fn) {
var dom = typeof id === "string" ? $(`#${id}`) : $(id);
dom.on(type, fn);
};
A.on(window, "load", function () {
A.on("btn", "click", function () {
});
});
参数适配器
function f(obj = {}) {
var _adapter = {
name: "nx",
title: "tx",
age: 20,
color: "red",
prize: 40,
};
for (var i in _adapter) {
_adapter[i] = obj[i] || _adapter[i];
}
}
服务器端数据适配
function formatData(obj) {
return {
a: format.A(obj.a),
arr: [obj.c, format.B(obj.d)],
keys: [obj.key1, obj.key2, obj.key3],
};
}
var format = {
A() {},
B() {},
};
代理模式
核心
- 定义:由于一个对象,不能直接访问另外一个对象。通过添加中介的方式实现访问(类似中间件)
- 主要用于解决跨域问题(xhr)
应用一:站长统计
- 统计用户的信息
- 实现:img,video, script等标签的 src 属性,可跨域发送请求(get 方式)
var Count = (function () {
var _img = new Image();
return function (params) {
var str = "http://localhost:3000/stat/a.png?";
for (let key in params) {
if (str) str += "&";
str += key + "=" + params[key];
}
_img.src = str;
};
})();
Count({ a: 1, b: 2, c: 3 });
var express = require("express");
var app = express();
app.use("/stat", (req, res) => {
console.log(req.query);
res.status(204).end();
});
app.listen(3000);
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/fabfc4e6604ff76e15a4b24c983ce597.png)
应用二:JSONP
- 通过 jsonp,实现跨域 get 请求(script标签实现)
- 服务端返回调用函数的字符串
<!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>
<script>
function jsonpCallback(status, data) {
console.log(status, data);
}
var script = document.createElement("script");
const data = {
a: 1,
str: "abdc",
arr: [
{
key: 111,
},
],
};
script.src = `http://localhost:3000/jsonp?fn=jsonpCallback&data=${JSON.stringify(
data
)}`;
document.body.appendChild(script);
</script>
</body>
</html>
var express = require("express");
var app = express();
app.use("/jsonp", (req, res) => {
console.log(req.query);
const { fn, data } = req.query;
res.status(200).end(
`
${fn}('success', ${data})
`
);
});
app.listen(3000);
- 本地打开网页(控制台输出)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/cbe8cdadc97016959f781ca216691c25.png)
应用三:代理模版
- 原理:相同域中,页面之间的互相调用
- 实现
- X 域 中有 A , B 页面, 服务器在 Y 域
- A 表单请求到 Y时,重定向到 B,B 加载后调用 A 中的方法
- form 表单的返回结果 B,嵌套在 iframe 中
- B 中通过 iframe, 找到 A 中的方法名,用 eval 调用
- Y 中可以做后端数据处理
- 目录结构
- public
- server.js(Y 域,处理请求)
- server2.js(X域, 访问 public 页面)
- A.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>Document</title>
<style>
input {
width: 200px;
}
</style>
</head>
<body>
<script>
function callback(data) {
console.log("成功数据:", data);
}
</script>
<iframe name="proxyIframe" id="proxyIframe" src=""></iframe>
<form
action="http://localhost:3000/template"
method="post"
target="proxyIframe"
>
<input name="fn" value="callback" />
<input name="proxy" value="http://localhost:3001/B.html" />
<input type="submit" value="提交" />
</form>
</body>
</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>Document</title>
</head>
<body>
<div>i am b</div>
<script>
window.onload = function () {
if (top === self) return;
var arr = location.search.substr(1).split("&");
var callback, args;
for (let v of arr) {
const item = v.split("=");
if (item[0] === "callback") {
callback = item[1];
} else if (item[0] === "arg") {
args = item[1];
}
}
try {
eval(`top.${callback}("${args}")`);
} catch (e) {
console.log(e);
}
};
</script>
</body>
</html>
var express = require("express");
var app = express();
app.use(express.urlencoded({ extended: true }));
app.post("/template", (req, res) => {
const { fn, proxy } = req.body;
res.redirect(`${proxy}?callback=${fn}&arg=success11`);
});
app.listen(3000);
var express = require("express");
var app = express();
app.use(express.static("public"));
app.listen(3001);
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/8d3a401e868f078b7c3f517aa541581d.png)
装饰者模式
核心
- 定义:在不改变原有对象的基础上,对其进行包装拓展,满足新需求
- 目的:在原有的功能上,新增功能(类似中间件)
实现
var decorator = function (id, fn) {
var el = document.getElementById(id);
if (typeof el.onclick === "function") {
var oldClickFn = el.onclick;
el.onclick = function () {
oldClickFn();
fn();
};
} else {
el.onclick = fn;
}
};
var el = document.createElement("div");
el.id = "id1";
el.style.padding = "40px";
el.innerText = "点我";
document.body.appendChild(el);
decorator("id1", () => {
console.log("do some 111");
});
decorator("id1", () => {
console.log("do some 222");
});
decorator("id1", () => {
console.log("do some 333");
});
桥接模式
核心
- 定义:在系统沿着多个纬度变化时,不增加其复杂度,并解耦(相同逻辑提取)
- 目的:实现层(如元素绑定事件)和抽象层(业务逻辑)分离
- 开闭原则:对拓展开放,对修改关闭(功能点拆分,功能拓展)
应用一:为不同的元素,添加相同的事件交互
function changeColor(dom, color, bg) {
dom.style.color = color;
dom.style.backgroundColor = bg;
}
[...document.getElementsByTagName("div")].forEach((el) => {
el.onmouseover = function () {
changeColor(this, "red", "blue");
};
el.onmouseout = function () {
changeColor(this, "blue", "red");
};
});
应用二:多元化对象
- 拆分或抽象复杂的对象,变成一个个独立的单元。通过元的组合(桥接),创建复杂的对象。元之间解耦。
function Speed(x, y) {
this.x = x;
this.y = y;
}
Speed.prototype.run = function () {
console.log("运动起来");
};
function Color(cl) {
this.color = cl;
}
Color.prototype.draw = function () {
console.log("绘制彩色");
};
function Shape(sp) {
this.shape = sp;
}
Shape.prototype.change = function () {
console.log("改变形状");
};
function Speek(wd) {
this.word = wd;
}
Speek.prototype.say = function () {
console.log("我说话了");
};
function Ball(x, y, c) {
this.speed = new Speed(x, y);
this.color = new Color(c);
}
Ball.prototype.init = function () {
this.speed.run();
this.color.draw();
};
new Ball(10, 10, "red").init();
组合模式
核心
- 定义:又称 部分-整体模式。将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性
- 目的:通过对部分的组合,简化和丰富复杂的整体(类似于创建虚拟DOM)
应用一:新闻模块
- 容器类的原型方法,做了二次封装
- 对象类按参数统一处理(也可拆分成单独模块,或按配置划分不同的类别)
- 根据不同的元素分组和class,显示UI
var abstractFn = function () {
throw new Error("请重写抽象方法");
};
function inheritPrototype(subClass, supClass) {
var p = Object.create(supClass.prototype);
p.constructor = subClass;
subClass.prototype = p;
}
var News = function () {
this.children = [];
this.element = null;
};
News.prototype = {
init: abstractFn,
add: abstractFn,
getElement: abstractFn,
};
var utils = {
init(tag, opt = {}) {
this.element = document.createElement(tag);
for (let key in opt) {
this.element[key] = opt[key];
}
},
add(child) {
this.children.push(child);
this.element.appendChild(child.getElement());
return this;
},
};
var Container = function (id, parent) {
News.call(this);
this.id = id;
this.parent = parent;
this.init();
};
inheritPrototype(Container, News);
Container.prototype.init = function () {
utils.init.call(this, "ul", { id: this.id, className: "new-container" });
};
Container.prototype.add = function (child) {
return utils.add.call(this, child);
};
Container.prototype.getElement = function () {
return this.element;
};
Container.prototype.show = function () {
this.parent.appendChild(this.element);
};
var Item = function (className = "") {
News.call(this);
this.className = className;
this.init();
};
inheritPrototype(Item, News);
Item.prototype.init = function () {
utils.init.call(this, "li", { className: this.className });
};
Item.prototype.add = function (child) {
return utils.add.call(this, child);
};
Item.prototype.getElement = function () {
return this.element;
};
var NewsGroup = function (className = "") {
News.call(this);
this.className = className;
this.init();
};
inheritPrototype(NewsGroup, News);
NewsGroup.prototype.init = function () {
utils.init.call(this, "div", { className: this.className });
};
NewsGroup.prototype.add = function (child) {
return utils.add.call(this, child);
};
NewsGroup.prototype.getElement = function () {
return this.element;
};
function NewObj(opt) {
News.call(this);
const _adapter = {
url: "",
href: "#",
className: "normal",
label: "",
...opt,
};
for (let key in _adapter) {
this[key] = _adapter[key];
}
this.init();
}
inheritPrototype(NewObj, News);
NewObj.prototype.init = function () {
let { type, className, href, label, text } = this;
this.element = document.createElement("a");
let labelClass = "";
if (type === "img") {
var img = new Image();
img.src = this.url;
this.element.appendChild(img);
} else {
if (label) {
labelClass = `ui-${label}`;
if (this.pos === "left") {
text = `[${label}]${text}`;
} else {
text = `${text}[${label}]`;
}
}
this.element.innerHTML = text;
}
this.element.className = `ui-${type} ${labelClass} ${className || ""}`;
this.element.href = href;
};
NewObj.prototype.add = function () {};
NewObj.prototype.getElement = function () {
return this.element;
};
new Container("news", document.body)
.add(
new Item("normal").add(
new NewObj({
text: "梅西不拿。。。",
})
)
)
.add(
new Item("normal").add(
new NewObj({
text: "梅西不拿111。。。",
label: "live",
})
)
)
.add(
new Item("normal").add(
new NewsGroup("has-img")
.add(
new NewObj({
type: "img",
url: "xxx",
className: "small",
})
)
.add(
new NewObj({
text: "313232火炮。。。",
})
)
)
)
.add(
new Item("normal").add(
new NewObj({
text: "火炮。。。",
label: "CBA",
pos: "left",
})
)
)
.show();
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/6cba3ae4edfa35f731774a1ee64c37e4.png)
应用二:表单
- 表单按照不同的元素类型和UI结构,划分成不同的UI组件(互相组合)
- 结构配置:类似虚拟dom的JSX(结构化的标签)(描述性配置编程)
new Form("formId", document.body)
.add(
new Group("account", "账号").add(
new FormItem({
label: "用户名",
key: "user_name",
rightText: "4到6位数字",
}),
new FormItem({
label: "密码",
key: "pwd",
rightText: "6到12位数字或字母",
})
)
)
.add(
new Group("info", "信息").add(
new FormItem({
label: "昵称",
key: "nic",
}),
new FormItem({
label: "状态",
key: "status",
})
)
);
var config = {
id: "formId",
container: document.body,
list: [
{
label: "账号",
key: "account",
list: [
{
label: "用户名",
key: "user_name",
rightText: "4到6位数字",
placeholder: "请输入用户名",
},
],
},
],
};
handle(config)
享元模式
核心
- 定义:减少创建对象的数量,以减少内存占用和提高性能
- 原理:提取共享的数据和方法,优化性能与代码
应用一:分页功能
- 原理:页面列表始终展示一页的元素,分页时替换元素
- js
var Flyweight = (function () {
var created = [];
function create() {
var dom = document.createElement("div");
document.getElementById("container").appendChild(dom);
created.push(dom);
return dom;
}
return {
getDiv() {
if (created.length < num) {
return create();
} else {
const div = created.shift();
created.push(div);
return div;
}
},
};
})();
var article = new Array(32)
.fill(0)
.map((item, index) => `我是第${index}个元素`);
var page = 0;
var num = 10;
var len = article.length;
for (var i = 0; i < num; i++) {
if (article[i]) {
Flyweight.getDiv().innerHTML = article[i];
}
}
document.getElementById("next").onclick = function () {
if (len < num) return;
var n = (++page * num) % len;
for (let j = 0; j < num; j++) {
Flyweight.getDiv().innerHTML = article[n + j] || article[n + j - len];
}
};
<!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>
<div id="container"></div>
<button id="next">下一页</button>
<script src="./index.js"></script>
</body>
</html>
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/7934b8edc0623b322005eb24d8ed57bf.png)
应用二:享元动作
- 创建角色时,都会有运动这个动作,封装共享(原型继承)
var Flyweight = {
moveX(x) {
this.x = x;
return this;
},
moveY(y) {
this.y = y;
return this;
},
};
var Player = function (x, y, c) {
this.x = x;
this.y = y;
this.color = c;
};
Player.prototype = {
...Flyweight,
changeC() {
},
};
var Spirit = function (x, y, r) {
this.x = x;
this.y = y;
this.r = r;
};
Spirit.prototype = Flyweight;
Spirit.prototype.changeR = function () {};
console.log(new Player(5, 2, "red").moveX(1).moveY(1));
代码