JavaScript全解析——设计模式是什么
JavaScript全解析——设计模式是什么
●为了解决一类问题给出的 简洁而优化 的解决方案
●设计模式不是规则,是程序员开发过程中的经验总结
单例模式
●一个构造函数一生只有一个 实例对象
<script>
function Person(name) {
this.name = name
}
// 核心代码
let instance = null
function singleton() {
if (!instance) instance = new Person('Jack')
return instance
}
// 实现单例模式创建实例对象
// 第一次调用 singleton() 函数, 此时 instance 就是 null
// if 条件为 true, 给 instance 赋值
// 赋值为一个 Person 的实例
// return instance, 把 instance 保存的值赋值给了 p1
// p1 拿到的是 Person 的实例
const p1 = singleton()
// 第二次调用 singleton() 函数, 此时 instance 是Perosn 的实例
// if 条件为 false, 后面的代码不执行
// 直接执行 return instance
// 又一次把 Person 的实例返回了
// 赋值给了 p2 变量
// p2 拿到的还是 Person 之前的哪一个实例
const p2 = singleton()
console.log(p1, p2)
console.log(p1 === p2)
</script>
单例模式变形
●利用闭包和自执行函数结合的方式实现
function Person(name) {
this.name = name
}
let instance = null
function singleton() {
if (!instance) instance = new Person('Jack')
return instance
}
// 我们要以闭包的形式得到singleton 不让都暴露在全局
// 这里我们要使用的就是自执行函数
const singleton = (function outer() {
function Person(name) {
this.name = name
}
let instance = null
return function inner() {
if (!instance) instance = new Person('Jack')
return instance
}
})()
// const p1 = singleton()
// const p2 = singleton()
// console.log(p1, p2);
// console.log(p1 === p2);
// 到了这里我们该放进去的都放进去了接下来我们继续变形
/*
Person这个变量名现在只在函数里面有作用
如果我现在把singleton变成Person 和里面的Person有没有影响?
是没有影响的
我们先不改变 为了好说
现在要弄明白一个问题 就是外面的singleton()调用的是那个函数
*/
const singleton = (function outer() {
function Person(name) {
this.name = name
}
// 在Person的原型对象上定义一个方法 用来修改Person构造函数里面的name的值
Person.prototype.setName = function (val) { this.name = val }
// 单例的核心代码
let instance = null
return function inner(name) {
if (!instance) instance = new Person()
// 这里调用setName函数
instance.setName(name)
// 这个位置的instance就是person的实例
return instance
}
})()
// const p1 = singleton('Rose')
console.log(p1);
// const p2 = singleton('Jack')
console.log(p2);
console.log(p1 === p2);
// 再次修改变量
const Person = (function outer() {
function Person(name) {
this.name = name
}
// 在Person的原型对象上定义一个方法 用来修改Person构造函数里面的name的值
Person.prototype.setName = function (val) { this.name = val }
// 单例的核心代码
let instance = null
return function inner(name) {
if (!instance) instance = new Person()
// 这里调用setName函数
instance.setName(name)
// 这个位置的instance就是person的实例
return instance
}
})()
// 所以这个时候我们写new也没有关系
// 因为return的是一个复杂数据类型 自动创建的能力没有了
const p1 = new Person('Rose')
console.log(p1);
const p2 = new Person('Jack')
console.log(p2);
console.log(p1 === p2);
案例-自定义弹出层
结构
<!-- 整个弹出层 -->
<div class="dialog">
<!-- 顶部内容 -->
<div class="top">
<p>提示</p>
<span>X</span>
</div>
<!-- 中间内容区域 -->
<div class="content">
真实内容
</div>
<!-- 底部区域 -->
<div class="bottom">
<button>确定</button>
</div>
</div>
样式
.dialog {
width: 600px;
height: 360px;
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
box-shadow: 1px 2px 2px 0px #ccc;
background-color: #fff;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
display: flex;
flex-direction: column;
border-radius: 15px;
}
.dialog > .top {
height: 45px;
background-color: skyblue;
display: flex;
box-sizing: border-box;
justify-content: space-between;
padding: 0 20px;
align-items: center;
border-bottom: 1px solid #ccc;
border-radius: 15px 15px 0 0;
}
.dialog > .top > p {
font-size: 22px;
font-weight: 700;
color: #fff;
}
.dialog > .top > span {
cursor: pointer;
}
.dialog > .bottom {
height: 45px;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid #ccc;
}
.dialog > .bottom > button {
font-size: 20px;
padding: 0 10px;
cursor: pointer;
}
.dialog > .content {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
font-size: 20px;
}
交互
// 书写单例模式代码
const Dialog = (function () {
// 构造函数体
class Dialog {
constructor () {
this.dialog = document.createElement('div')
// 顶部面板
this.top = null
// 面板内的文本
this.desc = null
// 中间的内容区域
this.content = null
// 底部
this.bottom = null
// 面本内的文本
this.descText = '提示'
// 提示文本内容
this.title = '你好 世界'
// 颜色和文本你对照表
this.list = [
{
name: 'success',
descText: '成功',
bgColor: 'green'
},
{
name: 'danger',
descText: '危险',
bgColor: 'red'
},
{
name: 'warning',
descText: '警告',
bgColor: 'orange'
},
{
name: 'default',
descText: '提示',
bgColor: '#fff'
},
{
name: 'info',
descText: '信息',
bgColor: 'skyblue'
}
]
this.single()
}
// 只执行一次
single () {
this.creHTML()
this.setCss()
this.bindEvent()
}
// 创建整体结构
creHTML () {
// 创建 top 结构
this.top = document.createElement('div')
this.desc = document.createElement('p')
this.desc.innerText = this.descText
this.span = document.createElement('span')
this.span.innerText = 'X'
// 把 p 和 span 插入到 top 内
this.top.appendChild(this.desc)
this.top.appendChild(this.span)
// 创建内容结构
this.content = document.createElement('div')
this.content.innerText = this.title
// 创建底部结构
this.bottom = document.createElement('div')
this.btn = document.createElement('button')
this.btn.innerText = '确定'
this.bottom.appendChild(this.btn)
// 把创建好的结构插入到 dialog 内
this.dialog.appendChild(this.top)
this.dialog.appendChild(this.content)
this.dialog.appendChild(this.bottom)
// 把创建好的 dialog 结构插入到 页面内
document.body.appendChild(this.dialog)
}
// 设置 css 样式
setCss () {
// 给 dialog 设置样式
setStyles(this.dialog, {
width: '600px',
height: '360px',
'border-top': '1px solid #ccc',
'border-left': '1px solid #ccc',
'box-shadow': '1px 2px 2px 0px #ccc',
'background-color': '#fff',
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
margin: 'auto',
display: 'flex',
'flex-direction': 'column',
'border-radius': '15px',
})
// 给 top 设置样式
setStyles(this.top, {
height: '45px',
'background-color': 'skyblue',
display: 'flex',
'box-sizing': 'border-box',
'justify-content': 'space-between',
padding: '0 20px',
'align-items': 'center',
'border-bottom': '1px solid #ccc',
'border-radius': '15px 15px 0 0'
})
// 给 content 设置样式
setStyles(this.content, {
flex: 1,
display: 'flex',
'justify-content': 'center',
'align-items': 'center',
'font-size': '20px'
})
// 给 bottom 设置样式
setStyles(this.bottom, {
height: '45px',
display: 'flex',
'align-items': 'center',
'justify-content': 'center',
'border-top': '1px solid #ccc'
})
// 给 面板提示 设置样式
setStyles(this.desc, {
'font-size': '22px',
'font-weight': 700
})
// 给 面板的 关闭按钮
setStyles(this.span, {
cursor: 'pointer'
})
// 底部按钮
setStyles(this.btn, {
'font-size': '20px',
padding: '0 10px',
cursor: 'pointer'
})
}
// 事件绑定
bindEvent () {
this.btn.addEventListener('click', () => {
this.dialog.style.display = 'none'
})
this.span.addEventListener('click', () => {
this.dialog.style.display = 'none'
})
}
// 每次都要执行
init (title = '', type = 'default') {
// 根据你的 文本 和 关键字 设置内容
// 1. 根据类型获取到对照表内的对应信息
const info = this.list.find(item => item.name === type) || { name: 'default', descText: '提示', bgColor: '#fff' }
// 2. 根据 info 的内容开始设置
this.top.style.backgroundColor = info.bgColor
this.desc.innerText = info.descText
this.descText = info.descText
this.content.innerText = title
this.title = title
// 让 dialog 显示出来
this.dialog.style.display = 'flex'
}
}
// 功能函数
function setStyles(ele, styles) {
for (let k in styles) {
ele.style[k] = styles[k]
}
}
// 单例模式核心代码
let instance = null
return function (...arg) {
if (!instance) instance = new Dialog()
instance.init(...arg)
return instance
}
})()
发布订阅模式
<!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>
/*
发布订阅模式
例子: 去书店买书
没有设计模式 就是面向过程
=> 去到书店 , 问有没有书 , 没有就回去了,
=> 过一会儿 , 在去书店问问有没有书 , 没有就回去
=> 过一会儿 , 在去书店问问有没有书 , 没有就回去
=> 过一会儿 , 在去书店问问有没有书 , 没有就回去
=> 过一会儿 , 在去书店问问有没有书 , 没有就回去
=> 过一会儿 , 在去书店问问有没有书 , 没有就回去
=> 过一会儿 , 在去书店问问有没有书 , 没有就回去
=> .....
=> 过一会儿 , 去到书店问问有没有书 , 有了 , 买回去
发布订阅模式
=> 去到书店 , 问有没有书 , 没有的话留下一个电话号码 , 有了给我打电话
=> 回去等待电话
=> 接到电话了去到书店把书买回去
发布订阅: 把你想要做的事情都写好(也就是注册到on函数中) , 将来一触发(就是执行了trigger函数)就都做完了
*/
// 代码实现
class Observer{
constructor(status){
// 表示你的初始状态 , 将来一旦这个状态发生了改变我们要触发
// 也就是一旦有书了要通知人来买
this.status = status
// 定义个消息盒子 , 记录的都有谁和我定了书
// 定了几本书 , 都有那些书 , 是不是要记录上啊
this.message = {}
}
// 在原型上定义几个方法
// 这个表示注册一个事件
// 下面函数调用了传递了实参 , 我们这里要接受形参
on (type,fn) {
// type 事件类型
// fn 事件处理函数
// 接下来我们要绑定在message内部
// 我们要判断message内部有没有type这个类型
// 如果没有我们就让它称为一个数组
if(!this.message[type]) this.message[type] = []
// 之后我们向向消息盒子内添加函数
this.message[type].push(fn)
}
// 这个表示取消这个事件
// 也要接收两个参数
off (type , fn) {
// 这个时候我们也要先判断消息盒子中有没有这个类型
// 如果没有 , 我们就什么都不用做了
if(!this.message[type]) return
// 代码能执行到这里说名有这个函数
// 那我们就解绑这个函数就好了
// 如何解绑呢?
this.message[type] = this.message[type].filter(item => item !== fn) // 把不一样的重新赋值 , 那就把一样的筛出去了
}
// 触发事件
trigger (type) {
// 首先要判断有没有这个事件 , 如果没有就什么都不做了
if(!this.message[type]) return
// 代码能执行到这里说明有这个事件
// 那接下来就触发这个事件就好了
// 里面就是每一个函数 , 直接调用就可以了
this.message[type].forEach(item => item())
}
}
// 实例化个对象
// 这就是一个第三方 , 你可以把这个当做是书店的店员
const o = new Observer('没有')
// console.log(o);
// 准备几个函数
function handlerA() { console.log('handlerA');}
function handlerB() { console.log('handlerB');}
function handlerC() { console.log('handlerC');}
// 向o 上注册一个事件
// js这本书来了 , 执行handlerA这个函数
o.on('js',handlerA)
o.on('js',handlerB)
o.on('css',handlerA)
o.on('css',handlerC)
o.on('html',handlerB)
o.on('html',handlerC)
console.log(o);
// 向 o 取消一个事件注册
// 也就是我从别的地方买到这本书了 , 不要在给我打电话了
o.off('js',handlerA)
// 都注册好了以后由店员来触发这个事件
o.trigger('js') // 表示js这本书来了 , 这样就触发了这个函数
</script>
</body>
</html>
策略模式
●也是设计模式的一种 , 也是相对来说用的比较多的一种
●作用:
○减少if条件的判断
○就是把多种形式的内容罗列出来
●核心
○就是用一个数据结构 , 把各种操作记录下来
○值存储的就是操作后的结果
●例子
○需求:
=> 我已知一个购物总价 1356
=> 需要根据折扣计算最终价格
=> 折扣种类 8折 7折 300-20 500-50
实现:
=> 我们需要在标签上记录一个属性
=> 能记录到我们的各种折扣
=> 如果折扣有很多呢? 如果后期需要添加或者减少折扣呢?
=> 这样操作就能麻烦
●之前的实现方式
const type = '80%' // '70%' '300-20' '500-50'
if (type === '80%') {
} else if (type === '70%') {
console.log('逻辑实现');
} else if (type === '300-20') {
console.log('逻辑实现');
} else if (type === '500-50') {
console.log('逻辑实现');
} else if (type === '300-30') {
console.log('逻辑实现');
}
策略模式
// 我们利用的还是闭包
const discount = (function () {
// 留存一份数据结构
const discountList = {
// 这里的数据要如何设计
// 需要有折扣类型和折扣以后的价格
// 标识:能计算出最后的价格(这里的将来需要调用实现 , 也就是这里需要一个函数)
'80%':function (total) { return (total * 0.8).toFixed(2) }
}
// 因为要使用闭包 , 我们需要返回一个函数
function inner(total,type) {
// 这里我们出计算方式
// 这里需要根据传递进来的折扣查看discountList中有没有这个折扣就好了
// 需要把 type 当做键来访问 discountList
// 如果有返回的是一个函数 , 如果没有返回的是一个undefined
// console.log(discountList[type]);
// 判断有没有这个折扣 , 没有这个折扣就返回总价
if (!discountList[type]) return total
// 代码能执行到这里说明是有这个折扣的
return discountList[type](total) - 0
}
// 这里把inner函数当做一个对象来看待 , 向里面插入一些方法
// 添加一个方法 , 专门用来向 discountList内添加折扣类型
inner.add = function (type,fn) {
// type:表示添加的折扣
// fn: 是一个函数,用来计算出最后的价格
// console.log(type);
// console.log(fn);
// 添加到 discountList中
discountList[type] = fn
}
// 添加一个方法 , 用来删除一种折扣
inner.remove = function (type) {
// 删除掉这个折扣
delete discountList[type]
}
// 我们含可以把这个折扣的列表返回出去查看
inner.getList = function () {
return discountList
}
// 这里返回inner函数
return inner
})()
// 将来使用
// const res = discount('总价','折扣类型')
const res = discount(1000,'80%')
console.log(res);
// 将来使用添加的折扣
discount.add('300-20', price => price - parseInt(price / 300) * 20)
// 添加完毕以后需要在下面计算
const res1 = discount(1000,'300-20')
console.log(res1);
// 查看折扣列表
const list = discount.getList()
console.log(list);
案例-收银台
<!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>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
padding-left: 200px;
}
input, button {
outline: none;
}
ul, li {
list-style: none;
}
.addSale, .calc {
width: 200px;
height: 33px;
background-color: skyblue;
color: #fff;
cursor: pointer;
border: none;
margin: 30px;
}
input {
width: 300px;
height: 30px;
padding-left: 20px;
font-size: 22px;
display: block;
margin: 20px;
}
ul {
display: flex;
margin: 20px;
}
ul > li {
width: 120px;
height: 120px;
background-color: orange;
margin: 10px;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 30px;
cursor: pointer;
}
ul > li.active {
background-color: skyblue;
}
p {
font-size: 100px;
color: red;
margin: 20px;
}
</style>
</head>
<body>
<button class="addSale">添加折扣类型</button>
<!-- 总价格 -->
<input type="text">
<!-- 折扣类型 -->
<ul>
<li class="active">1111</li>
<li>1111</li>
<li>1111</li>
</ul>
<button class="calc">计算总价</button>
<!-- 最终价格 -->
<p>0.00</p>
折扣名称(显示在按钮上的文本): <input type="text" class="type">
计算方式(总价以 x 表示): <input type="text" class="method">
<script>
/*
收银台
*/
// 0. 获取元素
const ulBox = document.querySelector('ul')
const calcBtn = document.querySelector('.calc')
const totalInp = document.querySelector('input')
const pBox = document.querySelector('p')
const addBtn = document.querySelector('.addSale')
const nameInp = document.querySelector('.type')
const methodInp = document.querySelector('.method')
// 0. 以策略模式的形式准备一个 折扣记录
const calcPrice = (function () {
// 折扣列表
const calcList = {
'80%': function (total) { return (total * 0.8).toFixed(2) },
'70%': function (total) { return (total * 0.7).toFixed(2) }
}
function inner(total, type) {
if (!calcList[type]) return '0.00'
return calcList[type](total)
}
inner.add = function (type, fn) {
calcList[type] = fn
}
inner.remove = function (type) {
delete calcList[type]
}
inner.getList = function () {
return calcList
}
return inner
})()
// 0. 准备变量
let type = ''
// 1. 拿到当前所有的折扣渲染 li
// 将来一旦折扣添加了, 需要重新渲染 li
bindHtml()
function bindHtml() {
// 1-1. 拿到折扣类型列表
const list = calcPrice.getList()
// 1-2. 利用 list 渲染 li
let str = ''
for (let k in list) {
str += `
<li data-type="${ k }">${ k }</li>
`
}
ulBox.innerHTML = str
}
// 2. 折扣类型的选择(排他)
tab()
function tab() {
ulBox.addEventListener('click', e => {
if (e.target.nodeName !== 'LI') return
// 所有的没有类名
for (let i = 0; i < ulBox.children.length; i++) {
ulBox.children[i].classList.remove('active')
}
// 当前这个有类名
e.target.classList.add('active')
// 记录下当前的折扣类型
type = e.target.dataset.type
})
}
// 3. 结算按钮的事件
calcBtn.addEventListener('click', () => {
// 3-1. 拿到总价
const totalPrice = totalInp.value - 0
// 3-2. 计算最终价格
const resultPrice = calcPrice(totalPrice, type)
// 3-3. 把最终价渲染
pBox.innerText = resultPrice
})
// 4. 添加折扣
// 我们只能期望用户给我们两个内容
// 折扣名称
// 计算方式
addBtn.addEventListener('click', () => {
// 4-1. 拿到折扣名称
const name = nameInp.value
// 4-2. 拿到公式
// 开始组装
const r = '(parseInt(' + methodInp.value + ')).toFixed(2)'
// 语法: eval(字符串)
// 作用: 把该字符串当做js代码来执行
// const res = eval(r)
// 开始添加了
calcPrice.add(name, x => eval(r))
// 4-3. 从新渲染一遍 li
bindHtml()
// 4-4. 把两个文本框清空
nameInp.value = ''
methodInp.value = ''
})
</script>
</body>
</html>
模块模式
●要是设计模式中的一种
● 模块模式可以指定类想暴露的属性和方法,并且不会污染全局。采用闭包的形式
// 定义个自执行函数
var Person = (function() {
// 定义一个变量
var name = 'rose'
// 定义一个函数(方法)
function sayName() {
console.log('我的名字是:',name)
}
// 返回一个对象
// 因为是自执行函数 , Person得到的就是一个对象
// return {
// name: name,
// sayName: sayName
// }
return {
name,
sayName
}
})()
// 使用
console.log(Person);
Person.sayName()
相关推荐HOT
更多>>JavaScript全解析——案例-验证码按钮倒计时
JavaScript全解析——案例-验证码按钮倒计时
算法评测标准---空间复杂度是什么?
算法评测标准---空间复杂度是什么?空间复杂度是什么?一. 空间复杂度的概念,复杂度(Space Complexity),是对一个算法在运行过程中临时占用存...详情>>
2023-03-23 20:15:04怎么用css画三角形?
怎么用css画三角形?border这里的像素值 和 border-left这里的像素值可以是不一样的,也可以是一样的,根据三角形的形状来进行具体设置值即可,要...详情>>
2023-03-23 15:27:16Maven集成 tomcat插件及使用教程
在实际的项目开发中,特别是分布式项目,往往有N多个子项目需要同时启动测试。这样多个项目引用tomcat插件,配置不同的端口,就可以同时启动N个...详情>>
2023-02-23 14:44:00热门推荐
JavaScript全解析——案例-验证码按钮倒计时
沸hooks模仿componentDidMount是什么
热Redis的八大特性有哪些?
热Redis应用场景有哪些?
新JavaScript全解析——设计模式是什么
JavaScript全解析——数据操作是什么
JavaScript全解析——DOM操作-获取元素的方式
useEffect如何写在依赖?
useRef与createRef的区别是什么?
redux中如何使用中间件?
useDeferredValue vs useTransition有什么不同?
createSelector的使用场景有哪些?
从零开始学Java53之Integer底层原理探究
移动端兼容适配是什么?