设计模式-单例模式
- 设计模式:
- 为了实现某一类功能给出的简洁而优化的解决方案
- 设计模式 - 单例模式
- 核心意义: 一个构造函数(类) 一生只有一个实例
- 思路:
- 实例化一个类的时候
- 需要先判断, 如果当前类以前被实例化过, 不需要再次实例化了
- 而是直接拿到原先的实例化对象
- 如果没有被实例化过, 那么新实例化一个实例对象
- 解决:
- 单独找一个变量, 初始化为 null
- 每一次创建实例的时候, 都判断一下该变量的值
- 如果为 null, 那么创建一个实例, 赋值给该变量
- 如果不为 null, 那么我们不再创建实例, 直接给出该变量的值
class Dialog {
constructor() {
console.log("在 页面中 创建了一个 弹出层");
}
}
let instance = null;
function newDialog() {
if (instance == null) {
instance = new Dialog();
}
return instance;
}
// 第一次调用, 创建一个 实例化对象然后给到 instance 中, 并返回 instance
let d1 = newDialog();
// 第二次调用, 直接返回 instance
let d2 = newDialog();
console.log(d1);
console.log(d2);
console.log(d1 === d2);
单例模式变形
-
单例模式问题
- 向外部写了一个全局变量 instance
- 多次调用时, 无法传递不一样的参数
- 构造函数的类名与创建实例的函数名是两个东西
-
解决:
- 利用闭包
let newDialog = (function fn() { let instance = null; return function () { if (instance == null) { instance = new Dialog(); } return instance; }; })();- 创建一个 init 方法
class Dialog { constructor() { console.log("在 页面中 创建了一个 弹出层, 文本为: "); this.title = ""; } setTitle(newTitle) { this.title = newTitle; console.log("将 title 属性 重新修改为了: ", this.title); } } let newDialog = (function outer() { let instance = null; return function inner(str) { // 没有接收到不同参数的原因在于, 这个 if 只有第一次才会执行, 后续永远不会执行 if (instance == null) { instance = new Dialog(); } // 但是, 这个位置却可以多次执行, 所以我们可以在这里, 给这个 类传递不同的参数 instance.setTitle(str); return instance; }; })();- 将类放在闭包函数内, 然后将函数名修改为类名
let Dialog = (function outer() { // 因为 Dialog 这个 类, 永远就使用一次, 所以函数局部 更合适 class Dialog { constructor() { console.log("在 页面中 创建了一个 弹出层, 文本为: "); this.title = ""; } setTitle(newTitle) { this.title = newTitle; console.log("将 title 属性 重新修改为了: ", this.title); } } let instance = null; return function inner(str) { if (instance == null) { instance = new Dialog(); } instance.setTitle(str); return instance; }; })();
设计模式-策略模式
- 设计模式 - 策略模式
- 核心: 减少 if...else...
- 作用: 把多种形式的内容罗列出来
- 案例:
- 已知一个购物总价 1376
- 需要根据折扣计算最终价格
- 折扣种类: 8 折, 7 折, 300-30, 500-50
- 当前案例核心:
- 用一个数据结构, 把各种折扣记录下来
- 值存储的就是该折扣和总价的计算方式
const calcPrice = (function () {
const calcList = {
"80%": (total) => (total * 0.8).toFixed(2),
"70%": (total) => (total * 0.7).toFixed(2),
};
// 任何引用数据类型, 都可以当作 对象去使用
function inner(type, total) {
return calcList[type](total);
}
inner.add = function (type, fn) {
calcList[type] = fn;
};
inner.sub = function (type) {
delete calcList[type];
};
inner.getList = function () {
return calcList;
};
return inner;
})();
const res = calcPrice("80%", 1000);
console.log(res);
const res1 = calcPrice("70%", 1000);
console.log(res1);
calcPrice.add("300-30", (total) => total - Math.floor(total / 300) * 30);
const res2 = calcPrice("300-30", 1000);
console.log(res2);
calcPrice.sub("70%");
console.log(calcPrice.getList());
发布订阅模式
- 设计模式-发布订阅
- 首先明确 这个模式和一个叫做观察者模式类似但不是一个东西
- 目前一批开发人员认为这两个是一个东西, 另外一批认为是两个东西
- 案例: 买一本书
- 以前
- 去到书店, 问店员有没有 JS 从入门到入土
- 没有, 回去
- 一会再回去问, 有没有
- 没有, 回去
- 一会再回去问, 有没有
- ...
- 一会再回去问, 有没有
- 有了, 购买
- 现在
- 去到书店, 问店员有没有 JS 从入门到入土
- 没有, 给店员留下一个联系方式
- 直到店员给你打电话, 你触发技能(去买回来)
- 以前
- 在 JS 内 发布订阅 使用最多的地方 (addEventListener)
- xxx.addEventListener('click', fn)
- 思考: 以什么数据格式来记录
店员 ===
{
a: [fn1, fn2],
b: [fn3, fn4],
};
class Observer {
constructor(name) {
this.name = name; // 这步模拟店员名字, 可以不要
// 店员的记录手册
this.messages = {};
}
add(type, fn) {
if (this.messages[type] === undefined) this.messages[type] = [];
this.messages[type].push(fn);
}
remove(type, fn) {
if (this.messages[type] === undefined) return;
// if (fn === undefined) return this.messages[type] = []
if (fn === undefined) return delete this.messages[type];
this.messages[type] = this.messages[type].filter((item) => item !== fn);
}
trigger(type) {
// console.log(type)
// console.log(this.messages[type])
this.messages[type].forEach((item) => item());
}
}
const ob = new Observer("小李");
const A1 = () => {
console.log("我是 张三, 我要买这本书");
};
const A2 = () => {
console.log("我是 李四, 我要买这本书");
};
const B1 = () => {
console.log("我是 王五, 我要买这本书");
};
ob.add("A1", A1);
ob.add("A1", A2);
ob.add("B1", B1);
// ob.remove('A1', A1)
// ob.remove('A1')
ob.remove("C1");
ob.trigger("A1");
console.log(ob);
vue花括号语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root">
<p>使用name属性: {{name}}</p>
<p>使用age属性: {{ age}}</p>
<p>使用width属性: {{width }}</p>
<p>使用height属性: {{ height }}</p>
</div>
修改年龄: <input type="text" id="inp">
<script src="./02_vue花括号语法.js"></script>
<script>
const app = createApp({
el: '#root',
data: {
name: '张三',
age: 18,
width: 10086,
height: 10010
},
})
const inp = document.querySelector('#inp')
inp.oninput = function () {
app.age = this.value
}
// console.log(app)
</script>
</body>
</html>
function createApp(options) {
// 1. 安全判断
// 1.1 options 的 el 属性必须传递
if (options.el === undefined) {
// return console.log('此时代码出现问题, 应该阻断程序运行, 并且通知开发者有问题')
throw new Error("el 选项必须传递");
}
// 1.2 options 的 data 属性必须传递, 并且只能是 对象类型
if (Object.prototype.toString.call(options.data) !== "[object Object]") {
throw new Error("data 选项必须传递, 并且只能是对象类型");
}
// 1.3 el 属性的值, 必须能够获取到 DOM 节点
const root = document.querySelector(options.el);
if (root === null) {
throw new Error(
"el 选项必须为字符串, 并且需要能够获取到当前页面的根元素"
);
}
// 2. 开始数据劫持 (核心代码)
// 创建一个对象 存储我们劫持后的数据
const _data = {};
// 开始劫持
for (let key in options.data) {
Object.defineProperty(_data, key, {
get() {
return options.data[key];
},
set(val) {
options.data[key] = val;
// 每次修改数据后, 重新渲染页面
bindHtml(root, _data, rootHtml);
},
});
}
// 存储一个最开始的标签结构
const rootHtml = root.innerHTML;
// 首次打开页面就回执行这个地方的代码, 调用渲染函数
bindHtml(root, _data, rootHtml);
// 返回劫持好的对象
return _data;
}
/**
* 三个形参的拼写不重要
*
* 第一个: 去哪个标签内修改
* 第二个: 变量对应的数据是什么
* 第三个: 原本默认的 html 结构, 防止初次选然后找不到 {{}}
*/
function bindHtml(root, _data, _str) {
// 1. 创建正则, 用于捕获出字符串中所有的 {{XXX}}
// const reg = /{{name}}/
// const reg = /{{\w+}}/
// const reg = /{{(\w+)}}/
// const reg = /{{ *(\w+) *}}/
const reg = /{{ *(\w+) *}}/g;
// 2. 利用字符串的方法, 将所有符合规则的部分捕获到一个数组中
const arr = _str.match(reg);
// console.log(arr)
// 3. 遍历数组, 拿到所有的字符串, 然后对字符串进行后续的操作
arr.forEach((item) => {
const key = /{{ *(\w+) *}}/.exec(item)[1];
_str = _str.replace(/{{ *(\w+) *}}/, _data[key])
})
root.innerHTML = _str
}