快速掌握ES6的代理和反射

Reflect(反射)


Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect的设计目的:

  1. 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。

  2. 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。

  3. 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。

  4. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

代理与反射API


get()

接收参数:

  • target:目标对象。

  • property:引用的目标对象上的字符串键属性。

  • receiver:代理对象或继承代理对象的对象。

返回:

  • 返回值无限制

get()捕获器会在获取属性值的操作中被调用。对应的反射 API 方法为 Reflect.get()。

const myTarget = {};

const proxy = new Proxy(myTarget, {

get(target, property, receiver) {

console.log(‘get()’);

return Reflect.get(…arguments)

}

});

proxy.foo;

// get()

set()

接收参数:

  • target:目标对象。

  • property:引用的目标对象上的字符串键属性。

  • value:要赋给属性的值。

  • receiver:接收最初赋值的对象。

返回:

  • 返回 true 表示成功;返回 false 表示失败,严格模式下会抛出 TypeError。

set()捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set()。

const myTarget = {};

const proxy = new Proxy(myTarget, {

set(target, property, value, receiver) {

console.log(‘set()’);

return Reflect.set(…arguments)

}

});

proxy.foo = ‘bar’;

// set()

has()

接收参数:

  • target:目标对象。

  • property:引用的目标对象上的字符串键属性。

返回:

  • has()必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。

has()捕获器会在 in 操作符中被调用。对应的反射 API 方法为 Reflect.has()。

const myTarget = {};

const proxy = new Proxy(myTarget, {

has(target, property) {

console.log(‘has()’);

return Reflect.has(…arguments)

}

});

‘foo’ in proxy;

// has()

defineProperty()

Reflect.defineProperty方法基本等同于Object.defineProperty,用来为对象定义属性。

接收参数:

  • target:目标对象。

  • property:引用的目标对象上的字符串键属性。

  • descriptor:包含可选的 enumerable、configurable、writable、value、get 和 set定义的对象。

返回:

  • defineProperty()必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值。

const myTarget = {};

const proxy = new Proxy(myTarget, {

defineProperty(target, property, descriptor) {

console.log(‘defineProperty()’);

return Reflect.defineProperty(…arguments)

}

});

Object.defineProperty(proxy, ‘foo’, { value: ‘bar’ });

// defineProperty()

getOwnPropertyDescriptor()

Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象。

接收参数:

  • target:目标对象。

  • property:引用的目标对象上的字符串键属性。

返回:

  • getOwnPropertyDescriptor()必须返回对象,或者在属性不存在时返回 undefined。

const myTarget = {};

const proxy = new Proxy(myTarget, {

getOwnPropertyDescriptor(target, property) {

console.log(‘getOwnPropertyDescriptor()’);

return Reflect.getOwnPropertyDescriptor(…arguments)

}

});

Object.getOwnPropertyDescriptor(proxy, ‘foo’);

// getOwnPropertyDescriptor()

deleteProperty()

Reflect.deleteProperty方法等同于delete obj[name],用于删除对象的属性。

接收参数:

  • target:目标对象。

  • property:引用的目标对象上的字符串键属性。

返回:

  • deleteProperty()必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。

ownKeys()

Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和。

接收参数:

  • target:目标对象。

返回:

  • ownKeys()必须返回包含字符串或符号的可枚举对象。

getPrototypeOf()

Reflect.getPrototypeOf方法用于读取对象的__proto__属性

接收参数:

  • target:目标对象。

返回:

  • getPrototypeOf()必须返回对象或 null。

等等。。

代理模式


跟踪属性访问

通过捕获 get、set 和 has 等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过:

const user = {

name: ‘Jake’

};

const proxy = new Proxy(user, {

get(target, property, receiver) {

console.log(Getting ${property});

return Reflect.get(…arguments);

},

set(target, property, value, receiver) {

console.log(Setting ${property}=${value});

return Reflect.set(…arguments);

}

});

proxy.name; // Getting name

proxy.age = 27; // Setting age=27

隐藏属性

代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举。

const hiddenProperties = [‘foo’, ‘bar’];

const targetObject = {

foo: 1,

bar: 2,

baz: 3

};

const proxy = new Proxy(targetObject, {

get(target, property) {

if (hiddenProperties.includes(property)) {

return undefined;

} else {

return Reflect.get(…arguments);

}

},

has(target, property) {

if (hiddenProperties.includes(property)) {

return false;

} else {

return Reflect.has(…arguments);

}

}

});

// get()

console.log(proxy.foo); // undefined

console.log(proxy.bar); // undefined

console.log(proxy.baz); // 3

// has()

console.log(‘foo’ in proxy); // false

console.log(‘bar’ in proxy); // false

console.log(‘baz’ in proxy); // true

属性验证

因为所有赋值操作都会触发 set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值:

const target = {

onlyNumbersGoHere: 0

};

const proxy = new Proxy(target, {

set(target, property, value) {

if (typeof value !== ‘number’) {

return false;

} else {

return Reflect.set(…arguments);

}

}

});

proxy.onlyNumbersGoHere = 1;

console.log(proxy.onlyNumbersGoHere); // 1

proxy.onlyNumbersGoHere = ‘2’;

console.log(proxy.onlyNumbersGoHere); // 1

函数与构造函数参数验证

跟保护和验证对象属性类似,也可对函数和构造函数参数进行审查。比如,可以让函数只接收某种类型的值:

function median(…nums) {

return nums.sort()[Math.floor(nums.length / 2)];

}

const proxy = new Proxy(median, {

apply(target, thisArg, argumentsList) {

for (const arg of argumentsList) {

if (typeof arg !== ‘number’) {

throw ‘Non-number argument provided’;

}

}

return Reflect.apply(…arguments);

}

});

console.log(proxy(4, 7, 1)); // 4

console.log(proxy(4, ‘7’, 1));

// Error: Non-number argument provided

类似地,可以要求实例化时必须给构造函数传参:

class User {

constructor(id) {

this.id_ = id;

}

}

const proxy = new Proxy(User, {

construct(target, argumentsList, newTarget) {

if (argumentsList[0] === undefined) {

throw ‘User cannot be instantiated without id’;

} else {

return Reflect.construct(…arguments);

}

}

});

new proxy(1);

new proxy();

// Error: User cannot be instantiated without id

数据绑定与可观察对象

通过代理可以把运行时中原本不相关的部分联系到一起。这样就可以实现各种模式,从而让不同的代码互操作。比如,可以将被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中:

const userList = [];

class User {

constructor(name) {

this.name_ = name;

}

}

const proxy = new Proxy(User, {

construct() {

const newUser = Reflect.construct(…arguments);

userList.push(newUser);

return newUser;

}

});

new proxy(‘John’);

new proxy(‘Jacob’);

new proxy(‘Jingleheimerschmidt’);

console.log(userList); // [User {}, User {}, User{}]

另外,还可以把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息:

const userList = [];

function emit(newValue) {

console.log(newValue);

}

const proxy = new Proxy(userList, {

set(target, property, value, receiver) {

const result = Reflect.set(…arguments);

if (result) {

emit(Reflect.get(target, property, receiver));

}

return result;

}

});

proxy.push(‘John’);

// John

proxy.push(‘Jacob’);

// Jacob

使用 Proxy 实现观察者模式

const queuedObservers = new Set();

const observe = fn => queuedObservers.add(fn);

const observable = obj => new Proxy(obj, {set});

function set(target, key, value, receiver) {

const result = Reflect.set(target, key, value, receiver);

queuedObservers.forEach(observer => observer());

return result;

}

const person = observable({

name: ‘张三’,

age: 20

});

function print() {

console.log(${person.name}, ${person.age})

}

observe(print);

person.name = ‘李四’;

// 输出

// 李四, 20

结尾

本文主要参考阮一峰es6教程、js红宝书第四版

由于本人水平有限,如有错误,敬请与我联系指出,谢谢。

原文:https://segmentfault.com/a/1190000039956559

作者:greet_eason

END

推荐阅读  点击标题可跳转

24个解决实际问题的ES6代码段

11个 Javascript 小技巧【文末送书】

改善代码可读性的5种方法

关注下方「前端开发博客」,回复 “加群”

加入我们一起学习,天天进步


如果觉得这篇文章还不错,来个【分享、点赞、在看】三连吧,让更多的人也看到~

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
题可跳转

24个解决实际问题的ES6代码段

11个 Javascript 小技巧【文末送书】

改善代码可读性的5种方法

关注下方「前端开发博客」,回复 “加群”

加入我们一起学习,天天进步


如果觉得这篇文章还不错,来个【分享、点赞、在看】三连吧,让更多的人也看到~

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-3ngaC6eR-1715245752890)]

[外链图片转存中…(img-rBM7NU5R-1715245752891)]

[外链图片转存中…(img-hT2SZPb6-1715245752891)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值