Reflect(反射)
Reflect
对象与Proxy
对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect的设计目的:
-
将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。
-
修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
-
让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
-
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
推荐阅读 点击标题可跳转
关注下方「前端开发博客」,回复 “加群”
加入我们一起学习,天天进步
如果觉得这篇文章还不错,来个【分享、点赞、在看】三连吧,让更多的人也看到~
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
题可跳转
关注下方「前端开发博客」,回复 “加群”
加入我们一起学习,天天进步
如果觉得这篇文章还不错,来个【分享、点赞、在看】三连吧,让更多的人也看到~
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-3ngaC6eR-1715245752890)]
[外链图片转存中…(img-rBM7NU5R-1715245752891)]
[外链图片转存中…(img-hT2SZPb6-1715245752891)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!