Proxy详解
一.Proxy基础
1. 什么是Proxy?
Proxy是一个构造函数,可以通过它生成一个Proxy实例。
const proxy = new Proxy(target, handler);
// target: 目标对象,待操作对象(可以是普通对象,数组,函数,proxy实例对象)
// handler: 一个对象,最多可包含13个操作方法,也可以是空对象
2. Proxy的作用
1. Proxy是一个ES6语法,它的作用主要是通过handler对象中的拦截方法拦截目标对象target的
某些操作(如读取,赋值,函数调用,new等),然后过滤或者改写这些操作的默认行为,
或者只是在完成这些操作的默认行为的基础上,增加一些副作用(如打印前置log等)。
2. 生成的实例对象是针对target对象的“拦截器”。也可以叫做“代理器”。
3. 然后必须通过操作proxy,即“拦截器”(拦截器对象本身性质和目标对象target一样,比如:target是函数,
那么proxy也是函数)才能触发拦截方法,来完成对目标对象的操作和访问。
4. 如果handler是个空对象({}),那么操作拦截器相当于直接操作目标对象target。
3. Proxy构造函数的特征
1. Proxy函数没有prototype属性,所以也就不能使用instanceof判断是否是Proxy实例
2. Proxy实例的数据类型和target数据类型一致。
var proxy1 = new Proxy([], {});
proxy1 instanceof Array; // true
var proxy2 = new Proxy(function(){}, {});
typeof proxy2; //"function"
二. Proxy拦截器handler方法
一共有13个拦截方法(对应Reflect的13个方法),可以大体分为两个部分。
1. 新的方法名
返回值是布尔值的方法有:
1. has(target, propKey)
作用: 拦截判断target对象是否含有属性propKey的操作
拦截操作: propKey in proxy; 不包含for...in循环
对应Reflect: Reflect.has(target, propKey)
语法:
const handler = {
has(target, propKey){
// ...
//默认操作
return propKey in target;
}
}
const proxy = new Proxy(target, handler)
示例: 过滤某些私有属性
const handler = {
has(target, propKey) {
if (propKey[0] === '_') {
return false;
}
return propKey in target;
}
}
const target = {_myprop: 1, a: 2, c:3};
const proxy = new Proxy(target, handler);
'_myprop' in proxy; // false
特殊情况: 目标target是不可扩展或者某个属性不可配置,只能返回默认行为结果;否则报错
var obj = { a: 10 };
Object.preventExtensions(obj); var p = new Proxy(obj, {
has: function(target, prop) {
return false; //只能是return prop in target;
}
});
'a' in p; //Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible
2. deleteProperty(target, propKey)
返回:严格模式下只有返回true, 否则报错
作用: 拦截删除target对象的propKey属性的操作
拦截操作: delete proxy[propKey]
语法:
const handler = {
deleteProperty(target, propKey){
// ...
//默认操作; 操作成功并返回true;操作失败报错
const bool = Reflect.deleteProperty(...arguments);
if (bool) {
return bool;
} else {
throw new Error("delete failed");
}
}
}
const proxy = new Proxy(target, handler)
示例:
var obj = { a: 10 };
var p = new Proxy(obj, {
deleteProperty(target, prop) {
console.log('delete propName ',prop);
return delete target[prop]; // 严格模式下操作成功必须返回true;否则报错
}
});
delete p.a;
console.log(obj);
// 运行结果如下:
'delete propName a'
{}
特殊情况: 属性是不可配置属性时,不能删除; 但是对象不可扩展的时候,可以删除属性。
var obj = { a: 10 };
Object.defineProperty(obj, 'b', {
value: 2, configurable: false
});
var p = new Proxy(obj, {
deleteProperty(target, prop) {
return delete target[prop];
}
});
delete p.b; // Uncaught TypeError: Cannot delete property 'b' of #<Object>
console.log(obj);
3. ownKeys(target)
返回: 数组(数组元素必须是字符或者Symbol,其他类型报错)
作用: 拦截获取键值的操作
拦截操作: Object.getOwnPropertyNames(proxy)
Object.getOwnPropertySymbols(proxy)
Object.keys(proxy)
for...in...循环
语法:
最后取到的结果不是return的值,而是会自动过滤
const handler = {
ownKeys(target) {
// 所有的keys;也可以是其他的数组
return Reflect.ownKeys(target);
}
}
示例:
var obj = { a: 10, [Symbol.for('foo')]: 2 };
Object.defineProperty(obj, 'c', {
value: 3, enumerable: false
})
var p = new Proxy(obj, {
ownKeys(target) {
return [...Reflect.ownKeys(target), 'b', Symbol.for('bar')];
}
});
const keys = Object.keys(p); // ['a']
// 自动过滤掉Symbol/非自身/不可遍历的属性 for(let prop in p) { // 和Object.keys()过滤性质一样,只返回target本身的可遍历属性
console.log('prop-',prop); // prop-a
}
const ownNames = Object.getOwnPropertyNames(p);// ['a', 'c', 'b']
// 只返回拦截器返回的非Symbol的属性,不管是不是target上的属性 const ownSymbols = Object.getOwnPropertySymbols(p);// [Symbol(foo), Symbol(bar)]
// 只返回拦截器返回的Symbol的属性,不管是不是target上的属性 const ownKeys = Reflect.ownKeys(p);// ['a','c',Symbol(foo),'b',Symbol(bar)]
// 返回拦截器返回的所有值
特殊情况:
1)如果某个属性是不可配置的,那么该属性在拦截器中必须被返回,否则报错
2)如果target对象是不可扩展的,那么拦截器返回的数组必须是操作的默认返回结果。
即必须是被拦截的操作的默认行为,如getOwnPropertyNames()
4. apply(target, thisArg, args)--target是函数
返回:函数执行结果
作用: 拦截proxy作为函数调用时的操作
拦截操作:proxy()
proxy.call(obj, ...args)
proxy.apply(obj, [...args])
Reflect.apply(proxy, thisArg, args)
语法:
const handler = {
apply(target, contextThis/*上下文对象this*/, args/*参数数组*/) {
return Reflect.apply(...arguments);
}
}
示例:
const handler = {
apply(target, contextThis/*上下文对象this*/, args/*参数数组*/) {
console.log('---apply拦截器---',contextThis,'-',args);
return Reflect.apply(...arguments)+'end';
}
}
let target = function(a,b) {
return a+b;
}
const proxy = new Proxy(target, handler);
console.log('proxy-->',proxy(5,6));
console.log('proxy.call-->',proxy.call(null, 5, 6));
console.log('proxy.apply-->',proxy.apply(null, [5,6]));
console.log('Reflect-->',Reflect.apply(proxy, null, [5,6])); // 运行结果如下:
/*
---apply拦截器---undefined-[5,6]
proxy-->11end
---apply拦截器---null-[5,6]
proxy.call-->11end
---apply拦截器---null-[5,6]
proxy.apply-->11end
---apply拦截器---null-[5,6]
Reflect-->11end
*/
5. construct(target, args, newTarget)--target是构造函数
返回: 实例对象, 不是对象会报错
作用: 拦截new命令
拦截操作: new proxy(...args)
语法:
const handler = {
construct(target, args, newTarget){// args是参数数组, newTarget是生成的proxy实例
console.log('----拦截new----',args,'-', newTarget === proxy);
return Reflect.construct(target, args);// 默认行为,也可以return {a: b},只要是对象就可以
}
}
const target = function(a,b) {};
var proxy = new Proxy(target, handler);
console.log('proxy type-->', typeof proxy);
const result = new proxy(1,2); // 触发拦截器
console.log('result type-->',typeof result)
// 运行结果如下:
proxy type-->function
----拦截new----[1,2]-true
result type-->object
2. 方法名和对象原有方法名一样
6. get(target, propKey, receiver)
返回: 返回读取的属性
作用:拦截对象属性的读取
拦截操作:proxy[propKey]或者点运算符
语法:
const handler = {
get(target, propKey, receiver) {// receiver是proxy实例
return Reflect.get(target, propKey);
}
}
示例: 实现函数的链式操作
const funsObj = {
double(n) {return n*2},
square(n) {return n**2}
}
var pipe = (value) => {
const callStack=[];
return new Proxy({}, {
get(target, propKey, receiver) {
console.log(propKey,callStack);
if (propKey === 'get') {
return callStack.reduce((val, fn) => {
return fn(val);
}, value)
} else {
callStack.push(funsObj[propKey]);
}
return receiver; //返回proxy实例才能实现链式调用
}
})
}
pipe(3).double.square.get; //
get方法可以继承,但是receiver的值会是直接触发的那个对象。
const proxy= new Proxy({}, {
get(target, propKey, receiver) {
return receiver;
}
})
const p = Object.create(proxy);
console.log(p.a === p); // true p.a返回receiver
特殊情况:如果对象的属性writable和configurable同时为false, 则拦截器只能返回默认行为
const target = {};
Object.defineProperty(target, 'a', {
value: 1,
writable: false,
configurable: false
})
const proxy = new Proxy(target, {
get(target,propKey) {
return 2;
//应该return 1;不能返回其他值,否则报错
}
})
proxy.a; // Uncaught TypeError
7.set(target,propKey, value,receiver)
返回:严格模式下返回true操作成功;否则失败,报错
作用: 拦截对象的属性赋值操作
拦截操作: proxy[propkey] = value
语法:
const proxy = new Proxy({}, {
set(target, propKey, value,receiver) {// receiver时proxy实例
const bool = Reflect.set(...arguments);
if (bool){
return //!!!严格模式下操作成功必须返回true,否则报错;非严格模式下可以省略
//当Reflect.set传入的参数有receiver且属性writable=true时,会在receiver在定义属性,会触发defineProperty拦截
} else {
throw new Error("操作失败")
} },
defineProperty(target, propKey, propDesc) {
console.log('defineProperty')
}
})
set方法也可以继承,receiver也是最终调用的那个实例,和get方法一样。参照get的方法。
设置 target[propKey] = receiver;
当对象的属性writable为false时,该属性不能在拦截器中被修改;
const obj = {};
Object.defineProperty(obj, 'foo', {
value: 'bar',
writable: false,
configurable: true,
}); const handler = {
set: function(obj, prop, value, receiver) {
return Reflect.set(...arguments);
},
};
const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
console.log(obj); // {foo: bar} 说明修改失败
8. defineProperty(target, propKey,PropDesc)
返回: 严格模式下操作成功必须返回true;否则报错
作用:拦截定义属性或者重新定义属性操作
拦截操作: Object.defineProperty(proxy, propKey,propDesc)
Reflect.defineProperty(proxy, propKey,propDesc)
语法兼示例:
const proxy = new Proxy({}, {
defineProperty(target, propKey, propDescriptor) {// 最后一个参数是属性描述对象
const bool = Reflect.defineProperty(...arguments);
if (bool) {
return bool;// !!严格模式下操作成功必须返回true,否则报错
} else {
throw new Error("定义属性失败")
}
}
})
Object.defineProperty(proxy, 'a', {value:5})
特殊情况:
如果对象是不可扩展的(preventExtensible(obj)),则不能添加属性;
如果对象的某属性writable和configurable同时为false, 则不能重新定义该属性的值;
如果上面的属性有其中一个是true,可以重新定义该属性的值。
9. getPrototypeOf(target)
返回: 返回对象或者null,否则报错
作用:拦截获取原型对象的操作
拦截操作:Object.getPrototypeOf(proxy)
proxy.__proto__
Object.isPrototypeOf(proxy)
Reflect.getPrototypeOf(proxy)
instanceof
语法:
var p = new Proxy({}, {
getPrototypeOf(target) {
return Reflect.getPrototypeOf(target);
}
});
示例:
var proxy = new Proxy({}, {
getPrototypeOf(target) {
return {a:1}
}
});
Object.getPrototypeOf(proxy); // {a:1}
特殊情况:
如果目标对象是不可扩展的,那么只能返回默认的原型对象。
10.setPrototypeOf(target, proto)
返回:严格模式下返回true,否则报错;只能是布尔值
作用:拦截设置原型对象的操作
拦截操作:Object.setPrototypeOf(proxy, proto)
proxy.__proto__
Reflect.setPropertyOf(proxy,proto)
语法:
var proxy = new Proxy({}, {
setPrototypeOf(target, proto) {
const bool = Reflect.setPrototypeOf(...arguments);
if (bool) {
return bool; // !!!严格模式下操作成功必须返回true;否则报错
} else {
throw new Error("设置原型对象失败");
}
}
})
特殊情况:
如果对象不可扩展,只能进行默认行为。不能修改。
11. isExtensible(target)---只能返回默认行为结果
返回:布尔值
作用: 拦截是否可扩展方法的调用
拦截操作: Object.isExtensible(proxy)
Reflect.isExtensible(proxy)
语法:
const proxy = new Proxy({}, {
isExtensible(target) {
return Reflect.isExtensible(target); //!!!返回结果只能是这个;即proxy和target的可扩展性必须是一致的
}
})
12. preventExtensible(target)--遵循默认行为
返回:严格模式下返回true,否则报错;
拦截操作:Object.preventExtensible(proxy)
Reflect.preventExtentsible(proxy)
语法:
const proxy = new Proxy({}, {
preventExtensions(target) {
return Reflect.preventExtensions(target); //严格模式下,必须存在
}
})
13. getOwnPropertyDescriptor(target, propKey)
返回: 对象或者undefined
拦截操作: Object.getOwnPropertyDescriptor(proxy)
Reflect.getOwnPropertyDescriptor(proxy)
语法:
const proxy = new Proxy({}, {
getOwnPropertyDescriptor(target, propKey) {
return Reflect.getOwnPropertyDescriptor(target,propKey);
}
})
三. 静态方法-Proxy.revocable()
作用: 返回一个可取消的proxy实例
语法:
const {proxy, revoke} = Proxy.revocable(target,handler);
// proxy是实例
// revoke是个方法;直接调用后取消proxy实例
示例:
const {proxy, revoke} = Proxy.revocable({},{});
proxy.a=5;
revoke();
proxy.a // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
应用:
用于一个只能访问一次的代理器
四. this指向
拦截器方法中的this指向proxy实例
五.Proxy应用
观察者模式:通过拦截对象的赋值操作,实时调用观察函数。
如: mobx中的observable, observe的实现;通过函数观察对象,并实时作出反应
const obj = {name: 'lyra', age: 18};//被观察者--使用Proxy
const fn1 = () => {
obj.age = 16;
console.log(`Hello, ${obj.name}, ${obj.age}`);
}
const fn2 = () => console.log('there must be sth changed');
const observeFuncs = new Set();
const observe = (fn) => observeFuncs.add(fn);
const observable = function(obj) {
return new Proxy(obj, {
set(target, prop, value) {
observeFuncs.forEach(observer => observer()); //被观察者属性被赋值时,触发观察者函数调用
return Reflect.set(...arguments); //默认行为
}
})
}
const objProxy = observable(obj);//指定被观察者对象
observe(fn1); //指定观察者函数
observe(fn2);
objProxy.name = 'lyraLee';
// 运行结果如下:
Hello, lyraLee, 16
there must be sth changed
Proxy详解的更多相关文章
- jQuery proxy详解
第一次接触jQuery.proxy()时感觉这个方法不实用,不明白它到底是个什么意思.今天来将jQuery官网上的解释进行一下翻译,顺便添加自己的理解和一些示例.proxy也可称为代理. jQuery ...
- Spring AOP 的proxy详解
spring 提供了多种不同的方案实现对 bean 的 aop proxy, 包括 ProxyFactoryBean, 便利的 TransactionProxyFactoryBean 以及 AutoP ...
- nginx的proxy模块详解以及参数
文章来源 运维公会:nginx的proxy模块详解以及参数 使用nginx配置代理的时候,肯定是要用到http_proxy模块.这个模块也是在安装nginx的时候默认安装.它的作用就是将请求转发到相应 ...
- zabbix配置文件详解--服务(server)端、客户(agent)端、代理(proxy)端
在zabbix服务(server)端.客户(agent)端.代理(proxy)端分别对应着一个配置文件,即:zabbix_server.conf,zabbix_agentd.conf,zabbix_p ...
- 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)
一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...
- Node.js npm 详解
一.npm简介 安装npm请阅读我之前的文章Hello Node中npm安装那一部分,不过只介绍了linux平台,如果是其它平台,有前辈写了更加详细的介绍. npm的全称:Node Package M ...
- Android 网络框架之Retrofit2使用详解及从源码中解析原理
就目前来说Retrofit2使用的已相当的广泛,那么我们先来了解下两个问题: 1 . 什么是Retrofit? Retrofit是针对于Android/Java的.基于okHttp的.一种轻量级且安全 ...
- Nginx 反向代理、负载均衡、页面缓存、URL重写及读写分离详解
转载:http://freeloda.blog.51cto.com/2033581/1288553 大纲 一.前言 二.环境准备 三.安装与配置Nginx 四.Nginx之反向代理 五.Nginx之负 ...
- pip安装使用详解(转)
pip类似RedHat里面的yum,安装Python包非常方便.本节详细介绍pip的安装.以及使用方法. 1.pip下载安装 1.1 pip下载 1 # wget "https://py ...
随机推荐
- jQuery笔记归纳
使用jQuery前首先需要导如jQuery包:https://jquery.com/(点击下载按钮,进入后点击鼠标右键另存为即可下载) 例如:<script type="te ...
- python第一个浏览器的自动执行程序
1.目标:简单点,百度搜索“美丽的程序员” 2.操作方法: a.python已经安装完成 b.安装PIP:在windows的cmd窗口下输入easy_install pip c.安装sele ...
- SVN迁移到Gitlab实践经历
svn 迁移至git操作手册 项目交付.版本管理工具变更等情况下,迁移svn旧历史记录有很大必要,方便后续追踪文件的提交历史,文件修改记录比对等.git自带了从svn迁移至git的工具命令,可很好的对 ...
- s5p6818 从SD卡启动程序(制作SD启动卡)
背景: 最近在学习uboot,其中有一步很重要的任务就是需要实现uboot 的验证,没有办法验证uboot是不是自己做的,那么整个开发就会收到阻碍.另外,从公司现在开发的板子来看,uboot从sd卡启 ...
- Codeforces Round #557 Div. 1 based on Forethought Future Cup - Final Round
A:开场就读错题.读对了之后也没啥好说的. #include<bits/stdc++.h> using namespace std; #define ll long long #defin ...
- 音视频入门-04-BMP图像四字节对齐的问题
* 音视频入门文章目录 * BMP 图像四字节对齐 表示 BMP 位图中像素的位元是以行为单位对齐存储的,每一行的大小都向上取整为4字节(32 位 DWORD)的倍数.如果图像的高度大于 1,多个经过 ...
- [Vue]Vue keep-alive
keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们.和 <transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 ...
- WebSocket协议探究(三):MQTT子协议
一 复习和目标 1 复习 Nodejs实现WebSocket服务器 Netty实现WebSocket服务器(附带了源码分析) Js api实现WebSocket客户端 注:Nodejs使用的Socke ...
- 手机如何修改host文件
常常在想本地开发的时候使用pc端测试移动端,由于有安全域名限制,因此在本地修改了机器的host.将域名映射到本地局域网.这个做法是很正常的也很常见.但是现在新的需求是我需要在手机上也能够正常测试啊!怎 ...
- vue之双向绑定
Vue的一大核心是双向绑定,在2.0中采用数据劫持,用Object.defineProperty实现,但作者已声明在3.0中会采用proxy实现 Object.defineProperty是什么? ...