这个是需要去结合vue来看的,因为我写这个东西的时候就是希望在vue中使用,如果最后不能再vue中使用 那么就没有意义了。
而在vue中的使用的实际使用的时候给我出了一个难题,那就是vue的this的执行问题。我们知道vue对自己的实例是做了一层代理的。 我们在创建一个vue实例的时候,走的是data:{属性} 但是实际上我们可以通过this.属性 拿到这个属性值。这就说明了vue的实例对data做了一次代理,使得我们可以快速的拿到值。 因此,我们在使用写methods中的方法的时候,是不能使用箭头函数的,因为如果使用了箭头函数,那么this就会指向当前的环境,最终导致我们获取不到数据。
而在使用AOP的时候,我们并没有去处理this的执行问题,因此如果我们在vue中使用了我们的Action
<script> let strategy={ strategy1(text='strategy1'){ console.log(text); // eslint-disable-line }, strategy2(text='strategy2'){ console.log(text); // eslint-disable-line }, strategy3(text='strategy3'){ console.log(text); // eslint-disable-line }, } async function actuator (obj) { if (obj) { if (typeof obj === 'function') { await obj() } if (obj instanceof Array) { for (let fn of obj) { if (typeof fn === 'function') { await fn() } } } } } function Action (actions) { return async function () { try { // 执行before await actuator(actions.before) // 执行 service await actuator(actions.service) // 执行after await actuator(actions.after) } catch (err) { console.error(err);// eslint-disable-line actions.error(err) } } } function operateAction (actions, params) { let defaultParams = { p1: '默认p1', p2: '默认p2', p3: '默认p3', } Object.assign(defaultParams, params) let obj = { before:[strategy.strategy1(defaultParams.p1)], service:actions, after:strategy.strategy3.bind(null,defaultParams.p3), error:strategy.strategy3.bind(null,'错误') } if (actions instanceof Function) { obj.service = actions } else if (actions instanceof Object) { obj = actions } return Object.assign({}, obj) } export default { name: 'HelloWorld', props: { msg: String }, data: function () { return { test: 'test' }; }, methods: { alert: Action(operateAction(()=>{ console.log(this.test); // eslint-disable-line })) } } </script>以上代码中 最终会走到错误处理阶段 错误代码为 TypeError: Cannot read property ‘data’ of undefined
要解决这个问题其实很简单
alert () { Action(operateAction( ()=> { console.log(this.test); // eslint-disable-line }))() }我们可以再包裹一层。 但是 这样有点丑。。。虽然他可以很好的进行问题的处理,但是我还是觉得这种做法很丑。。。 那么我们换第二种方式
async function actuator (obj) { if (obj) { if (typeof obj === 'function') { await obj.call(this) } if (obj instanceof Array) { for (let fn of obj) { if (typeof fn === 'function') { await fn.call(this) } } } } } function Action (actions) { return async function () { try { // 执行before await actuator(actions.before) // 执行 service await actuator.call(this,actions.service) // 执行after await actuator(actions.after) } catch (err) { console.error(err);// eslint-disable-line actions.error(err) } } }使用call来强行绑定this,然后再取消箭头函数
alert: Action(operateAction(function (){ console.log(this.test); // eslint-disable-line }))这样就可以做到一种比较优美的使用了。
那么 还有没有更加优雅的实现方式呢? 我们想想Java中的AOP是怎么做的? 是通过@ 这个的注解来实现的 同时 我们观察我们的代码,很容易就会想到一种设计模式 装饰器模式 而在ES7 ES8中 装饰器也作为一个提案出现了,也有了 @ 这种操作。 但是我们研究了下文档 发现了问题所在,装饰器针对的 只是类已经类的方法 而vue中 很明显组件不是类的使用 没有class关键字。 这么说我们就不能用最优雅的方式来实现了吗? 不见得 因为有ts vue的ts的写法 就大部分的都是用类来实现的了 那么我们给出一个例子
function log(target: any, name: String,descriptor:any) { let oldValue:Function=descriptor.value descriptor.value=function(){ alert('log') console.log(target); oldValue.call(this) } } @Component export default class HelloWorld extends Vue { @Prop() private msg!: string test: String = 'test' @log alert() { alert(this.test); } mounted() { // this.alert() console.log('mounted'); } } </script>这里给出了的是一个装饰器的例子,并没有在使用AOP了,意义不是很大,我们通过装饰器的写法 那么就可以把我们的AOP代码写成
@action() service这个样子的形式 相较回调函数,看起来就多少舒服了一点了。
不过鉴于decorator现在还不成熟 而且也不能在对象中使用(经过测试,部分babel实现可以在对象中使用,但是如果你使用的是vscode就会报错~)所以还是使用回调函数的形式吧。
但是现在在decorator的提案上,已经有人给了一个issue 希望在对象字面值中使用装饰器,考虑到这个需求应该是很合理的,所以我觉得 这个方案被纳入标准还是可以期待一下的。 添加对对象字面值的支持 可以看下上面这个链接
那么到这里,我们的AOP就算是走完了,其实在一开始思考这个问题的时候,看了很多的资料,很多都是直接在function上面实现after这些代码,总感觉不是很好的样子,然后经过跟人的讨论种种,才最终定下来一个小的解决方案吧 但是从中却学到了很多的东西,在最后看到ts的时候,突然有种被打开了新世界的大门的感觉,而且终于可以跟一些设计模式对上号了。果然解决问题的时候,还是要多想想,多思考思考
