手记

一起扫荡JavaScript(十一)—— Proxy 与 JavaScript 中的 AOP

  我们知道,Vue中双向绑定原理的实现,核心是改写了get、set方法,往其内部注入脚本,使得程序的读与写过程中执行了除其本身读与写之外其他逻辑,而这个属性重定义的过程Vue2.0及以前采用的是Object.defineProperty()。Vue3.0则使用Proxy来实现这一过程。而本次我们一起来聊聊Proxy吧。

  Proxy,顾名思义,代理,就像同样做一件事情,你亲自做也是做,委托给另外的人帮你做也是做,这就是代理,代替你打理。而且,它还可以帮我们做一些其他的事情,比如拦截器,校验器等。

  看一下下面这段代码:


const targetObj = {

    name: 'dorsey',
    age: 25
}

const obj = new Proxy(targetObj, {

    get(target, key) {

        console.log('这里是重写之后的get方法,它已经拿到了需要读取的属性:' + key);

        return target[key];
    }
});

console.log(targetObj.name);    //  dorsey

console.log('===================');

console.log(obj.name);          //  这里是重写之后的get方法,它已经拿到了需要读取的属性:name

  新的代理对象改写了get方法,这样当需要从读值这里入手做一些改变时,就可以按照你的需求添加你所需要的逻辑。
  就像下面的数据校验与拦截:


const anotherObj = new Proxy(targetObj, {

    set(target, key, value) {

        if(key === 'age' && 'number' !== typeof value) throw 'age属性不是Number类型, 请重新设置'
        
        target[key] = value;
    }
});

targetObj.age = '100';
console.log(targetObj.age);     //  100

console.log('===================');

anotherObj.age = '100';  //  Uncaught age属性不是Number类型, 请重新设置
console.log(anotherObj.age);

  你可以发现,Proxy主要做的事情就是在源对象的基础上,加入一些自定义行为,但它又只是做了代理,本质上代理虽然能改变原对象,也能将结果正常反馈给源对象,但大多数情况下,代理不会去改变原对象本身的函数或值,也就是源对象还是源对象,但它又因为多了一层代理,可以在不改变源对象的情况下做更多的事情。而这,在我看来,就是一个AOP,就是一种面向切面的编程。

  什么意思?或者说简单的需要怎么理解这个AOP,怎么理解面向切面?

  切面,看看下图(这是切线,但实在画不出一个球,理解就好啦):

  切面切面,从远方而来,触之即走,无痕无留,简单的就是说,如何在源程序(原类,原对象)不改变的前提下,统一化加入一些自定义功能?

  那为什么会有这样的需要?这其实很常见,简单的用户输入输出,传递至后台,要校验吧?某个用户具备哪些权限,也需要校验吧?为便于以后定位问题所在,要有日志吧?但是,对于源对象而言,这些日志,校验实际上跟他们无关,尽管你可以直接写在里面,但这会使程序很臃肿,得不偿失,而这样万花丛中过,片叶不沾身的理念就此应运而生,它就是 —— AOP, 也就是面向切面编程。

  这个AOP能不能通过JavaScript来实现呢?当然可以。

  简单的实现如下:


function factory (fn, _before, _after) {

    return function () {

        _before && 'function' === typeof _before && _before.call(this);

        fn.call(this);
    
        _after && 'function' === typeof _after && _after.call(this);
    }
}

//  原函数
function hello () {

    this.a = 'dorsey';

    console.log('你好啊');
}

//  前置通知
function beforeFn () {

    console.log('前置通知');

    console.log(this.a);
}


//  后置通知
function afterFn () {

    console.log('后置通知');

    console.log(this.a);
}

let hello1 = factory(hello, beforeFn, afterFn);

hello();

console.log('=========================');

hello1();

  原来的hello函数还是原来的hello函数,在必要的时候,可以通过这个添加一些前置任务(比如校验、拦截)或者后置任务(比如日志),这样既达到了功能调整,又不会使代码非常冗余和高耦合,正如AOP的理念 —— 万花从中过,片叶不沾身。

2人推荐
随时随地看视频
慕课网APP