proxy - 详解

128 阅读3分钟

是什么:

Proxy 用于修改某些操作的默认行为,是在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

为什么:

使用 Proxy 的核心优点是可以交由它来处理一些非核心逻辑(如:读取或设置对象的某些属性前记录日志;设置对象的某些属性值前,需要验证;某些属性的访问控制等)。 从而可以让对象只需关注于核心逻辑,达到关注点分离,降低对象复杂度等目的。

proxy 模式一般可以使用在当你想要:拦截或者控制对一个对象的访问时,通过掩盖程序或隐藏逻辑来降低方法/类的复杂度,阻止没有经过验证/准备的重资源操作。

怎么做:

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。

var proxy = new Proxy(target, handler);

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

Proxy 实例的方法

1 get()

get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。

let person = {
    name: 'lxf'
}

let proxyPersonGet = new Proxy(person, {
    get: function (target, property) {
        console.log(property);
        return target[property]
    }
})

let child = Object.create(proxyPersonGet);

console.log(proxyPersonGet.name) // name lxf
console.log(child.name) // name lxf

get方法可以继承。上面代码中,拦截操作定义在Prototype对象上面,所以如果读取obj对象继承的属性时,拦截会生效。

2 set()

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

let proxyPersonSet = new Proxy({}, {
    set: function (obj, prop, value) {
        console.log(prop)
        if (prop === 'age') {
            if(value <= 200){
                obj[prop] = value;
            }else{
                console.log('年龄不正常!');
            }
        }else{
            obj[prop] = value;
        }
    }
})

proxyPersonSet.age = 210; //age 年龄不正常!

上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新 DOM。

3 apply()

apply方法拦截函数的调用、call和apply操作。
apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。

let target = function () {
    return 'I am target!'
}
let handler = {
    apply: function () {
        return "I am proxy!"
    }
}
console.log(target()); //I am target!
let p = new Proxy(target, handler);
console.log(p()); //I am proxy!

4 has()

has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。
has方法可以接受两个参数,分别是目标对象、需查询的属性名。
下面的例子使用has方法隐藏某些属性,不被in运算符发现。

let person = new Proxy({
    name:'lxf'
}, {
    has: function (target, key) {
        if (key[0] === '_') {
            console.log(key);
            return false;
        } else {
            console.log(key);
            return true;
        }
    }
});

console.log('_age' in person) //_age false
console.log('name' in person) //name true

上面代码中,如果原对象的属性名的第一个字符是下划线,proxy.has就会返回false,从而不会被in运算符发现。
另外,虽然for...in循环也用到了in运算符,但是has拦截对for...in循环不生效。

5 deleteProperty()

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。

let person = {
        name: 'lxf'
    },
    handler = {
        deleteProperty: function (target, key) {
            console.log(key);
            delete target[key];
            return true;
        }
    }
let proxy = new Proxy(person, handler);
delete proxy.name;
console.log(proxy) // name {}

其他实例方法就不在一一赘述,可以参考es6.ruanyifeng.com/#docs/proxy