单例模式的定义是,保证一个类仅有一个实例,并提供一个访问它的全局访问点。
有一些对象,比如线程池/全局缓存/浏览器中的 window
对象等等,我们就只需要一个实例。
下面将根据实际场景进行介绍。
当我们单击登录按钮时,页面中会出现一个登录的浮窗,而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。
传统做法在页面加载完成时,就创建好登录浮窗,当用户点击登录按钮时,显示登录浮窗,实现代码如下:
<button id="loginBtn">登录</button>
var loginLayer = (() => {
let div = document.createElement('div')
div.innerHTML = '我是登录弹窗'
div.style.display = 'none'
document.body.appendChild(div)
return div
})()
document.getElementById('loginBtn').onclick = () => {
loginLayer.style.display = 'block'
}
上述代码有以下缺点:
1 . 在无需登录的情况下,也会新增登录浮窗的 DOM
节点,浪费性能。
现在优化一下,将代码改为,在用户点击登录按钮后,才新增登录浮窗的 DOM
节点。
代码如下:
var createLoginLayer = () => {
let div = document.createElement('div')
div.innerHTML = '我是登录弹窗'
div.style.display = 'none'
document.body.appendChild(div)
return div
}
document.getElementById('loginBtn').onclick = () => {
const loginLayer = createLoginLayer()
loginLayer.style.display = 'block'
}
上述代码也存在缺陷,具体如下:
1 . 每次点击登录按钮,都会创建一个登录浮窗,频繁的创建 DOM
节点更加浪费性能。
实际上,我们只需要创建一次登录浮窗。
通过单例模式,重构上述代码。
const createLoginLayer = () => {
const div = document.createElement('div')
div.innerHTML = '我是登录弹窗'
div.style.display = 'none'
console.log(123)
document.body.appendChild(div)
return div
}
const createSingle = (function () {
var instance = {}
return function (fn) {
if (!instance[fn.name]) {
instance[fn.name] = fn.apply(this, arguments)
}
return instance[fn.name]
}
})()
const createIframe = function () {
const iframe = document.createElement('iframe')
document.body.appendChild(iframe)
iframe.style.display = 'none'
return iframe
}
const createSingleLoginLayer = createSingle(createLoginLayer)
const createSingleIframe = createSingle(createIframe)
document.getElementById('loginBtn').onclick = () => {
const loginLayer = createSingleLoginLayer
const iframe = createSingleIframe
loginLayer.style.display = 'block'
iframe.style.display = 'block'
}
经过重构,代码做了以下优化:
createLoginLayer
/ createIframe
的职责和管理单例对象 createSingle
的职责分离,符合单一职责原则;iframe
/ script
等其他标签时,可以直接复用该逻辑。单例模式是一种简单但非常实用的模式,特别是惰性单例技术,在合适的时候才创建对象,并且只创建唯一的一个。更奇妙的是,创建对象和管理单例的职责被分布在两个不同的方法中,这两个方法组合起来才具有单例模式的威力。
当我们计划国庆出去游玩时,在交通方式上,我们可以选择贵而快的飞机、价格中等但稍慢的动车、便宜但超级慢的火车,根据不同的人,选择对应的交通方式,且可以随意更换交通方式,这就是策略模式。
策略模式的定义是,定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。
有一个计算员工年终奖的需求,假设,绩效为 S
的员工年终奖是 4
倍工资,绩效为 A
的员工年终奖是 3
倍工资,绩效为 B
的员工年终奖是 2
倍工资,下面我们来计算员工的年终奖。
var calculateBonus = function(performanceLevel, salary) {
if (performanceLevel === 'S') {
return salary * 4;
}
if (performanceLevel === 'A') {
return salary * 3;
}
if (performanceLevel === 'B') {
return salary * 2;
}
};
calculateBonus('B', 20000); // 输出:40000
calculateBonus( 'S', 6000 ); // 输出:24000
上述代码有以下缺点:
if-else
语句描述逻辑,代码庞大;S
的奖金系数,必须修改 calculateBonus
函数,违反了开放-封闭原则;使用策略模式改良后
const strategies = {
S: salary => {
return salary * 4
},
A: salary => {
return salary * 3
},
B: salary => {
return salary * 2
}
}
const calculateBonus = (level, salary) => {
return strtegies[level](salary "level")
}
console.log(calculateBonus('s', 20000))
console.log(calculateBonus('a', 10000))
可以看到上述代码做了以下改动:
strategies
封装了具体的算法和计算过程(每种绩效的计算规则);calculateBonus
接受请求,把请求委托给策略类 strategies
(员工的绩效和工资;if-else
语句。策略模式使代码可读性更高,易于拓展更多的策略算法。当绩效系数改变,或者绩效等级增加,我们只需要为 strategies
调整或新增算法,符合开放-封闭原则。
当网页上的表单需要校验输入框/复选框等等规则时,如何去实现呢?
现在有一个注册用户的表单需求,在提交表单之前,需要验证以下规则:
使用 if-else
语句判断表单输入是否符合对应规则,如不符合,提示错误原因。
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<form id='registerForm' action="xxx" method="post">
用户名:<input type="text" name="userName">
密码:<input type="text" name="password">
手机号:<input type="text" name="phone">
<button>提交</button>
</form>
<script type="text/javascript">
let registerForm = document.getElementById('registerForm')
registerForm.onsubmit = () => {
if (registerForm.userName.value) {
alert('用户名不能为空')
return false
}
if (registerForm.password.value.length < 6) {
alert('密码长度不能少于6')
return false
}
if (!/(^1[3|5|8][0-9]$)/.test(registerForm.phone.value)) {
alert('手机号码格式不正确')
return false
}
}
</script>
</body>
</html>
上述代码有以下缺点:
onsubmit
函数庞大,包含大量 if-else
语句;onsubmit
缺乏弹性,当有规则需要调整,或者需要新增规则时,需要改动 onsubmit
函数内部,违反开放-封闭原则;使用策略模式重构上述代码。
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<form action="http://xxx.com/register" id="registerForm" method="post">
请输入用户名:
<input type="text" name="userName" />
请输入密码:
<input type="text" name="password" />
请输入手机号码:
<input type="text" name="phoneNumber" />
<button>
提交
</button>
</form>
<script type="text/javascript" src="index.js">
</script>
</body>
</html>
// 表单dom
const registerForm = document.getElementById('registerForm')
// 表单规则
const rules = {
userName: [
{
strategy: 'isNonEmpty',
errorMsg: '用户名不能为空'
},
{
strategy: 'minLength:10',
errorMsg: '用户名长度不能小于10位'
}
],
password: [
{
strategy: 'minLength:6',
errorMsg: '密码长度不能小于6位'
}
],
phoneNumber: [
{
strategy: 'isMobile',
errorMsg: '手机号码格式不正确'
}
]
}
// 策略类
var strategies = {
isNonEmpty: function(value, errorMsg) {
if (value === '') {
return errorMsg;
}
},
minLength: function(value, errorMsg, length) {
console.log(length)
if (value.length < length) {
return errorMsg;
}
},
isMobile: function(value, errorMsg) {
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
}
};
// 验证类
const Validator = function () {
this.cache = []
}
// 添加验证方法
Validator.prototype.add = function ({ dom, rules}) {
rules.forEach(rule => {
const { strategy, errorMsg } = rule
console.log(rule)
const [ strategyName, strategyCondition ] = strategy.split(':')
console.log(strategyName)
const { value } = dom
this.cache.push(strategies[strategyName].bind(dom, value, errorMsg, strategyCondition))
})
}
// 开始验证
Validator.prototype.start = function () {
let errorMsg
this.cache.some(cacheItem => {
const _errorMsg = cacheItem()
if (_errorMsg) {
errorMsg = _errorMsg
return true
} else {
return false
}
})
return errorMsg
}
// 验证函数
const validatorFn = () => {
const validator = new Validator()
console.log(validator.add)
Object.keys(rules).forEach(key => {
console.log(2222222, rules[key])
validator.add({
dom: registerForm[key],
rules: rules[key]
})
})
const errorMsg = validator.start()
return errorMsg
}
// 表单提交
registerForm.onsubmit = () => {
const errorMsg = validatorFn()
if (errorMsg) {
alert(errorMsg)
return false
}
return false
}
上述代码通过 strategies
定义规则算法,通过 Validator
定义验证算法,将规则和算法分离,我们仅仅通过配置的方式就可以完成表单的校验,这些校验规则也可以复用在程序的任何地方,还能作为插件的形式,方便的被移植到其他项目中。
策略模式是一种常用且有效的设计模式,通过上述例子,可以总结出策略模式的一些优点:
Context
拥有执行算法的能力,这也是继承的一种更轻便的代替方案。代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。
代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。
传统做法是小明直接把花送给小白,小白接收到花,代码如下:
const Flower = function () {
return '玫瑰'
}
const xiaoming = {
sendFlower: target => {
const flower = new Flower()
target.receiveFlower(flower)
}
}
const xiaobai = {
receiveFlower: flower => {
console.log('收到花', flower)
}
}
xiaoming.sendFlower(xiaobai)
但是,小明并不认识小白,他想要通过小代,帮他打探小白的情况,在小白心情好的时候送花,这样成功率更高。代码如下:
const Flower = function () {
return '玫瑰'
}
const xiaoming = {
sendFlower: target => {
const flower = new Flower()
target.receiveFlower(flower)
}
}
const xiaodai = {
receiveFlower: flower => {
xiaobai.listenGoodMood().then(() => {
xiaobai.receiveFlower(flower)
})
}
}
const xiaobai = {
receiveFlower: flower => {
console.log('收到花', flower)
},
listenGoodMood: fn => {
return new Promise((reslove, reject) => {
// 10秒后,心情变好
reslove()
})
}
}
xiaoming.sendFlower(xiaodai)
以上,小明通过小代,监听到小白心情的心情变化,选择在小白心情好时送花给小白。不仅如此,小代还可以做以下事情:
图片预加载时一种常见的技术,如果直接给 img 标签节点设置 src 属性,由于图片过大或网络不佳,图片的位置往往有一段时间时空白。
const myImage = (() => {
const imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc: src => {
imgNode.src = src
}
}
})()
myImage.setSrc('https://img30.360buyimg.com/ling/jfs/t1/187775/5/8271/435193/60c8117eE7d79ef41/1d21db2c4dca9a90.png')
通过开发者工具把网速设置为 5kb/s 时,会发现在很长一段时间内,图片位置是空白的。
下面用虚拟代理优化该功能,把加载图片的操作交给代理函数完成,在图片加载时,先用一张loading 图占位,当图片加载成功后,再把它填充进 img 节点。
代码如下:
const myImage = (() => {
const imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc: src => {
imgNode.src = src
}
}
})()
const loadingSrc = '../../../../img/loading.gif'
const imgSrc = 'https://img30.360buyimg.com/ling/jfs/t1/187775/5/8271/435193/60c8117eE7d79ef41/1d21db2c4dca9a90.png'
const proxyImage = (function () {
const img = new Image()
img.onload = () => {
myImage.setSrc(img.src)
}
return {
setSrc: src => {
myImage.setSrc(loadingSrc)
img.src = src
}
}
})()
proxyImage.setSrc(imgSrc)
上述代码有以下优点:
proxyImage
控制了对 MyImage
的访问,在 MyImage
未加载成功之前,使用 loading
图占位;img
节点设置 src
的函数 MyImage
,预加载图片的函数 proxyImage
,都只有一个职责;img
节点设置 src
和预加载图片的功能,被隔离在两个对象里,它们可以各自变化不影响对方。假设我们要实现一个同步文件的功能,通过复选框,当复选框选中的时候,将该复选框对应的 id 传给服务器,告诉服务器需要同步 id 对应的文件。
思考一下,会发现,如果每选中一个复选框,就请求一次接口,假设 1s 内选中了 10 个复选框,那么就要发送 10 次请求。
可以通过虚拟代理来优化上述做法,新增一个代理,帮助复选框发起同步文件的请求,收集在这 1s 内的请求,1s 后再一起把这些文件 id 发送到服务器。
代码如下:
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
<title></title>
</head>
<body>
a <input type="checkbox" value="a" />
b <input type="checkbox" value="b" />
c <input type="checkbox" value="c" />
d <input type="checkbox" value="d" />
<script type="text/javascript" src="index.js">
</script>
</body>
</html>
const synchronousFile = cache => {
console.log('开始同步文件,id为:'+ cache.join('/'))
}
const proxySynchronousFile = (() => {
const cache = []
let timer
return id => {
console.log(id)
cache.push(id)
if (timer) {
return
}
timer = setTimeout(() => {
synchronousFile(cache)
clearTimeout(timer)
timer = null
cache.length = 0
}, 2000)
}
})()
const checkbox = document.getElementsByTagName('input')
Array.from(checkbox).forEach(i => {
console.log(i)
i.onclick = () => {
if (i.checked) {
proxySynchronousFile(i.value)
}
}
})
在列表需要分页时,同一页的数据理论上只需要去后台拉取一次,可以把这些拉取过的数据缓存下来,下次请求时直接使用缓存数据。
使用缓存代理实现上述功能,代码如下:
(async function () {
function getArticle (currentPage, pageSize) {
console.log('getArticle', currentPage, pageSize)
// 模拟一个ajax请求
return new Promise((resolve, reject) => {
resolve({
ok: true,
data: {
list: [],
total: 10,
params: {
currentPage,
pageSize
}
}
})
})
}
const proxyGetArticle = (() => {
const caches = []
return async (currentPage, pageSize) => {
const cache = Array.prototype.join.call([currentPage, pageSize],',')
if (cache in caches) {
return caches[cache]
}
const { data, ok } = await getArticle(currentPage, pageSize)
if (ok) {
caches[cache] = data
}
return caches[cache]
}
})()
// 搜索第一页
await proxyGetArticle(1, 10)
// 搜索第二页
await proxyGetArticle(2, 10)
// 再次搜索第一页
await proxyGetArticle(1, 10)
})()
通过缓存代理,在第二次请求第一页的数据时,直接在缓存数据中拉取,无须再次从服务器请求数据。
上面根据实际场景介绍了虚拟代理和缓存代理的做法。
当我们不方便直接访问某个对象时,找一个代理方法帮我们去访问该对象,这就是代理模式。
可通过 github源码[1] 进行实操练习。
希望本文能对你有所帮助,感谢阅读❤️~
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/KDIsqVzA2XotlLyhsy-K9w
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。