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.prototype
是undefined
,所以不能使用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
// 好吧,我知道代码能更简单,只是想玩一玩