ES6学习笔记(十一)Object的继承者Reflect
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
。
(3) 让Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。
(4)Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就是前面每一个拦截方法都有一个对应的Reflect方法的原因。
Proxy(target, {
set: function(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;
}
});
上面代码中,Proxy
方法拦截target
对象的属性赋值行为。它采用Reflect.set
方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。
注意:在严格模式下,set操作总是返回true。
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
方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。
// 老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // // 新写法
Reflect.apply(Math.floor, undefined, [1.75]) //
2.静态方法
Reflect
对象一共有 个静态方法。Proxy也有种,一一对应。
- 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
对象的方法是一一对应的。下面是对它们的解释。
Reflect.get(target, name, receiver)
Reflect.get
方法查找并返回target
对象的name
属性,如果没有该属性,则返回undefined
。相当于target.get(name),第三个参数一般省略。
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
} Reflect.get(myObject, 'foo') //
Reflect.get(myObject, 'bar') //
Reflect.get(myObject, 'baz') //
如果name
属性部署了读取函数(getter),则读取函数的this
绑定receiver
。这点和Proxy一样,从参数名就可以看出来。
如果第一个参数不是对象,Reflect.get
方法会报错。因为非对象是没有属性的,读取不了。
Reflect.set(target, name, value, receiver)
Reflect.set
方法设置target
对象的name
属性等于value
。相当于target.name = value。
var myObject = {
foo: 1,
set bar(value) {
return this.foo = value;
},
} myObject.foo // Reflect.set(myObject, 'foo', 2);
myObject.foo // Reflect.set(myObject, 'bar', 3)
myObject.foo //
注意,如果 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
拦截。
如果第一个参数不是对象,Reflect.set
会报错。
Reflect.has(obj, name)
Reflect.has
方法对应name in obj
里面的in
运算符。也就是对原本in运算符的替换。
var myObject = {
foo: 1,
}; // 旧写法
'foo' in myObject // true // 新写法
Reflect.has(myObject, 'foo') // true
如果第一个参数不是对象,Reflect.has
和in
运算符都会报错。
Reflect.deleteProperty(obj, name)
Reflect.deleteProperty
方法等同于delete obj[name]
,用于删除对象的属性。
const myObj = { foo: 'bar' }; // 旧写法
delete myObj.foo; // 新写法
Reflect.deleteProperty(myObj, 'foo');
该方法返回一个布尔值。如果删除成功,或者被删除的属性不存在,返回true
;删除失败,被删除的属性依然存在,返回false
。
Reflect.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, ['张三']);
唉,有时候同一操作的实现方法太多了也不好啊。
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, []);
其他的不一一列举了。
3.实例:使用 Proxy 实现观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。本质是一种监控触发的过程。
const queuedObservers = new Set();//定义一个Set集合,用来装所有的触发函数
const observe = fn => queuedObservers.add(fn);//传入一个触发函数,添加到Set集合
const observable = obj => new Proxy(obj, {set});//创建代理对象
//触发函数,即被通知对象
function print() {
console.log(`${person.name}, ${person.age}`)
}
//代理的目标对象
const person = observable({
name: '张三',
age: 20
});
observe(print);//调用添加操作,把print方法添加到Set集合
person.name = '李四';//触发操作
//代理的拦截处理方法
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());//遍历Set集合,逐个调用触发函数,也就是说这里的触发函数没有参数,不然怎么传参呢
return result;
}
上面代码中,数据对象person
是观察目标,函数print
是观察者。一旦数据对象发生变化,print
就会自动执行。
使用 Proxy 写一个观察者模式的最简单实现,即实现observable
和observe
这两个函数。思路是observable
函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数。
上面代码中,先定义了一个Set
集合,所有观察者函数都放进这个集合。然后,observable
函数返回原始对象的代理,拦截赋值操作。拦截函数set
之中,会自动执行所有观察者。
ES6学习笔记(十一)Object的继承者Reflect的更多相关文章
- ES6学习笔记四:Proxy与Reflect
一:Proxy 代理. ES6把代理模式做成了一个类,直接传入被代理对象.代理函数,即可创建一个代理对象,然后我们使用代理对象进行方法调用,即可调用被包装过的方法: 1)创建 var proxy = ...
- ES6学习笔记三(proxy和reflect)
proxy用法 // 代理 { let obj={ time:'2017-03-11', name:'net', _r: }; let monitor=new Proxy(obj,{ // 拦截对象属 ...
- ES6学习笔记十一:编程风格技巧
一:用let取代var 二:静态字符串一律使用单引号或反引号,不使用双引号.动态字符串(模版字符串)使用反引号. 三:解构赋值: 使用数组对变量赋值时,优先使用解构赋值: 函数的参数如果是对象的成员, ...
- JS&ES6学习笔记(持续更新)
ES6学习笔记(2019.7.29) 目录 ES6学习笔记(2019.7.29) let和const let let 基本用法 let 不存在变量提升 暂时性死区 不允许重复声明 块级作用域 级作用域 ...
- es6学习笔记-class之一概念
前段时间复习了面向对象这一部分,其中提到在es6之前,Javasript是没有类的概念的,只从es6之后出现了类的概念和继承.于是乎,花时间学习一下class. 简介 JavaScript 语言中,生 ...
- ES6学习笔记<四> default、rest、Multi-line Strings
default 参数默认值 在实际开发 有时需要给一些参数默认值. 在ES6之前一般都这么处理参数默认值 function add(val_1,val_2){ val_1 = val_1 || 10; ...
- ES6学习笔记<二>arrow functions 箭头函数、template string、destructuring
接着上一篇的说. arrow functions 箭头函数 => 更便捷的函数声明 document.getElementById("click_1").onclick = ...
- ES6学习笔记<五> Module的操作——import、export、as
import export 这两个家伙对应的就是es6自己的 module功能. 我们之前写的Javascript一直都没有模块化的体系,无法将一个庞大的js工程拆分成一个个功能相对独立但相互依赖的小 ...
- ES6学习笔记<三> 生成器函数与yield
为什么要把这个内容拿出来单独做一篇学习笔记? 生成器函数比较重要,相对不是很容易理解,单独做一篇笔记详细聊一聊生成器函数. 标题为什么是生成器函数与yield? 生成器函数类似其他服务器端语音中的接口 ...
- ES6学习笔记<一> let const class extends super
学习参考地址1 学习参考地址2 ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准.因为当前版本的ES6是在2015年发布的,所以又称ECMAScript 2015:也 ...
随机推荐
- Ubuntu14.04下Mongodb(在线安装方式|apt-get)安装部署步骤(图文详解)(博主推荐)
不多说,直接上干货! 本博文介绍了MongoDB,并详细指引读者在Ubuntu下MongoDB的安装和使用.本教程在Ubuntu14.04下测试通过. 一.MongoDB介绍 MongoDB 是一个是 ...
- vim编辑器常用语法
1)yy (功能描述:复制光标当前一行) y数字y (功能描述:复制一段(从第几行到第几行))2)p (功能描述:箭头移动到目的行粘贴)3)u (功能描述:撤销上一步)4)dd (功能描述:删除光标当 ...
- SpringCloud学习笔记(5)----Spring Cloud Netflix之Eureka的服务认证和集群
1. Eureka服务认证 1. 引入依赖 <dependency> <groupId>org.springframework.boot</groupId> < ...
- Python中编写精美图形界面(PyQt5)
纯代码,布局的讲解 见: https://cloud.tencent.com/developer/article/1345469 Qt designer设计文件 .ui转 .py文件,进阶使用 注:不 ...
- 安装anaconda和tensorflow
一.首先下载anaconda,下载:Anaconda2-4.3.1-Linux-x86_64.sh(https://repo.continuum.io/archive/)参考网址:https://ww ...
- mysql忘记密码怎么处理
1修改/etc/my.cnf文件 在[mysqld]下加一行skip-grant-tables 2.重启mysql使配置生效 3.免密码登录mysql并设置新密码 设置新的root密码,并刷新权限 4 ...
- python 中i++、逻辑表达式
参考链接:https://www.cnblogs.com/yupeng/p/3345946.html i++运算符 python中没有类似i++之类实现+1的运算符,但是有++i,+-i.之类的,他们 ...
- 2019年北航OO第二单元(多线程电梯任务)总结
一.三次作业总结 1. 说在前面 对于这次的这三次电梯作业,我采用了和几乎所有人都不同的架构:将每个人当作一个线程.这样做有一定的好处:它使得整个问题的建模更加自然,并且在后期人员调度变得复杂时,可以 ...
- 洛谷 P3671 [USACO17OPEN]Where's Bessie? 贝西在哪呢
P3671 [USACO17OPEN]Where's Bessie? 贝西在哪呢 题目背景 农夫John正在测试一个他新发明的全自动寻找奶牛无人机,它能够照一张农场的图片然后自动找出奶牛的位置. 不幸 ...
- ubuntu下不用拔盘就可以重新识别usb设备
#!/bin/sh # Usage: ./resetusb ARGUMENT(The keyword for your usb device) var1=$ keyword=${var1:=Stora ...