参考答案:
定义: 用于定义基本操作的自定义行为
本质: 修改的是程序默认形为,就形同于在编程语言层面上做修改,属于元编程(meta programming)
元编程(Metaprogramming,又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作
一段代码来理解
1#!/bin/bash 2# metaprogram 3echo '#!/bin/bash' >program 4for ((I=1; I<=1024; I++)) do 5 echo "echo $I" >>program 6done 7chmod +x program
这段程序每执行一次能帮我们生成一个名为program
的文件,文件内容为1024行echo
,如果我们手动来写1024行代码,效率显然低效
Proxy
亦是如此,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
Proxy
为 构造函数,用来生成 Proxy
实例
1var proxy = new Proxy(target, handler)
target
表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))
handler
通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p
的行为
关于handler
拦截属性,有如下:
propKey in proxy
的操作,返回一个布尔值delete proxy[propKey]
的操作,返回一个布尔值Object.keys(proxy)
、for...in
等循环,返回一个数组Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象Object.defineProperty(proxy, propKey, propDesc)
,返回一个布尔值Object.preventExtensions(proxy)
,返回一个布尔值Object.getPrototypeOf(proxy)
,返回一个对象Object.isExtensible(proxy)
,返回一个布尔值Object.setPrototypeOf(proxy, proto)
,返回一个布尔值若需要在Proxy
内部调用对象的默认行为,建议使用Reflect
,其是ES6
中操作对象而提供的新 API
基本特点:
Proxy
对象具有的代理方法,Reflect
对象全部具有,以静态方法的形式存在Object
方法的返回结果,让其变得更合理(定义不存在属性行为的时候不报错而是返回false
)Object
操作都变成函数行为下面我们介绍proxy
几种用法:
get
接受三个参数,依次为目标对象、属性名和 proxy
实例本身,最后一个参数可选
1var person = { 2 name: "张三" 3}; 4 5var proxy = new Proxy(person, { 6 get: function(target, propKey) { 7 return Reflect.get(target,propKey) 8 } 9}); 10 11proxy.name // "张三"
get
能够对数组增删改查进行拦截,下面是试下你数组读取负数的索引
1function createArray(...elements) { 2 let handler = { 3 get(target, propKey, receiver) { 4 let index = Number(propKey); 5 if (index < 0) { 6 propKey = String(target.length + index); 7 } 8 return Reflect.get(target, propKey, receiver); 9 } 10 }; 11 12 let target = []; 13 target.push(...elements); 14 return new Proxy(target, handler); 15} 16 17let arr = createArray('a', 'b', 'c'); 18arr[-1] // c
注意:如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则会报错
1const target = Object.defineProperties({}, { 2 foo: { 3 value: 123, 4 writable: false, 5 configurable: false 6 }, 7}); 8 9const handler = { 10 get(target, propKey) { 11 return 'abc'; 12 } 13}; 14 15const proxy = new Proxy(target, handler); 16 17proxy.foo 18// TypeError: Invariant check failed
set
方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy
实例本身
假定Person
对象有一个age
属性,该属性应该是一个不大于 200 的整数,那么可以使用Proxy
保证age
的属性值符合要求
1let validator = { 2 set: function(obj, prop, value) { 3 if (prop === 'age') { 4 if (!Number.isInteger(value)) { 5 throw new TypeError('The age is not an integer'); 6 } 7 if (value > 200) { 8 throw new RangeError('The age seems invalid'); 9 } 10 } 11 12 // 对于满足条件的 age 属性以及其他属性,直接保存 13 obj[prop] = value; 14 } 15}; 16 17let person = new Proxy({}, validator); 18 19person.age = 100; 20 21person.age // 100 22person.age = 'young' // 报错 23person.age = 300 // 报错
如果目标对象自身的某个属性,不可写且不可配置,那么set
方法将不起作用
1const obj = {}; 2Object.defineProperty(obj, 'foo', { 3 value: 'bar', 4 writable: false, 5}); 6 7const handler = { 8 set: function(obj, prop, value, receiver) { 9 obj[prop] = 'baz'; 10 } 11}; 12 13const proxy = new Proxy(obj, handler); 14proxy.foo = 'baz'; 15proxy.foo // "bar"
注意,严格模式下,set
代理如果没有返回true
,就会报错
1'use strict'; 2const handler = { 3 set: function(obj, prop, value, receiver) { 4 obj[prop] = receiver; 5 // 无论有没有下面这一行,都会报错 6 return false; 7 } 8}; 9const proxy = new Proxy({}, handler); 10proxy.foo = 'bar'; 11// TypeError: 'set' on proxy: trap returned falsish for property 'foo'
deleteProperty
方法用于拦截delete
操作,如果这个方法抛出错误或者返回false
,当前属性就无法被delete
命令删除
1var handler = { 2 deleteProperty (target, key) { 3 invariant(key, 'delete'); 4 Reflect.deleteProperty(target,key) 5 return true; 6 } 7}; 8function invariant (key, action) { 9 if (key[0] === '_') { 10 throw new Error(`无法删除私有属性`); 11 } 12} 13 14var target = { _prop: 'foo' }; 15var proxy = new Proxy(target, handler); 16delete proxy._prop 17// Error: 无法删除私有属性
注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty
方法删除,否则报错
Proxy.revocable(target, handler);
Proxy
其功能非常类似于设计模式中的代理模式,常用功能如下:
使用 Proxy
保障数据类型的准确性
1let numericDataStore = { count: 0, amount: 1234, total: 14 }; 2numericDataStore = new Proxy(numericDataStore, { 3 set(target, key, value, proxy) { 4 if (typeof value !== 'number') { 5 throw Error("属性只能是number类型"); 6 } 7 return Reflect.set(target, key, value, proxy); 8 } 9}); 10 11numericDataStore.count = "foo" 12// Error: 属性只能是number类型 13 14numericDataStore.count = 333 15// 赋值成功
声明了一个私有的 apiKey
,便于 api
这个对象内部的方法调用,但不希望从外部也能够访问 api._apiKey
1let api = { 2 _apiKey: '123abc456def', 3 getUsers: function(){ }, 4 getUser: function(userId){ }, 5 setUser: function(userId, config){ } 6}; 7const RESTRICTED = ['_apiKey']; 8api = new Proxy(api, { 9 get(target, key, proxy) { 10 if(RESTRICTED.indexOf(key) > -1) { 11 throw Error(`${key} 不可访问.`); 12 } return Reflect.get(target, key, proxy); 13 }, 14 set(target, key, value, proxy) { 15 if(RESTRICTED.indexOf(key) > -1) { 16 throw Error(`${key} 不可修改`); 17 } return Reflect.get(target, key, value, proxy); 18 } 19}); 20 21console.log(api._apiKey) 22api._apiKey = '987654321' 23// 上述都抛出错误
还能通过使用Proxy
实现观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行
observable
函数返回一个原始对象的 Proxy
代理,拦截赋值操作,触发充当观察者的各个函数
1const queuedObservers = new Set(); 2 3const observe = fn => queuedObservers.add(fn); 4const observable = obj => new Proxy(obj, {set}); 5 6function set(target, key, value, receiver) { 7 const result = Reflect.set(target, key, value, receiver); 8 queuedObservers.forEach(observer => observer()); 9 return result; 10}
观察者函数都放进Set
集合,当修改obj
的值,在会set
函数中拦截,自动执行Set
所有的观察者
最近更新时间:2024-07-20