我们在上节中提到了,我们的代码实现还是存在问题,这个问题在哪里?
这个是需要去结合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的时候,突然有种被打开了新世界的大门的感觉,而且终于可以跟一些设计模式对上号了。果然解决问题的时候,还是要多想想,多思考思考