proxy详细介绍与使用

proxy 对象用于创建一个对象的代理,是在目标对象之前架设一个拦截,外界对该对象的访问,都必须先通过这个拦截。通过这种机制,就可以对外界的访问进行过滤和改写。

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

var proxy = new Proxy(target, handler);

target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

const obj = {};
const proxy=new Proxy(obj,{get:function(target,propKey){return 1}
})console.log(proxy.name) // 1
console.log(proxy.value) // 1

在上面代码中,Proxy接受两个参数,第一个是要代理的目标对象(上面的是空对象obj),在没有设置Proxy的情况下,对proxy的操作就是访问obj。第二个参数是配置对象,对每个被代理的操作,需要提供一个对应的处理函数,这个函数用于拦截对应的操作。

上面代码中,配置对象有个get方法,用来拦截对目标对象属性的访问请求。get方法中的两个参数分别是目标对象和所要访问的属性。由于设置的是返回1,所以访问任何属性都得到1。

如果handler没有设置拦截,就等同于直接通向原对象。

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

handler是空对象,没有拦截作用,访问proxy等同于访问obj。但是代理对象和原对象并不相等。

handler配置

handler 对象是一个容纳一批特定属性的占位符对象。它包含有 proxy 的各个捕获器。

1. get

get方法用于拦截某个属性的读取操作,接受三个参数,(目标对象,属性名,Proxy或者proxy实例本身),第三个参数可选。

const obj={value:1
}
const proxy=new Proxy(obj,{get:function(target,propKey){if(propKey in target){return target[propKey]}else{throw new ReferenceError(`${propKey} 不存在`)}}
})
console.log(proxy.value) // 1
proxy.name //报错

如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回undefined

get方法可以被继承

const obj = {a: 1
};
let proto = new Proxy(obj, {get(target, propertyKey) {return target[propertyKey];}
});
let newObj = Object.create(proto);
newObj.a // "GET foo"

拦截操作定义在原型对象上面,所以如果读取obj对象继承的属性时,拦截会生效。

下面是一个get方法的第三个参数的例子,它总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy 实例。

const proxy = new Proxy({}, {get: function(target, key, receiver) {return receiver;}
});
proxy.getReceiver === proxy // true

对于get方法,proxy存在一些约束

  • 如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同,否则会抛出异常。

    const obj = {};
    Object.defineProperty(obj, "a", {configurable: false,enumerable: false,value: 1,writable: false
    });
    const proxy=new Proxy(obj,{get:function(target,propKey){return 2      // return 1 就没问题}
    });
    proxy.a
    
  • 如果要访问的目标属性没有配置访问方法,即 get 方法是 undefined 的,则返回值必须为 undefined
    const obj = {};
    Object.defineProperty(obj, "a", {configurable: false,get: undefined
    });
    const proxy=new Proxy(obj,{get:function(target,propKey){return 1;        // return undefined 就没问题}
    });
    proxy.a
    

2. set

用来拦截某个属性的赋值操作,接受四个参数(目标对象、属性名、属性值、最初被调用的对象(通常是proxy实例本身,但 handlerset 方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是 proxy 本身))),第四个参数可选。

const proxy = new Proxy({}, {set: function(obj,prop,value){if(prop === 'value') {if(!Number.isInteger(value)){throw new TypeError('不是整数')}if(value>10){throw new RangeError('太大了')}}obj[prop] = value}
})
proxy.value = 5
proxy.value = 'val'     //报错
proxy.value = 20        //报错

由于设置了存值函数set,任何不符合要求的value属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。

来看看set在原型链上的情况.

const proxy = new Proxy({}, {set: function(obj, prop, value, receiver) {console.log(receiver === newObj);     // trueif(prop === 'value') {if(!Number.isInteger(value)){throw new TypeError('不是整数')}if(value>10){throw new RangeError('太大了')}}obj[prop] = value}
})
let newObj = Object.create(proxy);
newObj.value = 1;

将代理对象作为原型创建一个新对象,此时第四个参数就是这个新对象而不是proxy实例。

对于set方法,proxy也存在一些约束

  • 若目标属性是一个不可写及不可配置的数据属性,则不能改变它的值。

    const obj = {};
    Object.defineProperty(obj, "a", {configurable: false,enumerable: false,value: 1,writable: false
    });
    const proxy=new Proxy(obj,{set: function(obj, prop, value) {obj[prop] = value}
    });
    proxy.a = 1;
    proxy.a       // 还是 undefined
    
  • 如果目标属性没有配置存储方法,即 set 的是 undefined,则不能设置它的值.
    const obj = {};
    Object.defineProperty(obj, "a", {configurable: false,set: undefined
    });
    const proxy=new Proxy(obj,{set: function(obj, prop, value) {obj[prop] = value}
    });
    proxy.a = 1;
    proxy.a       // 还是 undefined
    
  • 在严格模式下,如果 set 方法返回 false,那么也会抛出一个异常。

3. apply

拦截函数的调用,接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。

var target = function () { return 'target'; };
var handler = {apply: function () {return 'proxy';}
};var p = new Proxy(target, handler);
p()
// "proxy"

变量pProxy 的实例,当它作为函数调用时(p()),就会被apply方法拦截,返回一个字符串。

4. has

用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。接受两个参数,分别是目标对象、需查询的属性名。

var handler = {has (target, key) {return key in target;}
};
var target = { prop: 'foo' };
var proxy = new Proxy(target, handler);
'propa' in proxy // false

如果原对象不可配置或者禁止扩展,这时has拦截会报错。

var obj = { a: 10 };
Object.preventExtensions(obj);var p = new Proxy(obj, {has: function(target, prop) {return false;}
});
'a' in p // TypeError is thrown

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

5. construct

用于拦截new命令

var handler = {construct (target, args, newTarget) {return new target(...args);}
};

接受三个参数,目标对象,构造函数的参数对象,new命令作用的构造函数。

construct方法返回的必须是一个对象,否则会报错。

6. deletePrototype

拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被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

deleteProperty方法拦截了delete操作符,删除第一个字符为下划线的属性会报错。
目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。

7. defineProperty

拦截了Object.defineProperty操作。

var handler = {defineProperty (target, key, descriptor) {return false;}
};
var proxy = new Proxy({}, handler);
proxy.a = 'a' // 不会生效

8. getOwnPropertyDescriptor

拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined

var handler = {getOwnPropertyDescriptor (target, key) {return Object.getOwnPropertyDescriptor(target, key);}
};
var target = {a: 'a' };
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'a')
// { value: 'a', writable: true, enumerable: true, configurable: true }

9. getPrototypeOf

用来拦截获取对象原型。

var proto = {};
var p = new Proxy({}, {getPrototypeOf(target) {return proto;}
});
Object.getPrototypeOf(p) === proto // true

10. isExtensible

拦截Object.isExtensible操作。

var p = new Proxy({}, {isExtensible: function(target) {console.log("called");return true;}
});
Object.isExtensible(p)
// "called"
// true

11. ownKeys

拦截对象自身属性的读取操作。

let target = {a: 1,b: 2,c: 3
};
let handler = {ownKeys(target) {return ['a'];}
};
let proxy = new Proxy(target, handler);
Object.keys(proxy)
// [ 'a' ]

12. preventExtensions

拦截Object.preventExtensions()

var proxy = new Proxy({}, {preventExtensions: function(target) {return true;}
});
Object.preventExtensions(proxy)
// Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible

13. setPrototypeOf

用来拦截Object.setPrototypeOf方法。

var handler = {setPrototypeOf (target, proto) {throw new Error('Changing the prototype is forbidden');}
};
var proto = {};
var target = function () {};
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// Error: Changing the prototype is forbidden

只要修改target的原型对象,就会报错。

proxyObject.defineProperty具有的优势

了解Proxy的基本用法过后接下来我们再深入探讨一下相比于Object.defineProperty, Proxy到底有哪些优势。

首先最明显的优势就是在于Proxy要更为强大一些,那这个强大具体体现在Object.defineProperty只能监听到对象属性的读取或者是写入,而Proxy除读写外还可以监听对象中属性的删除,对对象当中方法的调用等等。

这里我们为obj对象定义一个Proxy对象,在Proxy对象的处理对象中的外的添加一个deleteProperty的代理方法,这个方法会在外部对当前这个代理对象进行delete操作时会自动执行。

这个方法同样接收两个参数,分别是代理目标对象和所要删除的这个属性的名称。

const obj = {a: 1
}
const proxy = new Proxy(obj, {deleteProperty(target, propKey) {delete target[propKey]}
})delete proxy.a

vue2.x的响应式并不具备这样的处理,而是需要一个$delete方法来解决。

export default {data() {return {obj: {a: 1}}},methods: {deleteA() {delete this.obj.a;    // a属性确实删除了,但是没有触发对应的更新// this.$delete(this.obj, 'a')  能删除a,并触发更新}}
}

比如上方的使用delete直接删除a,并不会触发更新,这是因为Object.defineProperty只能监听到对象属性的读取或者是写入,没办法监听删除。我们顺便来看看vue2.x的解决办法:

function del(target: any[] | object, key: any) {// ...if (isArray(target) && isValidArrayIndex(key)) {target.splice(key, 1)return}const ob = (target as any).__ob__// ...delete target[key]if (!ob) {return}// 获取对象的观察者__ob__,并手动调用更新if (__DEV__) {ob.dep.notify({type: TriggerOpTypes.DELETE,target: target,key})} else {ob.dep.notify()}
}

可以看到 vue 实例的 $delete 处理了一些边界情况,处理了数组和对象的删除,最重要的是,获取了变量上的ob指针指向的观察者实例,并通知依赖dep里的watcher更新。

第二点优势就是对于数组对象进行监视,

通常我们想要监视数组的变化,基本要依靠重写数组方法,这也是vue2.x的实现方式,proxy可以直接监视数组的变化。以往我们想要通过Object.defineProperty去监视数组的操作最常见的方式是重写数组的操作方法,大体的方式就是通过自定义的方法去覆盖掉数组原型对象上的pushshift之类的方法,以此来劫持对应的方法调用的过程。

这里来看如何直接使用Proxy对象来对数组进行监视。这里我们定义一个list数组,然后对这个list数组进行Proxy监视。

在这个Proxy对象的处理对象上我们去添加一个set方法,用于监视数据的写入,在这个方法的内部我们打印参数的值,然后再target对象上设置传入的值,最后返回一个true表示写入成功。

这样我们再外部对数组的写入都会被监视到,例如我们这里通过push向数组中添加值。

const arr = []
const proxy = new Proxy(arr, {set(target, propKey, value) {target[propkey] = value;return true}
})

Proxy内部会自动根据push操作推断出来他所处的下标,每次添加或者设置都会定位到对应的下标property

最后相比于Object.defineProperty还有一点优势就是,Proxy是以非入侵的方式监管了对象的读写,那也就是说一个已经定义好的对象我们不需要对对象本身去做任何的操作,就可以监视到他内部成员的读写,而defineProperty的方式就要求我们必须按特定的方式单独去定义对象当中那些被监视的属性。

proxy详细介绍与使用相关推荐

  1. Vue详细介绍及使用

    Vue详细介绍及使用 一.Vue定义及简介 1.Vue定义 关于Vue简介,百度百科给出的解释是:Vue.js是一套构建用户界面的渐进式框架.与其他重量级框架不同的是,Vue 采用自底向上增量开发的设 ...

  2. TeamTalk 详细介绍

    TeamTalk 详细介绍 项目背景 蘑菇街能有今天的快速发展,得益于开源软件群雄崛起的大环境背景,我们一直对开源社区怀有感恩之情,因此也一直希望能为开源社区贡献一份力量. 2013年我们蘑菇街从社区 ...

  3. 全网详细介绍nginx的反向代理、正向代理配置,location的指令说明,反向代理的两个示例代码以及全局块,events块和http快的说明。

    文章目录 1. 文章引言 2. 何谓反向代理 3. 解析nginx的配置文件 3.1 全局块(global block) 3.2 events块(events block) 3.3 http块(htt ...

  4. 常用设计模式大全-详细介绍

    设计模式(Design Patterns) --可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  5. Nginx超详细介绍 nginx入门 nginx配置 nginx详解 nginx优化

    # 此文加上部署课程一起 nginx文档 nginx配置基本全了 nginx七种状态(tcp是11种状态) #nginx七种状态 Active connections: 2 server accept ...

  6. ES6详细介绍及使用

    ES6详细介绍及使用 一. ES6概念及发展史 1.ES6概念 以前学习JavaScript的时候,对ES5是有了解过的,但是在学习Vue的时候,就会发现有很多新的写法是ES6中的,真是让人捉急.所以 ...

  7. HTML页面加载和解析流程详细介绍

    浏览器加载和渲染html的顺序.如何加快HTML页面加载速度.HTML页面加载和解析流程等等,在本文将为大家详细介绍下,感兴趣的朋友不要错过 浏览器加载和渲染html的顺序 1. IE下载的顺序是从上 ...

  8. mysql为什么要压测_mysql集群压测的详细介绍

    本篇文章给大家带来的内容是关于mysql集群压测的详细介绍,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. mysql压测 mysql自带就有一个叫mysqlslap的压力测试工具,通 ...

  9. php比较运算符案列,PHP实例:PHP比较运算符的详细介绍

    <PHP实例:PHP比较运算符的详细介绍>要点: 本文介绍了PHP实例:PHP比较运算符的详细介绍,希望对您有用.如果有疑问,可以联系我们. 比拟运算符种类 PHP实战如同它们名称所暗示的 ...

最新文章

  1. [翻译]StructureMap 指南 – .NET中的依赖注入和控制反转
  2. 清华大学2017计算机录取分数线,2017年清华大学各省各批次录取分数线
  3. complex--创建复数
  4. 【UGV】Arduino Mega2560 获取小车角度信息,传感器JY60
  5. C#正则表达式编程(二):Regex类用法
  6. linux下anaconda3安装教程,Ubuntu18.04 安装 Anaconda3的教程详解
  7. // D:\SaveLog\_SaveLog.dpr立即备份晓亮的电脑操作记录热键(快捷键) F11由于原来的 AutoIt 杀毒软件总是误报没办法只好麻烦一点用 Delphi XE4 做了...
  8. java char数组查找_我爱java系列---【在字符数组中查询某个字符串是否存在】
  9. AWT_Swing_单选框(Java)
  10. Makefile文件(一)_介绍
  11. FIR数字滤波器的FPGA实现
  12. 机器学习_深度学习毕设题目汇总——数据分析_数据挖掘
  13. wps如何设置分段页眉
  14. html遇到英文单词整体换行,HTML在table中如何强制单词换行
  15. 职场漂流记:过往皆为浮云
  16. 初学Python-反射 装饰器 面向对象编程 异常处理 断言
  17. Unity适配iphone刘海屏
  18. python-今日头条
  19. android在root权限下实现apk的静默卸载,静默安装,重启
  20. Matlab/simulink 车辆七自由度平顺性仿真

热门文章

  1. 西川中学学生管理系统(伪)1.1版本
  2. C++命名空间(名字空间)详解
  3. 完全过TP源码(转)
  4. docker CMD ENTRYPOINT区别
  5. 2020那些搭载Imagination IP的设备(海外篇)
  6. 微信小程序 | 小鸡单词2.0
  7. MATLAB常用的快捷键
  8. 一个大三学生的学习生活之感
  9. 揭秘LOL背后的IT基础架构丨产品而非服务
  10. Bootstrap样式插件/响应式布局/旅游网案例