通过前面几篇文章,大家估计对对象的创建有了一个新的认识,在很多大型应用里面,对象会相当复杂,但是其刚创建的时候,并不是这样,而是通过后续的组合,封装,才一步一步完善起来的,从本章节开始,我们一块来学一学,如何把这些对象组合的更合理,从而使想强大的功能。
大家一定绑定过事件,而且一定像下面这样绑定过
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 中适配器的应用,更多的用在对象之间,为了使对象可用,通常我们会将对象进行拆分并重新包装,这就要求我们了解适配对象的内部结构,这正是与外观模式的最大不同。
下一章节,我们来一起弄一个经常出现的问题,面试常考,平常也经常遇到的,跨域问题。
