适配器模式(Adapter):将一个类的接口转换成客户但愿的另一个接口,使得本来因为接口不兼容而不能一块儿工做的那些类能够一块儿工做。
在生活中,咱们有许多的适配器,例如iPhone7之后的耳机接口从3.5mm圆孔接口更改为为了苹果专属的 lightning接口。许多人之前的圆孔耳机就须要下面的一个适配器,才可以在自个儿新买的iPhone上面听歌。html
在前端开发中,咱们可能会碰见这样的场景:当咱们试图调用某个模块或者对象的接口时,却发现这个接口的格式不符合咱们的需求。这时有两种解决办法:第一种是修改原来的接口实现,但若是原来的代码很复杂,例如是一个库或框架,更改原代码就显得很不现实了。因此这时就须要使用今天所讲的第二种办法:建立一个适配器,将原接口转换为客户但愿的另外一个接口,客户只须要使用适配器便可。前端
对于只有JavaScript这一门语言经验的前端开发来讲,可能对于接口的概念比较陌生。建议参考阅读TypeScript-接口的文档来更好的理解接口。git
在前端项目中,适配器模式的使用场景通常有如下三种状况:库的适配、参数的适配和数据的适配。下面我将以我在项目中的实际例子来讲明。web
项目上线前一般会要求前端开发者在页面中会接入统计网页数据用的SDK,这些SDK可以采集用户的信息和网页行生成可视化的图表和表格,来帮助网站运营人员和产品经理更好的根据用户行为来提高网页质量。咱们来看一下适配器在接入采集数据的库时的使用场景:ajax
目前国内作得比较好的数据分析网站有百度统计、神策数据、友盟等。在一个你作的电商类网站 项目上线前,你的产品经理要求你接入了百度的代码用于数据采集,并在几十个涉及用户操做的地方进行了埋点。百度统计提供的埋点接口格式以下:后端
_hmt.push(['_trackEvent', category, action, opt_label,opt_value]);
按照产品经理的要求,你根据上面的格式将埋点代码写到了页面的多个地方:设计模式
//index.html _hmt.push(['_trackEvent', 'web', 'page_enter', 'position', 'index.html']); //product-detail.html _hmt.push(['_trackEvent', 'web', 'page_enter', 'position', 'product-detail.html']); _hmt.push(['_trackEvent', 'web', 'product_detail_view', 'product_id', productId]); _hmt.push(['_trackEvent', 'web', 'add-product-chart', 'product_id', productId]); //...还有几十个页面
过了几个月以后,该电商网站发展速度很快,运营人员感受到百度统计提供的采集数据在已经没法知足当前网站的规模。运营人员和产品经理商量后决定,数据采集平台须要从百度统计切换到神策数据,神策数据提供的埋点接口格式以下:服务器
sa.track(eventName, { attrName: value })
接口的规则不一样,就意味着你须要将几十个百度统计的_htm.push
接口更改为为神策提供的sa.track
接口。其实不用这么麻烦,写一个适配器就能够完成全部埋点事件的迁移:网络
//app.js let _hmt = { push: (arr) { const [eventName, attrName, value] = [...arr.splice(2)]; let attrObj = { [attrName]: value }; sa.track(eventName, attrObj); } }
经过分析比较百度统计的接口和神策的接口,能够知道在神策中只须要传入三个参数,eventName
对应的是百度统计接口中的action
, attrName
对应的是百度统计接口中的opt_label
, value
对应的是百度统计接口中的opt_value
; 删除了百度统计的SDK后,SDK所提供的_htm
这个全局变量也就不存在了,咱们能够利用该变量名作适配器,在push
方法获取sa.track
所须要的三个参数并调用sa.track
便可。app
有的状况下一个方法可能须要传入多个参数,例如在SDK
这个类中有一个phoneStatus
,须要传入五个参数用于接收手机的相关信息:
class SDK { phoneStatus(brand, os, carrier, language, network) { //dosomething..... } }
一般在传入的参数大于3的时候,咱们就能够考虑将参数合并为一个对象的形式,就像咱们$.ajax
的作法同样。下面咱们能够将phoneStatus
的参数接口定义以下(String
表明参数类型,?:
表明可选项)
{ brand: String os: String carrier:? String language:? String network:? String }
能够看出,carrier
、language
,network
这三个属性不是必须传入的,它们在方法内部可能被设置一些默认值。因此这个时候咱们就能够在方法内部采用适配器来适配这个参数对象。
class SDK { phoneStatus(config) { let defaultConfig = { brand: null, //手机品牌 os: null, //系统类型: Andoird或 iOS carrier: 'china-mobile', //运营商,默认中国移动 language: 'zh', //语言类型,默认中文 network: 'wifi', //网络类型,默认wifi } //参数适配 for( let i in config) { defaultConfig[i] = config[i] || defaultConfig[i]; } //dosomething..... } }
数据的适配在前端中是最为常见的场景,这时适配器在解决先后端的数据依赖上有着重要的意义。一般服务器端传递的数据和咱们前端须要使用的数据格式是不一致的,特别是在在使用一些UI框架时,框架所规定的数据有着固定的格式。因此,这个时候咱们就须要对后端的数据格式进行适配。
例如网页中有一个使用Echarts折线图对网站每周的uv
,一般后端返回的数据格式以下所示:
[ { "day": "周一", "uv": 6300 }, { "day": "周二", "uv": 7100 }, { "day": "周三", "uv": 4300 }, { "day": "周四", "uv": 3300 }, { "day": "周五", "uv": 8300 }, { "day": "周六", "uv": 9300 }, { "day": "周日", "uv": 11300 } ]
可是Echarts须要的x轴的数据格式和坐标点的数据是长下面这样的:
["周二", "周二", "周三", "周四", "周五", "周六", "周日"] //x轴的数据 [6300. 7100, 4300, 3300, 8300, 9300, 11300] //坐标点的数据
因此这是咱们就可使用一个适配器,将后端的返回数据作适配:
//x轴适配器 function echartXAxisAdapter(res) { return res.map(item => item.day); } //坐标点适配器 function echartDataAdapter(res) { return res.map(item => item.uv); }
适配器模式在JS中的使用场景不少,在参数的适配上,有许多库和框架都使用适配器模式;数据的适配在解决先后端数据依赖上十分重要。可是适配器模式本质上是一个亡羊补牢的模式,它解决的是现存的两个接口之间不兼容的问题,你不该该在软件的初期开发阶段就使用该模式;若是在设计之初咱们就可以统筹的规划好接口的一致性,那么适配器就应该尽可能减小使用。