JS代理Proxy

JS代理Proxy

代理是目标对象的抽象。目标对象既可以直接被操作,也可以通过代理来操作。 但直接操作会绕过代理施予的行为。

首先就是空代理,就是什么也不做,在代理对象上执行的所有操作都会无障碍地传播到目标对象。

代理是使用 Proxy 构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。缺少其中任何一个参数都会抛出 TypeError。

我们来看看例子

const a = {
    name: 'Sonic'
};

const handler = {};

const proxy = new Proxy(a, handler);

console.log(a.name + ' ' + proxy.name + ' ' + handler.name);
//Sonic Sonic undefined

a.name = 'Lisa';
console.log(a.name + ' ' + proxy.name);
//Lisa Lisa

proxy.name = 'Nico';
console.log(a.name + ' ' + proxy.name);
//Nico Nico

在代理对象上执行的所有操作都会无障碍地传播到目标对象

这里需要注意Proxy.prototypeundefined,所以不能使用instanceof操作符,如果想要区分代理,可以使用严格相等

console.log(proxy === a);
//false

使用代理的主要目的是可以定义捕获器(trap)。捕获器就是在处理程序对象中定义的“基本操作的“拦截器”。每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接 或间接在代理对象上调用。每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对 象之前先调用捕获器函数,从而拦截并修改相应的行为

get()

例如,我们可以制定一个get()捕获器,只能写成get(),其他打咩

const target = {
    foo: 'bar'
};

const handler = {
    get() {
        return 'handler override';
    }
};

const proxy = new Proxy(target, handler);

console.log(target.foo); // bar
console.log(proxy.foo); // handler override

我们使用了代理对象去拿属性,所以就触发了get()方法,当然除了这种方式以外还有很多种方式可以触发

console.log(target['foo']); // bar
console.log(proxy['foo']); // handler override

console.log(Object.create(target)['foo']); // bar 
console.log(Object.create(proxy)['foo']); // handler override

当然这个函数接受三个参数target property receiver

let num = [1, 2, 3];

let handler = {
    get(target, property) {
        console.log(target === num);
        if (property in target) {
            return target[property];
        } else {
            return 0;
        }
    }
}

let proxy = new Proxy(num, handler);

console.log(proxy[1]);
console.log(proxy[100]);

// true
// 2
// true
// 0

通过结果我们不难发现target其实就是num数组,也就是new Proxy的第一个参数,property就是这个目标对象的属性。

set()

set捕获器会在设置属性值的时候被调用,这个函数接受四个参数target property value receiver

let num = [];

const handler = {
    set(target, property, value) {
        if (typeof value == 'number') {
            target[property] = value;
            console.log('insert success');
            return true;
        } else {
            console.log('this is not a number!');
            return false;
        }
    }
}

let proxy = new Proxy(num, handler);

proxy.push(1);
proxy.push(2);

console.log(proxy.length); // 2

proxy.push('test'); // TypeError: 'set' on proxy: trap returned falsish for property '2'

上面这段代码的意思就是让proxy数组只接受数字,当然如果是数字要返回true,不然只要返回false就会触发TypeError

ownKeys()

这个捕获器会在迭代时被调用

let user = {
    name: 'Sonic',
    age: 18,
    _pwd: '123'
};

let handler = {
    ownKeys(target) {
        return Object.keys(target).filter(key => !key.startsWith('_'));
    }
}

let proxy = new Proxy(user, handler);

for (let key in proxy) {
    console.log(key);
}

// name
// age

console.log(Object.keys(proxy)); // [ 'name', 'age' ]
console.log(Object.values(proxy)); // [ 'Sonic', 18 ]

上面这段代码就是将对象进行过滤,我们不希望能够访问到_pwd属性,所以我们使用filter进行过滤

但是有个问题如果我们返回对象中不存在的键,Object.keys 并不会列出这些键

let user = {};

let handler = {
    ownKeys(target) {
        return ['a', 'b', 'c'];
    }
};

let proxy = new Proxy(user, handler);

for (let pro in proxy) console.log(pro); //
console.log(Object.keys(proxy)); // []

原因是因为Object.keys只会去访问带有enumerable的属性,在这里,由于没有属性,其描述符为空,没有 enumerable 标志,因此它被略过。

为了检查一个属性是否有enumerble属性,Object.keys会调用[[GetOwnProperty]]来进行判断,所以我们想要让这个属性能被访问到,就需要使用捕获器getOwnPropertyDescriptor

let user = {};

let handler = {
    ownKeys(target) {
        return ['a', 'b', 'c'];
    },

    getOwnPropertyDescriptor(target, prop) {
        return {
            enumerable: true,
            configurable: true
        }
    }
};

let proxy = new Proxy(user, handler);

for (let pro in proxy) console.log(pro); // a b c
console.log(Object.keys(proxy)); // ['a', 'b', 'c']

deleteProperty()

有一个普遍的约定,即以下划线 _ 开头的属性和方法是内部的。不应从对象外部访问它们。

(什么时候才能像Python一样直接在底层就写好啊啊啊,虽然class能行,但是不能继承,唉)

let user = {
    name: 'Sonic',
    age: 15,
    _pwd: '123456'
};

console.log(user._pwd); //123456
//👆求求了你报错好不好

咳咳,所以为了避免能访问到带_的属性我们就需要一个新的捕获器deleteProperty()同时还需要get()set()以及ownKeys()

let user = {
    name: 'Sonic',
    age: 15,
    _pwd: '123456'
};

let handler = {
    get(target, prop) {
        if (prop.startsWith('_')) {
            throw new Error('Access defined');
        }

        let value = target[prop];
        return (typeof value === 'function') ? value.bind(target) : value;
    },

    set(target, prop, val) {
        if (prop.startsWith('_')) {
            throw new Error('Access defined');
        } else {
            target[prop] = val;
            return true;
        }
    },

    deleteProperty(target, prop) {
        if (prop.startsWith('_')) {
            throw new Error('Access defined');
        } else {
            delete target[prop];
            return true;
        }
    },

    ownKeys(target) {
        return Object.keys(target).filter(key => !key.startsWith('_'));
    }
};

let proxy = new Proxy(user, handler);

try {
    console.log(proxy._pwd);
} catch (e) {
    console.log(e.message);
};

try {
    proxy._pwd = '456789';
} catch (e) {
    console.log(e.message);
};

try {
    delete proxy._pwd;
} catch (e) {
    console.log(e.message);
};

for (let prop in proxy) console.log(prop);

has()

人如其名,两个参数,target 是目标对象,property 属性名称。

我们想使用in操作符检查一个数是否在range中

function range(start, end) {
    this.start = start;
    this.end = end;
};

let handler = {
    has(target, prop) {
        return prop >= target.start && prop <= target.end;
    }
};

range = new Proxy(new range(1, 10), handler);

console.log(5 in range); // true
console.log(50 in range); // false
// 好吧,我知道代码能更简单,只是想玩一玩
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值