ES6 Reflect详解

文章目录

  • 概述
  • 静态方法
    • Reflect.get(target, name, receiver)
    • Reflect.set(target, name, value, receiver)
    • Reflect.has(obj, name)
    • Reflect.deleteProperty(obj, name)
    • Reflect.construct(target, args)
    • Reflect.getPrototypeOf(obj)
    • Reflect.setPrototypeOf(obj, newProto)
    • Reflect.apply(func, thisArg, args)
    • Reflect.defineProperty(target, propKey, descriptor)
    • Reflect.getOwnPropertyDescriptor(target, propKey)
    • Reflect.preventExtensions(target)
    • Reflect.isExtensible (target)
    • Reflect.ownKeys (target)
  • 实例:使用 Proxy 实现观察者模式

概述

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

Reflect对象的设计目的有这样几个。

(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。

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

// 老写法
try {Object.defineProperty(obj, propKey, descriptor);// success
} catch (e) {// failure
}// 新写法
if (Reflect.defineProperty(obj, propKey, descriptor)) {// success
} else {// failure
}

有关Object.defineProperty(obj, propKey, descriptor)更详解的讲解,可以参阅这篇文章

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

// 老写法
'desc' in { desc: 'testCase' } // true// 新写法
Reflect.has({ desc: 'testCase' }, 'desc') // true

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

const setProxy = new Proxy({}, {set: function(target, name, value, receiver) {console.log(target, name, value, receiver)var success = Reflect.set(target, name, value, receiver);if (success) {console.log('property ' + name + ' on ' + target + ' set to ' + value);}return success;}
});
setProxy.age = 24// {} 'age' 24 Proxy(Object) {}
// property age on [object Object] set to 24

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

下面是另一个例子。

const loggedProxy = new Proxy({ name: 'caoyuan' }, {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);}
});oggedProxy.name // get {name: 'caoyuan'} name
'name' in loggedProxy // has name
delete loggedProxy['name'] // delete name

上面代码中,每一个Proxy对象的拦截操作(getdeletehas),内部都调用对应的Reflect方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。

有了Reflect对象以后,很多操作会更易读。

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

静态方法

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

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

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

下面是对它们的解释。

Reflect.get(target, name, receiver)

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

const calculateObj = {num1: 1,num2: 2,get addNum(){return this.num1 + this.num2}
}Reflect.get(calculateObj, 'num1') // 1
Reflect.get(calculateObj, 'num2') // 2
Reflect.get(calculateObj, 'addNum') // 3
Reflect.get(calculateObj, 'num100') // undefined

如果name属性部署了读取函数(getter),则当receiver有传值时,读取函数的this绑定为receiver

const calculateObj = {num1: 1,num2: 2,get addNum(){return this.num1 + this.num2}
}const receiverObj = {num1: 3,num2: 4
}Reflect.get(calculateObj, 'addNum', receiverObj) // 7

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

Reflect.get(1, 'foo') // TypeError: Reflect.get called on non-object
Reflect.get(false, 'foo') // TypeError: Reflect.get called on non-object

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

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

const setObj = {num1: 1,set valSet(value) {this.num1 = value}
}Reflect.set(setObj, 'num1', 2)
setObj.num1 // 2Reflect.set(setObj, 'valSet', 3)
setObj.num1 // 3

如果name属性设置了赋值函数(setter),则当receiver有传值时,赋值函数的this绑定receiver, 所以修改的实际上是 receiver 的属性值。

const setObj = {num1: 1,set valSet(value) {this.num1 = value}
}const receiverObj = {num1: 4
}Reflect.set(setObj, 'valSet', 5, receiverObj)setObj.num1 // 1
receiverObj.num1 // 5

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

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

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

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

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

Reflect.set(1, 'foo', {}) // TypeError: Reflect.set called on non-object
Reflect.set(false, 'foo', {}) // TypeError: Reflect.set called on non-object

Reflect.has(obj, name)

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

const obj = {age: 666
}// 旧写法
'age' in obj // true// 新写法
Reflect.has(obj, 'age') // true

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

Reflect.deleteProperty(obj, name)

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

const obj = {age: 666
}// 旧写法
delete obj['age'] // true
// 或
delete obj.age // true// 新写法
Reflect.deleteProperty(obj, 'age') // true

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

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

Reflect.construct(target, args)

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

function StuInfo(name) {this.name = name
}// new 的写法
const instance = new StuInfo('caoyuan')// Reflect.construct 的写法
const instance = Reflect.construct(StuInfo, ['caoyuan'])

如果Reflect.construct()方法的第一个参数不是函数,会报错。

Reflect.getPrototypeOf(obj)

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

function StuInfo(name) {this.name = name
}const instance = new StuInfo('caoyuan');// 旧写法
Object.getPrototypeOf(instance) === StuInfo.prototype;// 新写法
Reflect.getPrototypeOf(instance) === StuInfo.prototype;

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

Object.getPrototypeOf(1) // Number {[[PrimitiveValue]]: 0}
Reflect.getPrototypeOf(1) // TypeError: Reflect.getPrototypeOf called on non-object

Reflect.setPrototypeOf(obj, newProto)

Reflect.setPrototypeOf方法用于设置目标对象的原型(prototype),对应Object.setPrototypeOf(obj, newProto)方法。它返回一个布尔值,表示是否设置成功。

const obj = {};// 旧写法
Object.setPrototypeOf(obj, Array.prototype);// 新写法
Reflect.setPrototypeOf(obj, Array.prototype);obj.length // 0

如果无法设置目标对象的原型(比如,目标对象禁止扩展),Reflect.setPrototypeOf方法返回false

Reflect.setPrototypeOf({}, null)
// true
Reflect.setPrototypeOf(Object.freeze({}), null)
// false

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

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

如果第一个参数是undefinednullObject.setPrototypeOfReflect.setPrototypeOf都会报错。

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

Reflect.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, []);

Reflect.defineProperty(target, propKey, descriptor)

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')

这个方法可以与Proxy.defineProperty配合使用。

const proxyObj = new Proxy({}, {defineProperty(target, propKey, descriptor) {console.log(descriptor)Reflect.defineProperty(target, propKey, descriptor)}
})proxyObj.name = 'caoyuan'
// {value: 'caoyuan', writable: true, enumerable: true, configurable: true}proxyObj.name // 'caoyuan'

上面代码中,Proxy.defineProperty对属性赋值设置了拦截,然后使用Reflect.defineProperty完成了赋值。

Reflect.getOwnPropertyDescriptor(target, propKey)

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

const obj = {};
Object.defineProperty(obj, 'name', {value: 'caoyuan',writable: false,enumerable: false
})// 旧写法
const desc = Object.getOwnPropertyDescriptor(obj, 'name');// {value: 'caoyuan', writable: false, enumerable: false, configurable: false}// 新写法
const desc = Reflect.getOwnPropertyDescriptor(obj, 'name');

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

Reflect.preventExtensions(target)

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

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

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

// ES5 环境
Object.preventExtensions(1) // 报错// ES6 环境
Object.preventExtensions(1) // 1// 新写法
Reflect.preventExtensions(1) // TypeError: Reflect.preventExtensions called on non-object

Reflect.isExtensible (target)

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

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

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

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

Reflect.ownKeys (target)

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

const obj = {name: 'caoyuan',age: 666,[Symbol.for('for1')]: 'symbolVal1',[Symbol.for('for2')]: 'symbolVal2'
}// 旧写法
Object.getOwnPropertyNames(obj)
// ['name', 'age']Object.getOwnPropertySymbols(obj)
// [Symbol(for1), Symbol(for2)]// 新写法
Reflect.ownKeys(myObject)
// ['name', 'age', Symbol(for1), Symbol(for2)]

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

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

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

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

上面代码中,数据对象person是观察目标,函数print是观察者。一旦数据对象发生变化,print就会自动执行。

下面,使用 Proxy 写一个观察者模式的最简单实现,即实现observableobserve这两个函数。思路是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/656023.shtml

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

相关文章

项目解决方案:高清视频监控联网设计方案

目 录 一、客户需求 二、网络拓扑图 三、方案描述 四、服务器配置 五、方案优势 1. 多级控制 2. 平台可堆叠使用 3. 支持主流接入协议 4. 多种终端显示 5. 视频质量诊断 6. 客户端功能强大 7. 一机一档 一、客户需求 客户现场存在两个网络环境&#xff0c…

自动化测试CSS元素定位

1.1 CSS定位 1.1.1 绝对路径定位 目标 查找第一个文本为“猜猜看”的a标签 实现 CSS表达式 html>body>div>a[.”猜猜看”] python表达式 driver.find_element_by_css_selector(‘html>body>div>a[.”猜猜看”]’) 1.1.2 相对路径定位 目标 查找第…

【Tomcat与网络1】史前时代—没有Spring该如何写Web服务

在前面我们介绍了网络与Java相关的问题, 最近在调研的时候发现这块内容其实非常复杂,涉及的内容多而且零碎,想短时间梳理出整个体系是不太可能的,所以我们还是继续看Tomcat的问题,后面有网络的内容继续补充吧。 目录 …

【美团】Java高级开发工程师(用户增长方向)

更新时间:2024/01/29 | 工作地点:上海市 | 事业群:点评事业部 | 工作经验:3年 部门介绍 大众点评作为国内重要的本地生活消费决策参考平台,多年来深耕本地生活消费领域,深受广大用户的信任和喜爱。我们的业…

MongoDB安装以及卸载

查询id: docker ps [rootlocalhost ~]# docker stop c7a8c4ac9346 c7a8c4ac9346 [rootlocalhost ~]# docker rm c7a8c4ac9346 c7a8c4ac9346 [rootlocalhost ~]# docker rmi mongo sudo docker pull mongo:4.4 sudo docker images 卸载旧的 sudo docker stop mong…

分享几个好用的前端网站

1.echart图表demo集 https://www.isqqw.com/ 2.json地图在线预览 https://geojson.io/ 3. 全国地图json数据 https://www.poi86.com/ 4.jquery插件库 https://www.jq22.com/ 5.jquery操作手册 https://www.bejson.com/apidoc/jquery/index.html 6.丰富的表格插件 ht…

【基础算法】1、快速排序快速选择

快速排序思想: 1、找一个分界点。 2、在分界点两边开始调整范围。 3、递归两边,重复。 例题: 给定你一个长度为 n的整数数列。 请你使用快速排序对这个数列按照从小到大进行排序。 并将排好序的数列按顺序输出。 输入格式 输入共两行&#xf…

NoSQL 数据库管理系统和模型的比较

前些天发现了一个人工智能学习网站,通俗易懂,风趣幽默,最重要的屌图甚多,忍不住分享一下给大家。点击跳转到网站。 NoSQL 数据库管理系统和模型的比较 介绍 当大多数人想到数据库时,他们通常会想到传统的关系数据库…

【TCP】流量控制和拥塞控制

前言 TCP(传输控制协议)是互联网协议(IP)网络传输层协议,负责控制数据包的顺序和流量控制,以防止网络拥塞和数据丢失。TCP流量控制和拥塞控制是确保网络有效通信的重要机制。具体分析如下: 流…

微服务-微服务Alibaba-Nacos 源码分析(上)

Nacos&Ribbon&Feign核心微服务架构图 架构原理 1、微服务系统在启动时将自己注册到服务注册中心,同时外发布 Http 接口供其它系统调用(一般都是基于Spring MVC) 2、服务消费者基于 Feign 调用服务提供者对外发布的接口,先对调用的本地接口加上…

利用qrcode.vue库生成二维码

利用qrcode.vue库生成二维码 安装 在vue2中 npm install --save qrcode.vue1 # yarn add qrcode.vue在vue3中 npm install --save qrcode.vue3 # yarn add qrcode.vue使用 普通使用: import { createApp } from vue import QrcodeVue from qrcode.vuecreateAp…

【Java程序设计】【C00168】基于SSM的旅游网管理系统(论文+PPT)

基于SSM的旅游网管理系统(论文PPT) 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的旅游网系统 本系统分为用户前台功能模块和管理员功能模块2个功能模块。 用户前台功能模块:当游客打开系统的网址后,首先…

【Python笔记-设计模式】单例模式

一、说明 单例是一种创建型设计模式,能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。 (一) 解决问题 维护共享资源(数据库或文件)的访问权限,避免多个实例覆盖同一变量,引发程序崩溃。 …

React 基础学习02

以下是React18版本的基础学习资源 点击我获取更多学习资源 1. …模板扩展符 import logo from ./logo.svg; import ./App.css; function App() {const imgData {className: small,style: {wdith: 200px,height: 200px,backgroundColor: grey}}return (<div className&quo…

JavaWeb中的Filter(过滤器)和 Listener(监听器)

提示&#xff1a;这两个东西听起来似乎很难&#xff0c;实际上是非常简单的&#xff0c;按照要求写就行了&#xff0c;一定不要被新名词给吓到了。 JavaWeb中的Filter&#xff08;过滤器&#xff09; 一、Filter&#xff08;过滤器&#xff09;1.如何编写 Filter2.Filter 中的细…

最小二乘圆柱拟合(高斯牛顿法)

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 本期话题&#xff1a;最小二乘圆柱拟合 相关背景资料 点击前往 圆柱拟合输入和输出要求 输入 8到50个点&#xff0c;全部采样自圆柱上。每个点3个坐标&#xff0c;坐…

自动化测试的10大误区!

自动化测试因提高效率&#xff0c;减少重复工作的特性而被广泛采用。然而&#xff0c;随着自动化测试的普及&#xff0c;自动化测试也面临一系列挑战和误解。 这些误区不仅影响了测试的有效性&#xff0c;还会导致一定的项目风险&#xff0c;为了确保自动化测试能够真正提升测…

【EI会议征稿通知】2024年第四届激光,光学和光电子技术国际学术会议(LOPET 2024)

2024年第四届激光&#xff0c;光学和光电子技术国际学术会议(LOPET 2024) 2024 4th International Conference on Laser, Optics and Optoelectronic Technology 2024年第四届激光&#xff0c;光学和光电子技术国际学术会议(LOPET 2024)将于2024年5月17日-19日在中国重庆举行。…

长度最小的子数组[中等]

一、题目 给定一个含有n个正整数的数组和一个正整数target。找出该数组中满足其总和大于等于target的长度最小的连续子数组[numsl, numsl1, ..., numsr-1, numsr]&#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回0。 示例 1&#xff1a; 输入&#xff…

【iOS ARKit】光照效果--光源

光照 在现实世界中&#xff0c;光扮演了极其重要的角色&#xff0c;没有光万物将失去色彩&#xff0c;没有光世界将一片漆黑。在3D数字世界中亦是如此&#xff0c;3D数字世界本质上是一个使用数学精确描述的真实世界复本&#xff0c;光照计算是影响这个数字世界可信度的极其重要…