|
| 1 | +## Proxy |
| 2 | + |
| 3 | +> 定义: 用于定义基本操作的自定义行为 |
| 4 | +
|
| 5 | +由于`proxy`修改的是程序默认形为,就形同于在编程语言层面上做修改,属于元编程(`meta` `programming`) |
| 6 | + |
| 7 | +**元编程(英语:Metaprogramming**,又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作 |
| 8 | + |
| 9 | +一段代码来理解元编程 |
| 10 | +```bash |
| 11 | +#!/bin/bash |
| 12 | +# metaprogram |
| 13 | +echo '#!/bin/bash' >program |
| 14 | +for ((I=1; I<=1024; I++)) do |
| 15 | + echo "echo $I" >>program |
| 16 | +done |
| 17 | +chmod +x program |
| 18 | +``` |
| 19 | +这段程序每执行一次能帮我们生成一个名为program的文件,文件内容为1024行`echo`,如果我们手动来写1024行代码,显然就很低效 |
| 20 | + |
| 21 | +元编程优点:与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译 |
| 22 | + |
| 23 | +`proxy`音译为代理,在操作目标对象前架设一层代理,将所有本该我们手动编写的程序交由代理来处理,通俗点可以理解为生活的代购,中介服务,所有的行为都不会直接触达目标对象 |
| 24 | + |
| 25 | +### 语法 |
| 26 | +```js |
| 27 | +/ |
| 28 | +* target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理 |
| 29 | +* handler 一个通常以函数作为属性的对象,用来定制拦截行为 |
| 30 | +*/ |
| 31 | +const proxy = new Proxy(target, handle) |
| 32 | +``` |
| 33 | + |
| 34 | +举个例子 |
| 35 | +```js |
| 36 | +const origin = {} |
| 37 | +const obj = new Proxy(origin, { |
| 38 | + get: function (target, propKey, receiver) { |
| 39 | + return '10' |
| 40 | + } |
| 41 | +}); |
| 42 | + |
| 43 | +obj.a // 10 |
| 44 | +obj.b // 10 |
| 45 | +origin.a // undefined |
| 46 | +origin.b // undefined |
| 47 | +``` |
| 48 | +上方代码我们给一个空对象的get架设了一层代理,所有`get`操作都会直接返回我们定制的数字10,需要注意的是,代理只会对`proxy`对象生效,如上方的`origin`就没有任何效果 |
| 49 | + |
| 50 | +### Handler 对象常用的方法 |
| 51 | + |
| 52 | +| 方法 | 描述 | |
| 53 | +| ---- | ---- | |
| 54 | +| handler.has() | in 操作符的捕捉器。| |
| 55 | +| handler.get() | 属性读取操作的捕捉器。| |
| 56 | +| handler.set() | 属性设置操作的捕捉器。| |
| 57 | +| handler.deleteProperty() | delete 操作符的捕捉器。| |
| 58 | +| handler.ownKeys() | Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。| |
| 59 | +| handler.apply() | 函数调用操作的捕捉器。| |
| 60 | +| handler.construct() | new 操作符的捕捉器| |
| 61 | + |
| 62 | + |
| 63 | +下面挑`handler.get`重点讲一下,其它方法的使用也都大同小异,不同的是参数的区别 |
| 64 | + |
| 65 | +#### handler.get |
| 66 | + |
| 67 | +get我们在上面例子已经体验过了,这里再详细介绍一下,方法用于代理目标对象的属性读取操作 |
| 68 | + |
| 69 | +授受三个参数 `get(target, propKey, ?receiver) ` |
| 70 | +- target 目标对象 |
| 71 | +- propkey 属性名 |
| 72 | +- receiver Proxy实例本身 |
| 73 | + |
| 74 | +**举个例子** |
| 75 | +```js |
| 76 | +const person = { |
| 77 | + like: "vuejs" |
| 78 | +} |
| 79 | + |
| 80 | +const obj = new Proxy(person, { |
| 81 | + get: function(target, propKey) { |
| 82 | + if (propKey in target) { |
| 83 | + return target[propKey]; |
| 84 | + } else { |
| 85 | + throw new ReferenceError("Prop name \"" + propKey + "\" does not exist."); |
| 86 | + } |
| 87 | + } |
| 88 | +}) |
| 89 | + |
| 90 | +obj.like // vuejs |
| 91 | +obj.test // Uncaught ReferenceError: Prop name "test" does not exist. |
| 92 | +``` |
| 93 | +上面的代码表示在读取代理目标的值时,如果有值则直接返回,没有值就抛出一个自定义的错误 |
| 94 | + |
| 95 | +**注意:** |
| 96 | +- 如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同 |
| 97 | +- 如果要访问的目标属性没有配置访问方法,即get方法是undefined的,则返回值必须为undefined |
| 98 | + |
| 99 | +如下面的例子 |
| 100 | +```js |
| 101 | +const obj = {}; |
| 102 | +Object.defineProperty(obj, "a", { |
| 103 | + configurable: false, |
| 104 | + enumerable: false, |
| 105 | + value: 10, |
| 106 | + writable: false |
| 107 | +}) |
| 108 | + |
| 109 | +const p = new Proxy(obj, { |
| 110 | + get: function(target, prop) { |
| 111 | + return 20; |
| 112 | + } |
| 113 | +}) |
| 114 | + |
| 115 | +p.a // Uncaught TypeError: 'get' on proxy: property 'a' is a read-only and non-configurable.. |
| 116 | +``` |
| 117 | + |
| 118 | +### 可撤消的Proxy |
| 119 | + |
| 120 | +`proxy`有一个唯一的静态方法,`Proxy.revocable(target, handler) ` |
| 121 | + |
| 122 | +`Proxy.revocable()`方法可以用来创建一个可撤销的代理对象 |
| 123 | + |
| 124 | +该方法的返回值是一个对象,其结构为: `{"proxy": proxy, "revoke": revoke}` |
| 125 | + |
| 126 | +- proxy |
| 127 | +表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉。 |
| 128 | +- revoke |
| 129 | +撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。 |
| 130 | + |
| 131 | +该方法常用于完全封闭对目标对象的访问, 如下示例 |
| 132 | +```js |
| 133 | +const target = { name: 'vuejs'} |
| 134 | +const {proxy, revoke} = Proxy.revocable(target, handler) |
| 135 | +proxy.name // 正常取值输出 vuejs |
| 136 | +revoke() // 取值完成对proxy进行封闭,撤消代理 |
| 137 | +proxy.name // TypeError: Revoked |
| 138 | +``` |
| 139 | + |
| 140 | +### Proxy的应用场景 |
| 141 | + |
| 142 | +`Proxy`的应用范围很广,下方列举几个典型的应用场景 |
| 143 | +- **校验器** |
| 144 | + |
| 145 | +想要一个`number`,拿回来的却是`string`,头大不大?下面我们使用`Proxy`实现一个逻辑分离的数据格式验证器,嗯,真香! |
| 146 | + |
| 147 | +```js |
| 148 | +const target = { |
| 149 | + _id: '1024', |
| 150 | + name: 'vuejs' |
| 151 | +} |
| 152 | + |
| 153 | +const validators = { |
| 154 | + name(val) { |
| 155 | + return typeof val === 'string'; |
| 156 | + }, |
| 157 | + _id(val) { |
| 158 | + return typeof val === 'number' && val > 1024; |
| 159 | + } |
| 160 | +} |
| 161 | + |
| 162 | +const createValidator = (target, validator) => { |
| 163 | + return new Proxy(target, { |
| 164 | + _validator: validator, |
| 165 | + set(target, propkey, value, proxy){ |
| 166 | + let validator = this._validator[propkey](value) |
| 167 | + if(validator){ |
| 168 | + return Reflect.set(target, propkey, value, proxy) |
| 169 | + }else { |
| 170 | + throw Error(`Cannot set ${propkey} to ${value}. Invalid type.`) |
| 171 | + } |
| 172 | + } |
| 173 | + }) |
| 174 | +} |
| 175 | + |
| 176 | +const proxy = createValidator(target, validators) |
| 177 | + |
| 178 | +proxy.name = 'vue-js.com' // vue-js.com |
| 179 | +proxy.name = 10086 // Uncaught Error: Cannot set name to 10086. Invalid type. |
| 180 | +proxy._id = 1025 // 1025 |
| 181 | +proxy._id = 22 // Uncaught Error: Cannot set _id to 22. Invalid type |
| 182 | + |
| 183 | +``` |
| 184 | + |
| 185 | + |
| 186 | + |
| 187 | +- **防止对象的内部属性(私有属性)被外部读写** |
| 188 | + |
| 189 | +在日常编写代码的过程中,我们想定义一些私有属性,通常是在团队中进行约定,大家按照约定在变量名之前添加下划线 _ 或者其它格式来表明这是一个私有属性,但我们不能保证他能真私‘私有化’,下方示例使用Proxy轻松实现私有属性拦截 |
| 190 | + |
| 191 | +```js |
| 192 | +const target = { |
| 193 | + _id: '1024', |
| 194 | + name: 'vuejs' |
| 195 | +} |
| 196 | + |
| 197 | +const proxy = new Proxy(target, { |
| 198 | + get(target, propkey, proxy){ |
| 199 | + if(propkey[0] === '_'){ |
| 200 | + throw Error(`${propkey} is restricted`) |
| 201 | + } |
| 202 | + return Reflect.get(target, propkey, proxy) |
| 203 | + }, |
| 204 | + set(target, propkey, value, proxy){ |
| 205 | + if(propkey[0] === '_'){ |
| 206 | + throw Error(`${propkey} is restricted`) |
| 207 | + } |
| 208 | + return Reflect.get(target, propkey, value, proxy) |
| 209 | + } |
| 210 | +}) |
| 211 | + |
| 212 | +proxy.name // vuejs |
| 213 | +proxy._id // Uncaught Error: _id is restricted |
| 214 | +proxy._id = '1025' // Uncaught Error: _id is restricted |
| 215 | +``` |
| 216 | + |
| 217 | +`Proxy`的使用场景还有很多很多,这里也不再一一列举,只要你需要在某一个动作的生命周期内做一些特定的处理,那么`Proxy`都是适合你的 |
| 218 | + |
| 219 | +### Proxy 与 Object.defineProperty |
| 220 | +在 `Proxy` 出现之前,`JavaScript` 中就提供过 `Object.defineProperty`,允许对对象的 `getter/setter` 进行拦截 |
| 221 | + |
| 222 | +大家熟悉的Vue框架双向绑定的实现也由`defineProperty`重构为`Proxy`,那么两者的区别在究竟在哪里呢? |
| 223 | + |
| 224 | +请看下面这张图,图中所示的都是`defineProperty`现存的问题,而`Proxy`均已完美支持 |
| 225 | + |
| 226 | +  |
| 227 | + |
| 228 | +### 兼容 |
| 229 | +除了IE,其它浏览器最新版本均已支持 |
| 230 | + |
| 231 | + |
| 232 | + |
| 233 | +参考: |
| 234 | +- [https://zh.wikipedia.org/wiki/](https://zh.wikipedia.org/wiki/) |
| 235 | +- [https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy) |
| 236 | +- [https://es6.ruanyifeng.com/#docs/proxy#Proxy-revocable](https://es6.ruanyifeng.com/#docs/proxy#Proxy-revocable) |
| 237 | +- [https://juejin.im/post/5e78d908f265da57340267f7#heading-2](https://juejin.im/post/5e78d908f265da57340267f7#heading-2) |
0 commit comments