适配器设计模式在JavaScript中非常有用,在处理跨浏览器兼容问题、整合多个第三方SDK的调用,都可以看到它的身影。
其实在日常开发中,很多时候会不经意间写出符合某种设计模式的代码,毕竟设计模式就是老前辈们总结提炼出来的一些能够帮助提升开发效率的一些模版,源于日常的开发中。
而适配器其实在JavaScript中应该是比较常见的一种了。
在维基百科中,关于适配器模式的定义为:
在软件工程中,适配器模式是一种软件设计模式,允许从另一个接口使用现有类的接口。它通常用于使现有的类与其他类一起工作,而无需修改其源代码。
在生活中最常见的就是电源插头的适配器了,世界各国的插座标准各不相同,如果需要根据各国的标准购买对应的电源插头那未免太过于浪费钱财,如果说自己带着插座,把人家墙敲碎,重新接线,也肯定是不现实的。
所以就会有插头的适配器,用来将某种插头转换成另一种插头,在插座和你的电源之间做中转的这个东西,就是适配器。
而转向到编程中,我个人是这样理解的:
将那些你不愿意看见的脏代码藏起来,你就可以说这是一个适配器
举个日常开发中的例子,我们在做一个微信公众号开发,里边用到了微信的支付模块,经过长时间的联调,终于跑通了整个流程,正当你准备开心的打包上线代码的时候,得到了一个新需求:
我们需要接入支付宝公众号的SDK,也要有支付的流程
为了复用代码,我们可能会在脚本中写下这样的逻辑:
if (platform === 'wechat') {
wx.pay(config)
} else if (platform === 'alipay') {
alipay.pay(config)
}
// 做一些后续的逻辑处理
但是一般来说,各厂的SDK所提供的接口调用方式都会多多少少有些区别, 虽说有些时候文档可能用的是同一份,致敬友商。
所以针对上述的代码可能是这样的:
// 并不是真实的参数配置,仅仅举例使用
const config = {
price: 10,
goodsId: 1
}
// 还有可能返回值的处理方式也不相同
if (platform === 'wechat') {
config.appId = 'XXX'
config.secretKey = 'XXX'
wx.pay(config).then((err, data) => {
if (err) // error
// success
})
} else if (platform === 'alipay') {
config.token = 'XXX'
alipay.pay(config, data => {
// success
}, err => {
// error
})
}
就目前来说,代码接口还算是清晰,只要我们写好注释,这也不是一个太糟糕的代码。
但是生活总是充满了意外,我们又接到了需求需要添加QQ的SDK、美团的SDK、小米的SDK,或者某些银行的SDK。
此时你的代码可能是这样的:
switch (platform) {
case 'wechat':
// 微信的处理逻辑
break
case 'QQ':
// QQ的处理逻辑
break
case 'alipay':
// 支付宝的处理逻辑
break
case 'meituan':
// 美团的处理逻辑
break
case 'xiaomi':
// 小米的处理逻辑
break
}
这已经不是一些注释能够弥补的问题了,这样的代码会变得越来越难维护,各种SDK千奇百怪的调用方式,如果其他人也要做类似的需求,还需要重新写一遍这样的代码,那肯定是很浪费资源的一件事儿。
所以为了保证我们业务逻辑的清晰,同时也为了避免后人重复的踩这个坑,我们会将它进行拆分出来作为一个公共的函数来存在:
找到其中某一个SDK的调用方式或者一个我们约定好的规则作为基准。
我们来告诉调用方,你要怎么怎么做,你能怎样获取返回数据,然后我们在函数内部进行这些各种肮脏的判断:
function pay ({
price,
goodsId
}) {
return new Promise((resolve, reject) => {
const config = {}
switch (platform) {
case 'wechat':
// 微信的处理逻辑
config.price = price
config.goodsId = goodsId
config.appId = 'XXX'
config.secretKey = 'XXX'
wx.pay(config).then((err, data) => {
if (err) return reject(err)
resolve(data)
})
break
case 'QQ':
// QQ的处理逻辑
config.price = price * 100
config.gid = goodsId
config.appId = 'XXX'
config.secretKey = 'XXX'
config.success = resolve
config.error = reject
qq.pay(config)
break
case 'alipay':
// 支付宝的处理逻辑
config.payment = price
config.id = goodsId
config.token = 'XXX'
alipay.pay(config, resolve, reject)
break
}
})
}
这样无论我们在什么环境下,只要我们的适配器支持,就可以按照我们约定好的通用规则进行调用,而具体执行的是什么SDK,则是适配器需要关心的事情:
// run anywhere
await pay({
price: 10,
goodsId: 1
})
对于SDK提供方,仅仅需要知道自己所需要的一些参数,然后按照自己的方式进行数据返回。
对于SDK调用房,仅仅需要我们约定好的通用的参数,以及按照约定的方式进行监听回调处理。
整合多个第三方SDK的任务就交由适配器来做,然后我们将适配器的代码压缩,混淆,放在一个看不见的角落里去,这样的代码逻辑就会变得很清晰了 :)。
适配器大致就是这样的作用,有一点一定要明确,适配器不是银弹, 那些繁琐的代码始终是存在的,只不过你在写业务的时候看不到它罢了 ,眼不见心不烦。
个人觉得,jquery中就有很多适配器的例子,包括最基础的$('selector').on,这个不就是一个很明显的适配器模式么?
一步步的进行降级,并且抹平了一些浏览器之间的差异,让我们可以通过简单的on来进行在主流浏览器中进行事件监听:
// 一个简单的伪代码示例
function on (target, event, callback) {
if (target.addEventListener) {
// 标准的监听事件方式
target.addEventListener(event, callback)
} else if (target.attachEvent) {
// IE低版本的监听方式
target.attachEvent(event, callback)
} else {
// 一些低版本的浏览器监听事件方式
target[`on${event}`] = callback
}
}
或者在Node中的这样的例子更是常见,因为早年是没有Promise的,所以大多数的异步由callback来完成,且有一个约定好的规则,Error-first callback:
const fs = require('fs')
fs.readFile('test.txt', (err, data) => {
if (err) // 处理异常
// 处理正确结果
})
而我们的新功能都采用了async/await的方式来进行,当我们需要复用一些老项目中的功能时,直接去修改老项目的代码肯定是不可行的。
这样的兼容处理需要调用方来做,所以为了让逻辑代码看起来不是太混乱,我们可能会将这样的回调转换为Promise的版本方便我们进行调用:
const fs = require('fs')
function readFile (fileName) {
return new Promise((resolve, reject) => {
fs.readFile(fileName, (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
await readFile('test.txt')
因为前边也提到了,这种Error-first callback是一个约定好的形式,所以我们可以很轻松的实现一个通用的适配器:
function promisify(func) {
return (...args) => new Promise((resolve, reject) => {
func(...args, (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
然后在使用前进行对应的转换就可以用我们预期的方式来执行代码:
const fs = require('fs')
const readFile = promisify(fs.readFile)
await readFile('test.txt')
在Node8中,官方已经实现了类似这样的工具函数:util.promisify
个人观点:所有的设计模式都不是凭空想象出来的,肯定是在开发的过程中,总结提炼出的一些高效的方法,这也就意味着,可能你并不需要在刚开始的时候就去生啃这些各种命名高大上的设计模式。
因为书中所说的场景可能并不全面,也可能针对某些语言,会存在更好的解决办法,所以生搬硬套可能并不会写出有灵魂的代码 :)
纸上得来终觉浅,绝知此事要躬行。 ———— 《冬夜读书示子聿》,陆游
来源:https://www.cnblogs.com/jiasm/archive/2018/09/19/9674222.html
classList是一个DOMTokenList的对象,用于在对元素的添加,删除,以及判断是否存在等操作。以及如何兼容操作
js的原型链,得出了一个看似很简单的结论。对于一个对象上属性的查找是递归的。查找属性会从自身属性(OwnProperty)找起,如果不存在,就查看prototype中的存在不存在。
arguments是什么?在javascript 中有什么样的作用?讲解JavaScript 之arguments的使用总结,包括arguments.callee和arguments.calle属性介绍。
WebSocket是HTML5下一种新的协议,为解决客户端与服务端实时通信而产生的技术。其本质是先通过HTTP/HTTPS协议进行握手后创建一个用于交换数据的TCP连接
HTML文档在浏览器解析后,会成为一种树形结构,我们需要改变它的结构,就需要通过js来对dom节点进行操作。dom节点(Node)通常对应的是一个标题,文本,或者html属性。
javascript中基本类型指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。 引用类型指那些保存在堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。
apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;第一个参数都是this要指向的对象,也就是想指定的上下文;都可以利用后续参数传参;bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。
在js中有句话叫一切皆对象,而几乎所有对象都具有__proto__属性,可称为隐式原型,除了Object.prototype这个对象的__proto__值为null。Js的prototype属性的解释是:返回对象类型原型的引用。每个对象同样也具有prototype属性,除了Function.prototype.bind方法构成的对象外。
Js支持“=”、“==”和“===”的运算符,我们需要理解这些 运算符的区别 ,并在开发中小心使用。它们分别含义是:= 为对象赋值 ,== 表示两个对象toString值相等,=== 表示两个对象类型相同且值相等
js的变量分为2种类型:局部变量和全局变量。主要区别在于:局部变量是指只能在变量被声明的函数内部调用,全局变量在整个代码运行过程中都可以调用。值得注意的js中还可以隐式声明变量,而隐式声明的变量千万不能当做全局变量来使用。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!