JavaScript---Proxy对象

目录

一、概述

1.1Proxy构造函数

1.2拦截读取属性行为的例子

1.3注意点

1.4拦截操作

二、proxy的方法

2.1get()

2.2set()

2.3has()

2.4ownKeys()


一、概述

Proxy用于修改某些“操作”的“默认行为”。等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。

Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供一种机制,可以对外界的访问进行过滤改写

一个简单的例子:

const obj = new Proxy({},{
    get(target,prop,receiver){
        console.log(`获得${prop}`);
        return Reflect.get(target,prop,receiver);
    },
    set(target,prop,value,receiver){
        console.log(`设置${prop}=${value}`);
        return Reflect.set(target,prop,value,receiver);
    }
})
obj.count = 1; // 设置count=1
++obj.count; // //获得count,设置count=2

1.1Proxy构造函数

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

const proxy = new Proxy(target,handler);
  • new Proxy():生成一个Proxy实例
  • target:所拦截的目标对象
  • handler:一个对象,用来定制拦截行为

1.2拦截读取属性行为的例子

const proxy = new Proxy({},{
    get(target,prop){
        return 35;
    }
});
proxy.time // 35
proxy.name // 35
proxy.age // 35

上面代码中,Proxy接收两个参数。第一个参数是要代理的对象(一个空对象)第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数拦截对应的操作。

由于上面代码使用get方法拦截对目标属性的访问请求,因此访问任何属性都得到35

1.3注意点

要使得Proxy起作用,必须针对Proxy实例进行操作,而不是针对“目标”进行操作。

如果“handler”没有设置任何拦截,则就等同于直接通向原对象

const target = {};
const handler = {};
const proxy = new Proxy(target,handler);
proxy.a = 1;
console.log(target.a); // 1

Proxy实例也可以作为其它对象的原型对象

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});
let obj = Object.create(proxy);
obj.time // 35

上面代码中,proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。

1.4拦截操作

同一个拦截器函数(handler)可以设置多个操作

对于:“可以设置、但没有设置拦截的操作”,则直接落在目标对象上,按照原先的方式产生结果。

Proxy支持的拦截操作一共13种,如下所示:

  • get(target,propKey,receiver):拦截对象属性的读取(proxy.foo或proxy['foo'])
  • set(target,propKey,value,receiver):拦截对象属性的设置(proxy.foo = v或proxy['foo'] = v),返回一个布尔值
  • has(target,propKey):拦截propKey in prop的操作,返回一个布尔值
  • deleteProperty(target,propKey):拦截delete proxy[propKey]的操作,返回一个布尔值
  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in
  • getOwnPropertyDescriptor(target,propKey):拦截Object.getOwnProperty(proxy,propKey,propDesc),返回属性的描述对象
  • getdefineProperty(target,propKey,propDesc):拦截Object.defineProperty(proxy,propKey,propDesc)、Object.defineProperties(proxy,propDescs),返回一个布尔值
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值
  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象
  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值
  • setPrototypeOf(target,proto):拦截Object.setPrototypeOf(proxy,proto),返回一个布尔值
  • apply(target,thisArg,args):拦截Proxy实例作为函数调用的操作(proxy(..args)、proxy.call(object,...args)、proxy.apply(...))
  • construct(target,args):拦截Proxy实例作为构造函数调用的操作(new proxy(...args))

解释】一些参数解释

  1. target:目标对象(目标函数),即被代理的对象(函数)
  2. propKey:属性名
  3. receiver:代理对象本身或继承自代理对象的对象。通常情况下,没有特殊情况,receiver就是代理对象
  4. value:要设置的属性值
  5. property:属性名
  6. thisArg:函数调用时的this值
  7. args:函数调用时传递的参数列表(数组形式)

二、proxy的方法

在这里只介绍常用方法

2.1get()

get(target,propKey,receiver)方法接受三个参数,依次为:“目标对象”、“属性名”、“proxy实例本身”,最后一个参数可选

另一个拦截读取操作的例子:

const proxy = new Proxy(person,{
    get(target,prop,receiver){
        if (prop in target){
            return target[prop];
        }
        else {
            throw new ReferenceError(`${prop}未找到`);
        }
    }
});

console.log(proxy.name); // 张三
console.log(proxy.age); // ReferenceError: age未找到

在上面代码中,如果访问目标对象不存在的属性,会抛出一个操作。如果没有这个拦截函数,访问不存在的属性,只会undefined

function createArray(...elements){
    console.log(elements); // [1, 2, 3]
    const handler = {
        get(target,prop,receiver){
            const index = Number(prop);
            if (index < 0){
                prop = String(target.length + index);
            }
            return Reflect.get(target,prop,receiver);
        }
    }
    const target = [];
    target.push(...elements);
    return new Proxy(target,handler);
}
const arr = createArray(1,2,3);
console.log(arr[-1]); // 3

上面代码中,如果数组的位置参数为负数,就会从数组右侧开始计算

2.2set()

set(target,prop,value,receiver)方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为:“目标对象”、“属性名”、“属性值”和Proxy实例,最后一个参数可选

现在我们假设Person对象有一个age属性,该属性应该是一个不大于100的整数,那么可以使用Proxy保证属性值符合要求

const handler = {
    set(target,prop,value){
        if (prop === 'age'){
            if (!Number.isInteger(value)){
                throw new TypeError('age必须是整数');
            }
            if (value > 100){
                throw new RangeError('age不能大于100');
            }
        }
        // 对于满足条件的age属性和其他属性,直接赋值
        obj[prop] = value;
    }
}

const person = new Proxy({},handler);
person.age = '人'; // 抛出TypeError: age必须是整数
person.age = 120;  // 抛出RangeError: age不能大于100

上面代码中,使用了set()拦截方法,任何不符合要求的set属性负值,都会抛出一个错误,这是数据验证的一种实现方法。

2.3has()

has(target,prop)方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是“in”运算符

has方法可以接受两个参数,分别是:“目标对象”、“查询的属性名

下面的例子使用has方法隐藏某些属性,不被in运算符发现

const handler = {
    has(target,prop){
        if (prop[0] === '_'){
            return false;
        }
        return key in target;
    }
};
const target = {
    _count: 1,
    count:1
};
const proxy = new Proxy(target,handler);
console.log('_count' in proxy); // false

has方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has方法不判断一个属性是对象自身的属性,还是继承的属性。

另外,虽然for...in循环也用到了in运算符,但是has拦截对for...in循环不生效

2.4ownKeys()

ownKeys(target)方法用来拦截对象自身属性的读取操作,具体来说,拦截以下操作:

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • for...in循环
const target = {
    a:1,
    b:2,
    c:3
};
const handler = {
    ownKeys(target){
        return ['a'];
    }
};
const proxy = new Proxy(target,handler);
console.log(Object.keys(proxy)); // ['a']

注意:使用Object.keys()方法时,有三雷属性会被ownKeys()方法自动过滤

  1. 目标对象上不存在的属性
  2. 属性名为Symbol值
  3. 不可遍历(enumerable)的属性

for...in循环也受到ownKeys()方法的拦截:

const obj = { hello: 'world' };
const proxy = new Proxy(obj, {
  ownKeys: function () {
    return ['a', 'b'];
  }
});
for (let key in proxy) {
  console.log(key); // 没有任何输出
}

ownKeys()只返回ab属性,由于obj没有这两个属性,因此for...in循环不会有任何输出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是洋洋a

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值