“睡服”面试官系列第十七篇之Reflect(建议收藏学习)

目录

 

1. 概述

2. 静态方法

2.1Reflect.get(target, name, receiver)

2.2Reflect.set(target, name, value, receiver)

2.3Reflect.has(obj, name)

2.4Reflect.deleteProperty(obj, name)

2.5Reflect.construct(target, args)

2.6Reflect.getPrototypeOf(obj)

2.7Reflect.setPrototypeOf(obj, newProto)

2.8Reflect.apply(func, thisArg, args)

2.9Reflect.defineProperty(target, propertyKey, attributes)

2.10Reflect.getOwnPropertyDescriptor(target, propertyKey)

2.11Reflect.isExtensible (target)

 2.12Reflect.preventExtensions(target)

2.13Reflect.ownKeys (target)

3. 实例:使用 Proxy 实现观察者模式


1. 概述

Reflect 对象与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 API。 Reflect 对象的设计目的有这样几个。
(1) 将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty ),放到 Reflect 对象上。现阶段,某些方法同时在 Object 和
Reflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法。
(2) 修改某些 Object 方法的返回结果,让其变得更合理。比如, Object.defineProperty(obj, name, desc) 在无法定义属性时,会抛出一个错误,而
Reflect.defineProperty(obj, name, desc) 则会返回 false 。

// 老写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}

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

// 老写法
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true

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

Proxy(target, {
set: function(target, name, value, receiver) {
var success = Reflect.set(target,name, value, receiver);
if (success) {
log('property ' + name + ' on ' + target + ' set to ' + value);
}
return success;
}
});

上面代码中, Proxy 方法拦截 target 对象的属性赋值行为。它采用 Reflect.set 方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功
能。
下面是另一个例子

var loggedObj = new Proxy(obj, {
get(target, name) {
console.log('get', target, name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log('delete' + name);
return Reflect.deleteProperty(target, name);
},
has(target, name) {
console.log('has' + name);
return Reflect.has(target, name);
}
});

上面代码中,每一个 Proxy 对象的拦截操作( get 、 delete 、 has ),内部都调用对应的 Reflect 方法,保证原生行为能够正常执行。添加的工作,就是
将每一个操作输出一行日志。
有了 Reflect 对象以后,很多操作会更易读。

// 老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
// 新写法
Reflect.apply(Math.floor, undefined, [1.75]) // 1

2. 静态方法

Reflect 对象一共有 13 个静态方法。

Reflect.apply(target, thisArg, args)
Reflect.construct(target, args)
Reflect.get(target, name, receiver)
Reflect.set(target, name, value, receiver)
Reflect.defineProperty(target, name, desc)
Reflect.deleteProperty(target, name)
Reflect.has(target, name)
Reflect.ownKeys(target)
Reflect.isExtensible(target)
Reflect.preventExtensions(target)
Reflect.getOwnPropertyDescriptor(target, name)
Reflect.getPrototypeOf(target)
Reflect.setPrototypeOf(target, prototype)

上面这些方法的作用,大部分与 Object 对象的同名方法的作用都是相同的,而且它与 Proxy 对象的方法是一一对应的。下面是对它们的解释。

2.1Reflect.get(target, name, receiver)

Reflect.get 方法查找并返回 target 对象的 name 属性,如果没有该属性,则返回 undefined 。

var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
}
Reflect.get(myObject, 'foo') // 1
Reflect.get(myObject, 'bar') // 2
Reflect.get(myObject, 'baz') // 3

如果 name 属性部署了读取函数(getter),则读取函数的 this 绑定 receiver 。

var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
};
var myReceiverObject = {
foo: 4,
bar: 4,
};
Reflect.get(myObject, 'baz', myReceiverObject) // 8

如果第一个参数不是对象, Reflect.get 方法会报错。

Reflect.get(1, 'foo') // 报错
Reflect.get(false, 'foo') // 报错

2.2Reflect.set(target, name, value, receiver)

Reflect.set 方法设置 target 对象的 name 属性等于 value

var myObject = {
foo: 1,
set bar(value) {
return this.foo = value;
},
}
myObject.foo // 1
Reflect.set(myObject, 'foo', 2);
myObject.foo // 2
Reflect.set(myObject, 'bar', 3)
myObject.foo // 3

如果 name 属性设置了赋值函数,则赋值函数的 this 绑定 receiver 。

var myObject = {
foo: 4,
set bar(value) {
return this.foo = value;
},
};
var myReceiverObject = {
foo: 0,
};
Reflect.set(myObject, 'bar', 1, myReceiverObject);
myObject.foo // 4
myReceiverObject.foo // 1

注意,如果 Proxy 对象和 Reflect 对象联合使用,前者拦截赋值操作,后者完成赋值的默认行为,而且传入了 receiver ,那么 Reflect.set 会触发
Proxy.defineProperty 拦截。

let p = {
a: 'a'
};
let handler = {
set(target, key, value, receiver) {
console.log('set');
Reflect.set(target, key, value, receiver)
},
defineProperty(target, key, attribute) {
console.log('defineProperty');
Reflect.defineProperty(target, key, attribute);
}
};
let obj = new Proxy(p, handler);
obj.a = 'A';
// set
// defineProperty

上面代码中, Proxy.set 拦截里面使用了 Reflect.set ,而且传入了 receiver ,导致触发 Proxy.defineProperty 拦截。这是因为 Proxy.set 的
receiver 参数总是指向当前的 Proxy 实例(即上例的 obj ),而 Reflect.set 一旦传入 receiver ,就会将属性赋值到 receiver 上面(即 obj ),导致
触发 defineProperty 拦截。如果 Reflect.set 没有传入 receiver ,那么就不会触发 defineProperty 拦截。

let p = {
a: 'a'
};
let handler = {
set(target, key, value, receiver) {
console.log('set');
Reflect.set(target, key, value)
},
defineProperty(target, key, attribute) {
console.log('defineProperty');
Reflect.defineProperty(target, key, attribute);
}
};
let obj = new Proxy(p, handler);
obj.a = 'A';
// set

如果第一个参数不是对象, Reflect.set 会报错

Reflect.set(1, 'foo', {}) // 报错
Reflect.set(false, 'foo', {}) // 报错

2.3Reflect.has(obj, name)

Reflect.has 方法对应 name in obj 里面的 in 运算符。

var myObject = {
foo: 1,
};
// 旧写法
'foo' in myObject // true 上一章 下一章
2017/11/20 Reflect - ECMAScript 6入门
http://es6.ruanyifeng.com/#docs/reflect 5/9
// 新写法
Reflect.has(myObject, 'foo') // true

如果第一个参数不是对象, Reflect.has 和 in 运算符都会报错

2.4Reflect.deleteProperty(obj, name)

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

const myObj = { foo: 'bar' };
// 旧写法
delete myObj.foo;
// 新写法
Reflect.deleteProperty(myObj, 'foo');

该方法返回一个布尔值。如果删除成功,或者被删除的属性不存在,返回 true ;删除失败,被删除的属性依然存在,返回 false 。

2.5Reflect.construct(target, args)

Reflect.construct 方法等同于 new target(...args) ,这提供了一种不使用 new ,来调用构造函数的方法

function Greeting(name) {
this.name = name;
}
// new 的写法
const instance = new Greeting('张三');
// Reflect.construct 的写法
const instance = Reflect.construct(Greeting, ['张三']);

2.6Reflect.getPrototypeOf(obj)

Reflect.getPrototypeOf 方法用于读取对象的 __proto__ 属性,对应 Object.getPrototypeOf(obj) 。

const myObj = new FancyThing();
// 旧写法
Object.getPrototypeOf(myObj) === FancyThing.prototype;
// 新写法
Reflect.getPrototypeOf(myObj) === FancyThing.prototype;

Reflect.getPrototypeOf 和 Object.getPrototypeOf 的一个区别是,如果参数不是对象, Object.getPrototypeOf 会将这个参数转为对象,然后再运
行,而 Reflect.getPrototypeOf 会报错。

Object.getPrototypeOf(1) // Number {[[PrimitiveValue]]: 0}
Reflect.getPrototypeOf(1) // 报错

2.7Reflect.setPrototypeOf(obj, newProto)

Reflect.setPrototypeOf 方法用于设置对象的 __proto__ 属性,返回第一个参数对象,对应 Object.setPrototypeOf(obj, newProto)

const myObj = new FancyThing();
// 旧写法
Object.setPrototypeOf(myObj, OtherThing.prototype);
// 新写法
Reflect.setPrototypeOf(myObj, OtherThing.prototype);

如果第一个参数不是对象, Object.setPrototypeOf 会返回第一个参数本身,而 Reflect.setPrototypeOf 会报错。

Object.setPrototypeOf(1, {})
// 1
Reflect.setPrototypeOf(1, {})
// TypeError: Reflect.setPrototypeOf called on non-object

如果第一个参数是 undefined 或 null , Object.setPrototypeOf 和 Reflect.setPrototypeOf 都会报错。

Object.setPrototypeOf(null, {})
// TypeError: Object.setPrototypeOf called on null or undefined
Reflect.setPrototypeOf(null, {})
// TypeError: Reflect.setPrototypeOf called on non-object

2.8Reflect.apply(func, thisArg, args)

Reflect.apply 方法等同于 Function.prototype.apply.call(func, thisArg, args) ,用于绑定 this 对象后执行给定函数。
一般来说,如果要绑定一个函数的 this 对象,可以这样写 fn.apply(obj, args) ,但是如果函数定义了自己的 apply 方法,就只能写成
Function.prototype.apply.call(fn, obj, args) ,采用 Reflect 对象可以简化这种操作。

const ages = [11, 33, 12, 54, 18, 96];
// 旧写法
const youngest = Math.min.apply(Math, ages);
const oldest = Math.max.apply(Math, ages);
const type = Object.prototype.toString.call(youngest);
// 新写法
const youngest = Reflect.apply(Math.min, Math, ages);
const oldest = Reflect.apply(Math.max, Math, ages);
const type = Reflect.apply(Object.prototype.toString, youngest, []);

2.9Reflect.defineProperty(target, propertyKey, attributes)

Reflect.defineProperty 方法基本等同于 Object.defineProperty ,用来为对象定义属性。未来,后者会被逐渐废除,请从现在开始就使用
Reflect.defineProperty 代替它

function MyDate() {
/*…*/
}
// 旧写法
Object.defineProperty(MyDate, 'now', {
value: () => Date.now()
});
// 新写法
Reflect.defineProperty(MyDate, 'now', {
value: () => Date.now()
})

如果 Reflect.defineProperty 的第一个参数不是对象,就会抛出错误,比如 Reflect.defineProperty(1, 'foo') 。

2.10Reflect.getOwnPropertyDescriptor(target, propertyKey)

Reflect.getOwnPropertyDescriptor 基本等同于 Object.getOwnPropertyDescriptor ,用于得到指定属性的描述对象,将来会替代掉后者

var myObject = {};
Object.defineProperty(myObject, 'hidden', {
value: true,
enumerable: false,
});
// 旧写法
var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');
// 新写法
var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden')

Reflect.getOwnPropertyDescriptor 和 Object.getOwnPropertyDescriptor 的一个区别是,如果第一个参数不是对象,
Object.getOwnPropertyDescriptor(1, 'foo') 不报错,返回 undefined ,而 Reflect.getOwnPropertyDescriptor(1, 'foo') 会抛出错误,表示参数非

2.11Reflect.isExtensible (target)

Reflect.isExtensible 方法对应 Object.isExtensible ,返回一个布尔值,表示当前对象是否可扩展。

const myObject = {};
// 旧写法
Object.isExtensible(myObject) // true
// 新写法
Reflect.isExtensible(myObject) // true

如果参数不是对象, Object.isExtensible 会返回 false ,因为非对象本来就是不可扩展的,而 Reflect.isExtensible 会报错

Object.isExtensible(1) // false
Reflect.isExtensible(1) // 报错

 2.12Reflect.preventExtensions(target)

Reflect.preventExtensions 对应 Object.preventExtensions 方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功

var myObject = {};
// 旧写法
Object.preventExtensions(myObject) // Object {}
// 新写法
Reflect.preventExtensions(myObject) // true

如果参数不是对象, Object.preventExtensions 在 ES5 环境报错,在 ES6 环境返回传入的参数,而 Reflect.preventExtensions 会报错。

// ES5 环境
Object.preventExtensions(1) // 报错
// ES6 环境
Object.preventExtensions(1) // 1
// 新写法
Reflect.preventExtensions(1) // 报错

2.13Reflect.ownKeys (target)

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

var myObject = {
foo: 1,
bar: 2,
[Symbol.for('baz')]: 3,
[Symbol.for('bing')]: 4,
};
// 旧写法
Object.getOwnPropertyNames(myObject)
// ['foo', 'bar']
Object.getOwnPropertySymbols(myObject)
//[Symbol(baz), Symbol(bing)]
// 新写法
Reflect.ownKeys(myObject)
// ['foo', 'bar', Symbol(baz), Symbol(bing)]

3. 实例:使用 Proxy 实现观察者模式

观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。

const person = observable({
name: '张三',
age: 20
});
function print() {
console.log(`${person.name}, ${person.age}`)
}
observe(print);
person.name = '李四';
// 输出
// 李四, 20

上面代码中,数据对象 person 是观察目标,函数 print 是观察者。一旦数据对象发生变化, print 就会自动执行。
下面,使用 Proxy 写一个观察者模式的最简单实现,即实现 observable 和 observe 这两个函数。思路是 observable 函数返回一个原始对象的 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;
}

上面代码中,先定义了一个 Set 集合,所有观察者函数都放进这个集合。然后, observable 函数返回原始对象的代理,拦截赋值操作。拦截函数 set 之
中,会自动执行所有观察者

总结

本博客源于本人阅读相关书籍和视频总结,创作不易,谢谢点赞支持。学到就是赚到。我是歌谣,励志成为一名优秀的技术革新人员。

欢迎私信交流,一起学习,一起成长。

推荐链接 其他文件目录参照

“睡服“面试官系列之各系列目录汇总(建议学习收藏)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/419172.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java死锁检测

/*** author:cp* time:2021-2-24* Description: java死锁测试* 进程id查看命令:jsp 找到对应的进程id* 通过jstack 进程id 输出死锁信息** 如何定位死循环导致的其他线程阻塞等待:* linux下top命令查看cpu使用率较高的java进程&am…

div始终在底部

<style type"text/css">body{margin:0;padding:0; } html,body{height:100%;}div{width:100%;height:100px;background:#fc9;position:absolute;top:100%;margin-top:-100px}</style><div></div> 底部多高它的margin-top 就负多少。保证不留…

“睡服”面试官系列第十八篇之generator函数的语法(建议收藏学习)

目录 1简介 1.1基本概念 1.2yield 表达式 1.3与 Iterator 接口的关系 2. next 方法的参数 3. for...of 循环 4. Generator.prototype.throw() 5. Generator.prototype.return() 6. next()、throw()、return() 的共同点 7. yield* 表达式 8. 作为对象属性的 Generator…

微信撤回消息格式

<msg><fromusername>wxid_1zcdlm1cjhr522</fromusername><scene>0</scene><commenturl></commenturl><appmsg appid"" sdkver"0"><title>还好</title><des></des><action>…

MSSQL 2005数据库与SP4补丁安装

MSSQL 2005数据库与SP4补丁安装 Sql Server 2005 正确安装之前的win7配置&#xff1a; http://wenku.baidu.com/link?url6T3jzVnu2XY_sfqfe9ZqQ_6dUOdrZwHc83baWh6tgD2D4RBLnbFVoYn5hpq80v3G9ZmE1mQMzmSVWbm6VR26LlSGg3Pobl6B5VskugHjYF7 Sql Server 2005 的下载地址&#xff…

“睡服”面试官系列第十九篇之async函数(建议收藏学习)

目录 1. 含义 2. 基本用法 3. 语法 3.1返回 Promise 对象 3.2Promise 对象的状态变化 3.3await 命令 3.4错误处理 3.5使用注意点 4. async 函数的实现原理 5. 与其他异步处理方法的比较 6. 实例&#xff1a;按顺序完成异步操作 7. 异步遍历器 7.1异步遍历的接口 …

spring技术内幕——深入解析spring架构与设计原理

林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;说&#xff1a;“我从心底认为&#xff0c;优秀的程序员与平庸的程序员之间的区别&#xff0c;是在于认为自己的代码重要还是数据结构更加重要。平庸的程序员眼里只有代码&#xff0c;优秀的程序员则关注数据结构及之前的关…

python3 字符串方法

python3 字符串方法 1.capitalize() 将字符串的第一个字符改为大写 1 >>> si love cnblog 2 >>> s.capitalize() 3 I love cnblog 2.casefold() 将字符串所有字符改为小写 1 >>> (s.capitalize()).casefold() 2 i love cnblog 3.center(width) 将字…

重学算法第三期|数据结构与算法001

目录 强烈推荐一个数据结构可视化工具&#xff1a;https://www.cs.usfca.edu/~galles/visualization/Algorithms.html&#xff0c;点击B树即可模拟B树的动态插入过程&#xff0c;非常有利于理解 1、开篇词 2、为什么要学习数据结构与算法 3、如何抓住重点&#xff0c;系统高…

部署和调优 3.4 腾讯企业邮箱免费版 未完

浏览器输入腾讯的企业邮箱官网 exmail.qq.com 点右上角的 申请开通 最下面有个 免费版 填好基本信息 根据提示验证密保邮箱 转载于:https://www.cnblogs.com/wangshaojun/p/5079304.html

前端学习(1715):前端系列javascript之页面配置

test-page.vue <template><view>test-page</view> </template><script>export default {data() {return {}},methods: {}} </script><style></style>page.json {"pages": [ //pages数组中第一项表示应用启动页&a…

03|复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?

目录 为什么需要复杂度分析&#xff1f; 大 O 复杂度表示法 时间复杂度分析 几种常见时间复杂度 空间复杂度分析 为什么需要复杂度分析&#xff1f; 事后统计法&#xff1a;代码跑一遍&#xff0c;通过统计、监控&#xff0c;就能得到算法执行的时间和占用的内存大小。这种…

前端学习(1716):前端系列javascript之页面配置下

page.vue {"pages": [ //pages数组中第一项表示应用启动页&#xff0c;参考&#xff1a;https://uniapp.dcloud.io/collocation/pages{"path" : "pages/test-page/test-page","style" : {"navigationBarTitleText":"t…

04 | 复杂度分析(下):浅析最好、最坏、平均、均摊时间复杂度

// n表示数组array的长度 int find(int[] array, int n, int x) {int i 0;int pos -1;for (; i < n; i) {if (array[i] x) {pos i;break;}}return pos; } 这段代码的时间复杂度还是 O(n) 吗&#xff1f;如果数组中第一个元素正好是要查找的变量 x&#xff0c;那就不需要…

Atitit.java swing打印功能 api  attilax总结

Atitit.java swing打印功能 api attilax总结 1. 打印方式有三种&#xff1a;2 1.1. 一是不经过任何修改&#xff0c;直接调用javascript中的window.print()打印。2 1.2. 二根据客户要求的报表格式&#xff0c;编写相应格式的html&#xff0c;装载数据打印&#xff0c;同样调用…

git clone 速度过慢

由于公司内网限制&#xff0c;通过git clone mybatis的源码时速度贼慢 原来的方式&#xff1a;git clone ​​​​​​​https://github.com/mybatis/mybatis-3.git 超级慢——失败 改进方式&#xff1a;git clone https://github.com.cnpmjs.org/mybatis/mybatis-3.git 贼快…

2016/1/4 学习笔记 数据类型转换 注释 语句

⑤ 数据类型转换 自动转换&#xff1a; 一般在算术运算过程中进行自 动转换为数字类型比较大的类型 由低级到高级转换 强制转换&#xff1a;又叫做显示转换。 1&#xff0c;从高精…

前端学习(1718):前端系列javascript之生命周期上

<script>export default {onLaunch: function() {console.log(App Launch)//登录//用户信息//存储},onShow: function() {console.log(App Show)//时间戳//计算用胡得使用时间},onHide: function() {console.log(App Hide)//应用进入后台所作得得事情},onError(e){consol…

05 | 数组:为什么很多编程语言中数组都从0开始编号?

什么是数组&#xff1f; 数组&#xff08;Array&#xff09;是一种线性表数据结构。它用一组连续的内存空间&#xff0c;来存储一组具有相同类型的数据。 线性表存储结构连续内存空间存储相同类型数据 优点&#xff1a;连续内存相同类型数据数组可以实现随机访问 缺点&#…