前言
通过前面几篇文章,大家估计对对象的创建有了一个新的认识,在很多大型应用里面,对象会相当复杂,但是其刚创建的时候,并不是这样,而是通过后续的组合,封装,才一步一步完善起来的,从本章节开始,我们一块来学一学,如何把这些对象组合的更合理,从而使想强大的功能。
一、外观模式
大家一定绑定过事件,而且一定像下面这样绑定过
document.onclick = fucntion(e) {
e.preventDefault();
if(e.target !== document.getElementById('myInput')) {
hideAlert();
}
}
function hideAlert() {
//隐藏提示框
}
上面方法没问题,功能也可以实现,但是如果团队中有人也给 document 绑定了 onclick 事件的话,那么后面的就会覆盖前面的。
所以上面功能应该使用 addEventListener 来添加监听事件,这样即使添加多个,也只是叠加,不会覆盖。同时还需要注意,老版本 IE 不支持 addEventListener 要改为 attachEvent ,当然,如果有浏览器版本也不支持 addEventListener 的话,还需要另行处理。
作为一名程序员,我们一定要时刻思考,怎么能用最少的成本,做最多的事。
对付这种情况,我们就可以用外观模式来行进封装。
举个例子,去学校食堂吃饭,用餐的人很多,可选择的菜品有很多,还有一群人有选择困难症,这样吃一顿饭就会有很多时间消耗在选择吃什么上,这个时候食堂为了提高效率,推出了套餐,比如宫保鸡丁套餐,可以提供米饭,菜,饮料等。我们不用遍历每一种菜,注释吃什么,饮料喝什么,因为套餐已经定制好了。
在 JS 中,我们可以通过点个“套餐”,来简化复杂的需求。这样就提供了一个高级接口,简化了对复杂底层接口不统一的情况。
//外观模式实现
function addEvent(dom, type, fn) {
//支持 addEventListener的浏览器
if(dom.addEventListener) {
dom.addEventListener(type, fn, false)
//支持 attachEvent的浏览器
} else if(dom.attachEvent) {
dom.attachEvent('on' + type, fn);
//支持 on + '事件名'的浏览器
} else {
dom['on' + type] = fn;
}
}
这样一来我们就可以安心的为元素添加点击事件了
var input = document.getElementById('myinput');
addEvent(input, 'click', function() {
console.log('绑定的第一个事件');
})
addEvent(input, 'click', function() {
console.log('绑定的第二个事件');
})
由上面的实例我们可以看出,外观模式在解决浏览器兼容问题上,应用比较多,那我们可以继续改造上面的代码,对事件参数 event 来进行兼容处理。
//获取事件对象
var getEvent = function(event) {
return event || window.event;
}
//获取元素
var getTarget = function(event) {
return event.target || event.srcElement;
}
//组织默认行为
var preventDefault = function(event) {
var event = getEvent(event);
if(event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
其实外观模式的不光在浏览器兼容方面大放异彩,它的核心思想是简化底层操作,比如代码库中,经常用外观模式来封装多个功能。
var A = {
//通过id获取元素
g: function(id) {
return document.getElementById(id);
},
//设置元素css属性
css: fucntion(id, key, value) {
document.getElementById(id).style[key] = value;
},
//设置元素属性
attr: function(id, key, value) {
document.getElementById(id)[key] = value;
},
html: function(id, html) {
document.getElementById(id)innerHTML = value;
},
//为元素绑定事件
on: function(id, type, fn) {
document.getElementById(id)['on' + type] = fn;
}
};
//通过这个代码库,我们可以简化设置元素的属性设置
A.css('box', 'background', 'red');
A.attr('box', 'className', box);
A.html('box', '新增加的内容');
A.on('box', 'click', function() {
A.css('box', 'width', '500px')
});
二、适配器模式
接下来这个模式,专门解决头疼问题,说到适配器,就不得不提一家公司,苹果,这家公司总喜欢制定新规则,每次新产品发布,都会重组一大部分适配厂商。比如前几年的新款 mac 取消所有 usb 接口,一律改为 type_C ,这就导致所有人都得买个转换器,而官网的转换器 145 一个,还只有一个接口,心疼~
好在绿联应运而生,价格实惠,输出稳定,还能一转多。
不过最近的 iPhone12 充电线,大家自行体会~。
如果有绿联公关部的同学看到这篇文章,请联系我结算一下广告费!
有点跑题了哈,说到我们的适配器模式,这个模式就是上面那个 145 。顾名思义,适配器就是把接口不同的东西,转化成接口相适配的模式。
//这里有一个我们自己写的代码库
var A = {
//通过id获取元素
g: function(id) {
return document.getElementById(id);
},
//为元素绑定事件
on: function(id, type, fn) {
document.getElementById(id)['on' + type] = fn;
}
};
我们如果我们把上面的代码库,适配一下 JQuery ,需要改成下面这样
var A = {
//通过id获取元素
g: function(id) {
return $(id).get(0);
},
//为元素绑定事件
on: function(id, type, fn) {
var dom = typeof id === 'string' ? $('#' + id) : $(id);
dom.on(type, fn);
}
};
上面的过程,就是一个适配器模式,除此之外适配器模式还有很多用途,比如当某个方法需要传多个参数时。
function doSomeThing(name, title, age, color, size, prize) {}
上面你方法要记住这么多参数,还不能打乱顺序,比较困难,这时候用到适配器模式如下:
/**
*obj.name: name
*obj.title: title
*obj.age: age
*obj.color: color
*obj.size: size
*obj.prize: prize
***/
function doSomeThing(obj) {}
上面这种格式在使用 TypeScript 做数据类型校验时极为方便。
当然如果对参数格式要求不严格,某些参数没有时自动添加一个默认值,那么就可以按照下面的方式
function doSomeThing(obj) {
var _adapter = {
name: 'Murphy',
title: '设计模式',
age: '24',
color: 'pink',
size: 100,
prize: 50
}
for(var i in _adapter) {
_adapter[i] = obj[i] || _adapter[i];
}
//doSomthing
}
不过适配器模式真正的用武之地是在前后端分离上,它让前端脱离了后台参数的限制,如果后端因架构改变导致数据结构发生变化,那我们只要写个适配器就可以了。
function ajaxAdapter(data) {
//处理数据并返回新数据
return [data['key1'], data['key2'], data['key3']]
}
$.ajax({
url: 'someAdress.php',
success: function(data, status) {
if(data) {
//使用适配后的数据--返回的对象
doSomething(ajaxAdapter(data));
}
}
});
JavaScirpt 中适配器的应用,更多的用在对象之间,为了使对象可用,通常我们会将对象进行拆分并重新包装,这就要求我们了解适配对象的内部结构,这正是与外观模式的最大不同。
下一章节,我们来一起弄一个经常出现的问题,面试常考,平常也经常遇到的,跨域问题。