随着前端界的空前繁荣,各类框架横空出世,包括各种mvvm框架百家争鸣,好比Anglar、Vue、React等等,它们最大的优势就是能够实现数据绑定,不再须要手动进行DOM操做了,它们实现的原理也基本上是脏检查或数据劫持。咱们先以Vue框架出发,探索其中数据劫持的奥秘。前端
Vue 2.0的版本所使用的数据劫持,说白了就是经过Object.defineProperty()来劫持对象属性的setter和getter操做,在数据变更时作你想要作的事情,举个栗子:webpack
var data = { name:'xiaoming' } Object.keys(data).forEach(function(key){ Object.defineProperty(data,key,{ get:function(){ console.log('get'); }, set:function(){ console.log('监听到数据发生了变化'); } }) }); data.name //控制台会打印出 “get” data.name = 'xiaohong' //控制台会打印出 "监听到数据发生了变化"
可是有没有比Object.defineProperty更好的实现方式呢?web
答案是确定的有,那就是咱们今天的主人公:Proxy编程
一、Proxy简介数组
Proxy这个词的原意是代理,用在这里表示由它来代理某些操做,能够译为代理器。babel
也能够理解成在目标对象以前设置一层拦截,外界对该对象的访问,都必须先经过这层拦截,所以提供了一种机制,能够对外界的访问进行过滤和改写。框架
在生活中,代理模式的场景是十分常见的,例如咱们如今若是有购买海外产品(给女友买一个LV的包包,前提是你要先有个女友,^_^)的需求,更多的是去找代购中介机构,而不是直接去国外买。此时,代购起到的做用就是代理的做用。mvvm
Proxy构造函数可以让咱们轻松的使用代理模式:函数
var proxy = new Proxy(target, handler);
Proxy构造函数中有两个参数:spa
target是用Proxy包装的被代理对象(能够是任何类型的对象,包括原生数组,函数,甚至另外一个代理)。
handler是一个对象,其声明了代理target 的一些操做,其属性是当执行一个操做时定义代理的行为的函数。
讲的通俗点,如何让代购帮你买LV的包包呢?
首先,你须要告诉代购你看好了哪款包包,这个款式就是Proxy里的第一个参数target。
其次就是制定购买策略,例如国外比国内便宜20%,就买2个,便宜40%,就买4个,这个策略就是第二个参数handle。
二、Proxy中的处理方法
Proxy有13种数据劫持的操做,那是至关的强大:
2.1 get方法
get方法是在你获得某对象属性值时预处理的方法,接受两个经常使用参数
能够代购来模拟handle中的get方法,以下
var Bao = { name: "LV", price:9999, }; var proxyBao = new Proxy(Bao, { get: function(target, key) { if (target['price']>5000) { return '超出客户心理价位,不买了'; } else { return '符合客户心理预期,买买买'; } } }); proxyBao.price //"超出客户心理价位,不买了"
解释一下:客户想买一个LV的包,心理价位是5000,把购买目标和需求都告诉了代购,代购询问了下国外的价格,这款LV的包是9999,超出了客户的心理价位,因而不买了。
2.2 set方法
set方法用来拦截某个属性的赋值操做,能够接受四个参数
假定Person对象有一个age属性,该属性应该是一个不大于 200 的整数,那么可使用Proxy保证age的属性值符合要求。
let validator = { set: function(target, key, value) { if (key === '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 属性以及其余属性,直接保存 target[key] = value; } }; let person = new Proxy({}, validator); person.age = 100; person.age // 100 person.age = 'young' // 报错 The age is not an integer person.age = 300 // 报错 The age seems invalid
上面代码中,因为设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。
三、Proxy相比Object.defineProperty的优点
3.1 支持数组
let arr = [1,2,3] let proxy = new Proxy(arr, { get (target, key, receiver) { console.log('get', key) return Reflect.get(target, key, receiver) }, set (target, key, value, receiver) { console.log('set', key, value) return Reflect.set(target, key, value, receiver) } }) proxy.push(4) // 可以打印出不少内容 // get push (寻找 proxy.push 方法) // get length (获取当前的 length) // set 3 4 (设置 proxy[3] = 4) // set length 4 (设置 proxy.length = 4)
Proxy 不须要对数组的方法进行重载,省去了众多 hack,减小代码量等于减小了维护成本,并且标准的就是最好的。
3.2 针对对象
在数据劫持这个问题上,Proxy 能够被认为是 Object.defineProperty() 的升级版。外界对某个对象的访问,都必须通过这层拦截。所以它是针对 整个对象,而不是 对象的某个属性,因此也就不须要对 keys 进行遍历。
let obj = { name: 'Eason', age: 30 } let handler = { get (target, key, receiver) { console.log('get', key) return Reflect.get(target, key, receiver) }, set (target, key, value, receiver) { console.log('set', key, value) return Reflect.set(target, key, value, receiver) } } let proxy = new Proxy(obj, handler) proxy.name = 'Zoe' // set name Zoe proxy.age = 18 // set age 18
3.3 嵌套支持
本质上,Proxy 也是不支持嵌套的,这点和 Object.defineProperty() 是同样的。所以也须要经过逐层遍从来解决。Proxy 的写法是在 get 里面递归调用 Proxy 并返回,代码以下:
let obj = { info: { name: 'eason', blogs: ['webpack', 'babel', 'cache'] } } let handler = { get (target, key, receiver) { console.log('get', key) // 递归建立并返回 if (typeof target[key] === 'object' && target[key] !== null) { return new Proxy(target[key], handler) } return Reflect.get(target, key, receiver) }, set (target, key, value, receiver) { console.log('set', key, value) return Reflect.set(target, key, value, receiver) } } let proxy = new Proxy(obj, handler) // 如下两句都可以进入 set proxy.info.name = 'Zoe' proxy.info.blogs.push('proxy')
四、应用实例
4.1 使用Proxy实现表单校验
let person = { name: 'xiaoming', age: 30 } let handler = { set (target, key, value, receiver) { if (key === 'name' && typeof value !== 'string') { throw new Error('用户姓名必须是字符串类型') } if (key === 'age' && typeof value !== 'number') { throw new Error('用户年龄必须是数字类型') } return Reflect.set(target, key, value, receiver) } } let boy = new Proxy(person, handler) boy.name = 'xiaohong' // OK boy.age = '18' // 报错 用户年龄必须是数字类型
五、总结
Proxy本质上属于元编程非破坏性数据劫持,在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念。
通俗的说Proxy在数据外层套了个壳,而后经过这层壳访问内部的数据,就像下面的图:
Proxy让JS开发者很方便的使用代理模式,使函数更增强大,业务逻辑更加清楚。
Proxy 不但能够取代 Object.defineProperty 而且还扩增了很是多的功能。Proxy 技术支持监测数组的 push 等方法操做,支持对象属性的动态添加和删除,极大的简化了响应化的代码量。Vue 3.0的也会使用Proxy去实现部分核心代码。
在业务开发时应该注意Proxy使用场景,当对象的功能变得复杂或者咱们须要进行必定的访问限制时,即可以考虑使用代理。