概述:改变默认行为,对外界的访问进行过滤和改写
var proxy = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
上面代码对一个空对象架设了一层拦截
,重定义
了属性的读取
(get)和设置
(set)行为。对设置了拦截行为的对象obj
,去读写它的属性,就会得到下面的结果。
proxy.count = 1
// setting count!
++proxy.count
// getting count!
// setting count!
// 2
上面代码说明,Proxy 实际上重载了点运算符
,即用自己的定义
覆盖了语言的原始定义
。
语法,var proxy = new Proxy(target, handler);
var proxy = new Proxy(target, handler);
target:参数表示所要拦截的目标对象
handler:参数也是一个对象,用来定制拦截行为
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
Proxy 对象,设置到object.proxy属性,从而可以当做object`对象的属性调用
var object = { proxy: new Proxy(target, handler) };
Proxy 实例作为其他对象的原型对象
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
//obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。
同一个拦截器函数,可以设置拦截多个操作
var handler = {
get: function(target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function(target, thisBinding, args) {
return args[0];
},
construct: function(target, args) {
return {value: args[1]};
}
};
var fproxy = new Proxy(function(x, y) {
return x + y;
}, handler);
fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true
Proxy 支持的拦截操作
get(target, propKey, receiver)
:拦截对象属性的读取
,比如proxy.foo
和proxy['foo']
。set(target, propKey, value, receiver)
:拦截对象属性的设置
,比如proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。has(target, propKey)
:拦截propKey in proxy的操作
,返回一个布尔值。deleteProperty(target, propKey)
:拦截delete proxy[propKey]
的操作,返回一个布尔值。ownKeys(target)
:拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环
,返回一个数组。该方法返回目标对象
所有自身的属性
的属性名
,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。getOwnPropertyDescriptor(target, propKey)
:拦截Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。defineProperty(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, object, args)
:拦截Proxy 实例作为函数调用的操作
,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)
。construct(target, args)
:拦截 Proxy 实例作为构造函数调用的操作
,比如new proxy(...args)
。
方法 | 描述 | handler.apply() | 拦截 Proxy 实例作为函数调用的操作 | handler.construct() | 拦截 Proxy 实例作为构造函数调用的操作 | handler.defineProperty() | 拦截 Object.defineProperty() 的操作 | handler.deleteProperty() | 拦截 Proxy 实例删除属性操作 | handler.get() | 拦截 读取属性的操作 | handler.set() | 拦截 属性赋值的操作 | handler.getOwnPropertyDescriptor() | 拦截 Object.getOwnPropertyDescriptor() 的操作 | handler.getPrototypeOf() | 拦截 获取原型对象的操作 | handler.has() | 拦截 属性检索操作 | handler.isExtensible() | 拦截 Object.isExtensible()操作 | handler.ownKeys() | 拦截 Object.getOwnPropertyDescriptor() 的操作 | handler.preventExtension() | 拦截 Object().preventExtension() 操作 | handler.setPrototypeOf() | 拦截Object.setPrototypeOf()操作 | Proxy.revocable() | 创建一个可取消的 Proxy 实例 |
---|
get(target,key,receiver)
依次三个参数:目标对象、被读取的属性名、proxy 实例本身
需要return
应用实例
1、访问目标对象不存在的属性,抛出一个错误而不是返回undefined
var person = {
name: "张三"
};
var proxy = new Proxy(person, {
get: function(target, property) {
if (property in target) {
return target[property];
} else {
throw new ReferenceError("Property \"" + property + "\" does not exist.");
}
}
});
proxy.name // "张三"
proxy.age // 抛出一个错误
2、get方法可以继承(定义在Prototype对象上,拦截实例通过原型链获取继承的方法的操作)
let proto = new Proxy({}, {
get(target, propertyKey, receiver) {
console.log('GET ' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.foo // "GET foo"
上面代码中,拦截
操作定义在Prototype对象上面
,所以如果读取obj对象继承的属性时(本身没有,通过原型链查找的属性
),拦截会生效。
3、使用get拦截,实现数组读取负数的索引
function createArray(...elements) {
let handler = {
get(target, propKey, receiver) {
let index = Number(propKey);
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
let target = [];
target.push(...elements);
return new Proxy(target, handler);
}
let arr = createArray('a', 'b', 'c');
arr[-1] // c
//简略版
var arr=new Proxy([0,1,2,3,4],{
get(target, p, receiver) {
if(p<0){
var n=eval(target.length-1+p);
return target[n];
}
return target[p];
}
})
console.log(arr[-1]); //3
4、将读取属性的操作(get),转变为执行某个函数【vue3.0】有点像发布订阅
var pipe = (function () {
return function (value) {
var funcStack = [];
var oproxy = new Proxy({} , {
get : function (pipeObject, fnName) {
if (fnName === 'get') {
//如果获取的是get 对数组funcStack中的函数一次调用
return funcStack.reduce(function (val, fn) {
return fn(val);
},value);
}
//如果不是get 向数组funcStack中添加函数
funcStack.push(window[fnName]);
return oproxy;
}
});
return oproxy;
}
}());
var double = n => n * 2;
var pow = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") | 0;
pipe(3).double.pow.reverseInt.get; // 63
//有点像发布订阅
5、利用get拦截,实现一个生成各种 DOM 节点的通用函数dom【vue3.0】
const dom=new Proxy({},{
get(target, p, receiver) {
//(attrs={},...children) 第一个参数为attrs 剩余的都是children
return function (attrs={},...children) {
//get属性名 就是要创建的 元素名称
const el=document.createElement(p);
//循环 attrs设置元素属性
for (let prop of Object.keys(attrs)){
el.setAttribute(prop,attrs[prop]);
}
for (let child of children){
if(typeof child === 'string'){
child = document.createTextNode(child);
}
el.append(child)
}
return el;
}
}
})
const el = dom.div(
{},
'Hello, my name is ',
dom.a({href: '//example.com'}, 'Mark'),
'. I like:',
dom.ul({},
dom.li({}, 'The web'),
dom.li({}, 'Food'),
dom.li({}, '…actually that\'s it')
)
);
//相对与div :{}为 attrs ,剩余的都是子级
console.log(el);
document.body.appendChild(el);
第三个参数的例子,一般情况下就是 Proxy 实例
const proxy = new Proxy({}, {
get: function(target, property, receiver) {
return receiver;
}
});
proxy.getReceiver === proxy // true
const proxy = new Proxy({}, {
get: function(target, property, receiver) {
return receiver;
}
});
const d = Object.create(proxy);
d.a === d // true
上面代码中,d
对象本身没有a属性
,所以读取d.a
的时候,会去d的原型proxy对象找
。这时,receiver就指向d
,代表原始的读操作所在的那个对象。
set(obj, prop, value,receiver)
依次四个参数:目标对象,属性名,属性值,Proxy 实例本身
在赋值操作发生时进行自己想要的操作,还可以进行数据绑定,即每当对象发生变化时,会自动更新 DOM(vue3.0)
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 对于满足条件的 age 属性以及其他属性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错
设置一些内部属性不被外部读写(假定开头是_的为内部属性)
const handler = {
get (target, key) {
invariant(key, 'get');
return target[key];
},
set (target, key, value) {
invariant(key, 'set');
target[key] = value;
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
}
}
const target = {};
const proxy = new Proxy(target, handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property
第四个参数的例子
set方法的第四个参数receiver,指的是原始的操作行为所在的那个对象,一般情况下是proxy实例本身,跟get的第三个参数的运用相同
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver;
}
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
proxy.foo === proxy // true
apply(target, context, args),拦截函数的调用、call和apply操作
依次三个形参:目标对象,上下文对象(this),参数数组
设置apply拦截后 函数自身内部的代码不再执行
var handler = {
apply (target, ctx, args) {
return Reflect.apply(...arguments);
}
};
示例
var fn=function () {
//不执行
console.log(this,'fn')
return 'I am fn'
}
var p=new Proxy(fn,{
apply(target, thisArg, argArray) {
console.log(thisArg,'p') //undefined 'p'
return 'I am p'
}
})
console.log(p()); //I am p
var twice = {
apply (target, ctx, args) {
return Reflect.apply(...arguments) * 2;
}
};
function sum (left, right) {
return left + right;
};
var proxy = new Proxy(sum, twice);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30
//直接调用Reflect.apply方法,也会被拦截。
Reflect.apply(proxy, null, [9, 10]) // 38
construct(target, args, newTarget)
依次三个形参:目标对象,参数对象,new 后面的函数
construct方法返回的必须是一个对象
var handler = {
construct (target, args, newTarget) {
return new target(...args);
}
};
var p = new Proxy(function () {}, {
construct: function(target, args) {
console.log('called: ' + args.join(', '));
return { value: args[0] * 10 };
}
});
(new p(1)).value
// "called: 1"
// 10
has(target, key),拦截HasProperty操作,即判断对象是否具有某个属性(in运算符)
依次两个形参
下面的例子使用has方法隐藏某些属性,不被in运算符发现。
var handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false
deleteProperty(target, key),用于拦截delete操作
依次两个形参:目标对象、需查删除的属性名
var handler = {
deleteProperty (target, key) {
invariant(key, 'delete');
delete target[key];
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
}
}
var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property
Proxy.revocable() ,返回一个可取消的 Proxy 实例
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
this 问题
Proxy不做任何拦截的情况下,也无法保证与目标对象的行为一致
在Proxy代理的情况下,目标对象内部的this关键字会指向 Proxy 代理
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true
const _name = new WeakMap();
class Person {
constructor(name) {
_name.set(this, name);
}
get name() {
return _name.get(this);
}
}
const jane = new Person('Jane');
jane.name // 'Jane'
const proxy = new Proxy(jane, {});
proxy.name // undefined
上面代码中,目标对象jane
的name
属性,实际保存在外部WeakMap
对象_name
上面,通过this
键区分。由于通过proxy.name
访问时,this
指向proxy
,导致无法取到值,所以返回undefined
。
原生对象的内部属性,只有通过正确的this才能拿到
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);
proxy.getDate();
// TypeError: this is not a Date object.
用bind将this绑定到原始处理对象,就可以解决这个问题
const target = new Date('2015-01-01');
const handler = {
get(target, prop) {
if (prop === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, prop);
}
};
const proxy = new Proxy(target, handler);
proxy.getDate() // 1
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!