函数分流
- window.onresize
- mousemove
- 上传进度
示例:单倍群页面需要根据窗口变化重新计算节点位置
throttle(fn, interval) {
let timer, firstTime
return () => {
const args = arguments
if (firstTime) {
fn(args)
return (firstTime = false)
}
if (!timer) {
timer = setTimeout(() => {
clearTimeout(timer)
timer = null
fn(args)
}, interval || 300)
}
return false
}
}
单例模式
- dialog
单例模式的核心是确保只有一个实例, 并提供全局访问。线程池,全局缓存,window,
在ES6中,export出实例即是单例
// 代理类
var ProxySingletonVreateDiv = (function() {
var instance
return function(html) {
if (!instance) {
instance = new CreateDiv(html)
}
return instance
}
})()
var CreateDiv = (function() {
var CreateDiv = function(html) {
this.html = html
this.init()
}
CreateDiv.prototype.init = function() {
var div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
return CreateDiv
})()
// a === b
var a = new ProxySingletonVreateDiv('1')
var b = new ProxySingletonVreateDiv('2')
策略模式
- 不同动画算法:ease, linear, ease-in, ease-out
- 购物车活动算法
- 表单验证
一个功能有多种方案,算法灵活。一系列被封装好的算法,以及接受委托的环境类。对于js来说,更加简单直接的算法是把不同的策略算法直接定义为函数
<form action="http:// xxx.com/register" id="registerForm" method="post">
<input type="text" name="userName" placeholder="用户名" />
<input type="text" name="password" placeholder="密码" />
<input type="text" name="phoneNumber" placeholder="电话" />
<button>提交</button>
</form>
<script>
var registerForm = document.getElementById('registerForm')
// 业务规则
var strategies = {
isNonEmpty: function(value, errorMsg) {
if (value === '') {
return errorMsg;
}
},
minLength: function(value, length, errorMsg) {
if (value.length < length) {
return errorMsg;
}
},
isMobile: function(value, errorMsg) {
if (!/^\d{6,16}$/.test(value)) {
return errorMsg;
}
}
}
var Validator = function () {
this.cache = []; // 保存所有的验证
}
Validator.prototype.add = function(dom, rule, errorMsg) {
var ary = rule.split(':')
this.cache.push(function() {
var strategy = ary.shift()
ary.unshift(dom.value)
ary.push(errorMsg)
return strategies[strategy].apply(dom, ary)
})
}
Validator.prototype.start = function() {
for(var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
var msg = validatorFunc();
if(msg) {
return msg;
}
}
}
var validatorFunc = function() {
var validator = new Validator();
validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空')
validator.add(registerForm.password, 'minLength:6', '长度不能为空')
validator.add(registerForm.phoneNumber, 'isMobile', 'mobile不能为空')
var errorMsg = validator.start()
return errorMsg
}
registerForm.onsubmit = function() {
var errorMsg = validatorFunc();
if(errorMsg) {
alert(errorMsg)
return false;
}
}
代理模式
- 虚拟代理图片预加载
- 权限管理
- loading状态 遵循单一职责原则,不影响主体功能
- ajax异步请求分页数据
var User = {
post: function () {
console.log('发文章');
},
remove: function () {
console.log('删除文章');
}
}
var ProxyUser = {
post: function () {
if (有权限) {
User.post();
}
console.log('没有权限发文章');
},
remove: funtion () {
if (有权限) {
User.remove();
}
console.log('没有权限删除文章');
}
}
var Mod = {
init: function (data) {
// 主逻辑
}
}
var ProxyLoading = {
init: function (mod) {
showLoading();
if (请求成功) {
hideLoading();
mod.init(data);
} else {
hideLoading();
}
}
}
ProxyLoading.init(Mod);
/* =============== ajax异步请求分页数据 =============== */
var getPageContext = function(pageId) {
var result;
ajax({
url: "/test/index.php",
method: "get",
dataType: "json",
data: {
id: pageId
},
success: function(response) {
result = response;
}
});
return result;
};
/* =============== 缓存代理类 =============== */
var getPageContextProxy = (function() {
var cache = {}; // 缓存计算结果
return function() {
var args = Array.prototype.join.call(arguments, ",");
if(args in cache) {
return cache[args];
}
return cache[args] = getPageContext.apply(this, arguments);
}
})();
/* =============== 客户端实现 =============== */
getPageContextProxy(1); // 向服务器请求第1页数据
getPageContextProxy(2); // 向服务器请求第2页数据
getPageContextProxy(1); // 从缓存中读取第1页数据
装饰者模式
- 数据上报
- 插件式的表单验证
var username = document.getElementById('username'),
password = document.getElementById('password'),
submitBtn = document.getElementById('submitBtn');
Function.prototype.before = function(beforefn){
var _self = this;
return function(){
if(beforefn.apply(this,arguments) === false){
//beforefn返回false的情况下直接return,不再执行后面的原函数
return;
}
return _self.apply(this,arguments);
}
}
var validata = function(){
if(username.value === ''){
return alert('用户名不能为空');
}
if(password.value === ''){
return alert('密码不能为空');
}
}
var formSubmit = function(){
var param = {
username:username.value,
password:password.value
}
ajax('http:xxx.com',param); //ajax具体实现略
}
formSubmit = formSubmit.before(validata);
submitBtn.onclick = function(){
formSubmit()
}
代理模式和装饰者模式区别意图和目的,代理模式是在为主体内容提供一些额外的访问主体的方式,权限,保留主体功能的独立性。而装饰者模式为对象加入动态的行为,这种行为是不确定的,并且是会添加额外的功能的。
发布订阅模式 & 中介者模式
区别
中介者模式涉及两个概念,中介和客户。它解决的是客户之间消息传递的问题。中介者的场景,类似于扣扣群或者中介所。多对多
观察者模式也涉及两个概念,观察者和目标。它解决的是观察者和众多目标之前通信的问题,而不是目标之间通信的问题。观察者的场景类似于,手机应用给顾客推送消息。注意:这里的手机应用是目标,顾客是观察者。一对多
- 购物车
// dom结构
<div class="box">
<ul id="showList">
<li class="good" data-id="good1">
<p class="name">商品1</p>
<p>单价:<span class="singlePrice">5</span></p>
<div>
<button class="min">-</button>
<span class="singleNum">0</span>
<button class="add">+</button>
</div>
</li>
<li class="good" data-id="good2">
<p class="name">商品2</p>
<p>单价:<span class="singlePrice">10</span></p>
<div>
<button class="min">-</button>
<span class="singleNum">0</span>
<button class="add">+</button>
</div>
</li>
<li class="good" data-id="good3">
<p class="name">商品3</p>
<p>单价:<span class="singlePrice">1</span></p>
<div>
<button class="min">-</button>
<span class="singleNum">0</span>
<button class="add">+</button>
</div>
</li>
</ul>
<div class="totalBox">
结算栏:
<ul id="cartListBox">
<!-- <li class="good" data-id="good1">
<button class="min">-</button>
<span class="singleNum">0</span>
<button class="add">+</button>
</li> -->
</ul>
一共花费<span id="totalPrice">0</span>元;
一共<span id="totalNum">0</span>件;
</div>
</div>
- 第一版本,就是很普通的流水账操作
- 第二版,通过一个mediator中介者,对外暴露add和min两个接口,只需要外部按钮绑定class,以及提供data-id属性,就可以实现多个展示栏的互相通信。为什么要选择中介者模式,也是因为,多个列表之间的操作,属于多个对象之间的通信,而中介者模式就是解决对象之间需要互相通信带来的耦合性。这样封装后,还是会有一些问题,如果还会有更多的列表对象需要加入通信,就需要增加另外一个流程,并且需要在调用更新方法的地方添加增加的操作流程方法,这样会使得这个mediator对象越来越臃肿,并且改变原有代码具有侵入性
var mediator = (function () {
// 原数据
var goods = [{
id: 'good1',
name: '商品1',
singlePrice: 5,
singleNum: 0,
}, {
id: 'good2',
name: '商品2',
singlePrice: 10,
singleNum: 0,
}, {
id: 'good3',
name: '商品3',
singlePrice: 1,
singleNum: 0,
}]
// 购物车列表
var accountsList = []
// 结算栏
var total = {
num: 0,
price: 0,
}
var _initTotal = function () {
total.price = 0
total.num = 0
}
var _findGoodIndex = function (id) {
for (var i = 0; i < goods.length; i++) {
if (goods[i].id === id) {
return i
}
}
return null
}
var _findGood = function (id) {
for (var i = 0; i < goods.length; i++) {
if (goods[i].id === id) {
return goods[i]
}
}
return null
}
// 更新商品栏list
var _updateList = function (index, type) {
if (type === 'add') {
goods[index].singleNum += 1
} else {
goods[index].singleNum = goods[index].singleNum - 1 <= 0 ? 0 : goods[index].singleNum - 1
}
}
// 更新结算栏list
var _updateAccountList = function (index, type) {
if (type === 'add') {
for (var i = 0; i < accountsList.length; i++) {
// 购物车已存在商品
if (accountsList[i].id === goods[index].id) {
accountsList[i].singleNum += 1
return
}
}
// 商品第一次添加
accountsList.push($.extend({}, goods[index]))
} else {
for (var i = 0; i < accountsList.length; i++) {
// 购物车已存在商品
if (accountsList[i].id === goods[index].id) {
// 移除购物车
if (accountsList[i].singleNum - 1 <= 0) {
accountsList.splice(i, 1)
} else {
accountsList[i].singleNum -= 1
}
return
}
}
}
}
// 计算总量
var _updateTotalData = function () {
_initTotal()
for (var i = 0; i < goods.length; i++) {
if (!goods[i].singleNum) continue // 优化:商品数量为0,不计算
total.num += goods[i].singleNum
total.price += goods[i].singleNum * goods[i].singlePrice
}
}
// 更新对应list dom
var _updateItemDom = function (index) {
var id = goods[index].id,
num = goods[index].singleNum
$(`#showList li[data-id=${id}] .singleNum`).text(num)
}
var _updataAccountItemDom = function (id) {
var item = _findGood(id)
if ($(`#cartListBox li[data-id=${id}]`).length && item.singleNum) {
$(`#cartListBox li[data-id=${id}] .singleNum`).text(item.singleNum)
} else if (item.singleNum) {
var content =
`<li class="good" data-id=${item.id}>
<span>${item.name}</span>
<button class="min">-</button>
<span class="singleNum">${item.singleNum}</span>
<button class="add">+</button>
</li>`
$('#cartListBox').append(content)
} else {
$(`#cartListBox li[data-id=${id}]`).remove()
}
}
var _updateTotalDom = function () {
$('#totalPrice').text(total.price)
$('#totalNum').text(total.num)
}
// 组合模式 ?可能这里会有一点过度封装, 但是为了大概演示一下, 也是为了思考后续的优化方式(引入发布订阅模式)
// 更新商品栏
var _updateListProcess = function (index, type) {
_updateList(index, type)
_updateItemDom(index)
}
// 更新结算栏
var _updateAccountListProcess = function (index, type, id) {
_updateAccountList(index, type)
_updataAccountItemDom(id)
}
// 更新最终
var _updateTotalProcess = function () {
_updateTotalData()
_updateTotalDom()
}
var _process = function (index, id, type) {
_updateListProcess(index, type)
_updateAccountListProcess(index, type, id)
_updateTotalProcess()
}
return {
// + 增加商品
add: function (id) {
var targetIndex = _findGoodIndex(id)
_process(targetIndex, id, 'add')
},
// - 减少商品
min: function (id) {
var targetIndex = _findGoodIndex(id)
_process(targetIndex, id, 'min')
},
}
})()
var cart = $('.box')
cart.on('click', '.add', function () {
mediator.add($(this).parents('.good').attr('data-id'))
})
cart.on('click', '.min', function () {
mediator.min($(this).parents('.good').attr('data-id'))
})
第三版, 加入了发布订阅模式,每一个需要改变的对象都 订阅了 chang事件(add,min事件也可以),中介者对象暴露每个列表对象需要做的操作,这样虽然每次增加新的列表对象需要在中介者中添加对应的操作属性,但是,却不会侵入原本有的代码
var event = {
clientList: [],
listen: function(key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = []
}
this.clientList[key].push(fn)
},
trigger: function() {
var key = Array.prototype.shift.call(arguments),
fns = this.clientList[key]
if (!fns || fns.length === 0) {
return false
}
for (var i = 0; i < fns.length; i++) {
fns[i].apply(this, arguments)
}
},
}
var installEvent = function(obj) {
for (var i in event) {
obj[i] = event[i]
}
}
var mediator = (function() {
......
return {
// 更新商品栏
_updateListProcess: function(id, type) {
var targetIndex = _findGoodIndex(id)
_updateList(targetIndex, type)
_updateItemDom(targetIndex)
},
// 更新结算栏
_updateAccountListProcess: function(id, type) {
var targetIndex = _findGoodIndex(id)
_updateAccountList(targetIndex, type)
_updataAccountItemDom(id)
},
// 更新最终
_updateTotalProcess: function() {
_updateTotalData()
_updateTotalDom()
},
}
})()
installEvent(mediator)
var list1 = (function() {
mediator.listen('change', function(id, type) {
mediator._updateListProcess(id, type)
})
})()
var list2 = (function() {
mediator.listen('change', function(id, type) {
mediator._updateAccountListProcess(id, type)
})
})()
var list3 = (function() {
mediator.listen('change', function() {
mediator._updateTotalProcess()
})
})()
var cart = $('.box')
// 触发事件
cart.on('click', '.add', function() {
mediator.trigger('change', $(this).parents('.good').attr('data-id'), 'add')
})
cart.on('click', '.min', function() {
mediator.trigger('change', $(this).parents('.good').attr('data-id'), 'min')
})
享元模式
职责链模式
职责链最大的优点是解耦了请求发送者和N个接受者之间的复杂关系,在维护充斥着条件分支语句的巨大函数,每一个分支都会有不同的处理方式,如果只维护这样一个函数,在后续维护中函数会越来越庞大以及难以理解。使用职责链模式后,每个节点对象都可以很灵活的拆分重组,增加删除一个节点对其他的节点不会造成影响。
因为职责链中多了一些节点的对象,可能在某些请求传递过程中,有些节点并没有起到实质性作用,所以要避免过长的职责链带来性能损耗。
var order500 = function(orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500定金,100优惠券')
} else {
return 'nextSuccessor'
}
}
var order200 = function(orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log('200定金,50优惠券')
} else {
return 'nextSuccessor'
}
}
var orderNormal = function(orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买')
} else {
console.log('库存不足')
}
}
var Chain = function(fn) {
this.fn = fn
this.successor = null
}
Chain.prototype.setNextSuccessor = function(successor) {
return this.successor = successor
}
Chain.prototype.passRequest = function() {
var ret = this.fn.apply(this, arguments)
if(ret === 'nextSuccessor') {
return this.successor && this.successor.passRequest.apply(this.successor, arguments)
}
return ret
}
var chainOrder500 = new Chain(order500)
var chainOrder200 = new Chain(order200)
var chainOrderNormal = new Chain(orderNormal)
chainOrder500.setNextSuccessor(chainOrder200)
chainOrder200.setNextSuccessor(chainOrderNormal)
chainOrder500.passRequest(1, true, 500)
chainOrder500.passRequest(2, true, 500)
chainOrder500.passRequest(3, true, 500)
chainOrder500.passRequest(1, false, 0)
适配器模式
- 参数适配
- 数据适配
- 库的适配,接入第三方的sdk,比如第三方数据采集平台切换
// a平台: _hmt.push(['_trackEvent', 'web', 'page_enter', 'position', // 'index.html']);
// b平台:sa.track(eventName, { attrName: value })
// 适配
let _hmt = {
push: (arr) {
const [eventName, attrName, value] = [...arr.splice(2)];
let attrObj = {
[attrName]: value
};
sa.track(eventName, attrObj);
}
}
但是适配器模式本质上是一个亡羊补牢的模式,它解决的是
现存的两个接口之间不兼容
的问题,不应该在软件的初期开发阶段就使用该模式;如果在设计之初我们就能够统筹的规划好接口的一致性,那么适配器就应该尽量减少使用。