ES6+ Reflect(一)
1. 前言
任何一门语言在走向成熟的过程都是趋向精细化和规范化,在 API 设计之初满足当时的需求场景。随着前端的飞速发展,软件复杂度的提升,很多 API 在使用过程中存在很多使用别扭的情况,不符合软件开发的规范。上一节我们学习了 Proxy,Proxy 的设计目的是为了取代 Object.defineProperty,优化 性能 ,使得数据劫持的过程更加规范。
本节我们将学习 ES6 新的全局对象 —— Reflect(反射),首先我们要了解一下,为什么会新 添加 这么 一个 全局对象?Reflect 上的一些 函数 基本上都可以在 Object 上找到,找不到的,也是可以通过对对象命令式的操作去实现的;那么为什么还要新 添加 一个 呢?本节我们将学习 Reflect 的相关知识。
2. 基础知识
Reflect 是 一个 内置的对象,它提供了 拦截 JavaScript 操作的 方法 。这些 方法 与 Proxy 中的 handlers 方法 相同。与大多数全局对象不同 Reflect 并非 一个 构造 函数 ,所以不能通过 new 运算符对其进行 调用 ,或者将 Reflect 对象作为 一个 函数 来 调用 。 Reflect 的所有 属性 和 方法 都是静态的(类似 JSON 或者 Math 等对象)。
2.1 基本 用法
Reflect 可以检查对象上是否存在特定 属性 ,可以使用 Reflect.has() 方法 检测。
let key = Symbol . for ( 'a' ) ; const obj = { name : 'imooc' , lession : 'ES6 Wiki' , [ key ] : } console . log ( Reflect . has ( obj , 'name' ) ) ; // true console . log ( Reflect . has ( obj , 'age' ) ) ; // false
可以使用 Reflect.get() 方法 获取 对象上的 属性 值。
console . log ( Reflect . get ( obj , 'name' ) ) ; // imooc
可以使用 Reflect.set() 方法 为对象 添加 一个 新的 属性 。
const res = Reflect . set ( obj , 'age' , ) ; console . log ( res ) ; // true console . log ( obj ) ; // {name: "imooc", lession: "ES6 Wiki", age: 7}
使用 Reflect.ownKeys() 方法 获取 对象上的自有 属性 。
console . log ( Object . keys ( obj ) ) ; // ["name", "lession"] console . log ( Reflect . ownKeys ( obj ) ) ; // ["name", "lession", Symbol(a)]
上面的 代码 可以看出,使用 Object.keys() 获取 不到 属性 是 Symbol 的值。
2.2 返回值
Reflect 对象上的 方法 并不是专门为对象设计的,而是在语言层面的,它可以拿到语言内部的 方法 ,和 Proxy 的结合可以实现元编程。并且每个操作都是有返回值的,上节我们使用 Proxy 简单地实现了 Vue3 的响应式。但是在 Vue3 源码中 获取 和设置对象上的 属性 使用的是 Reflect,Reflect 会返回 一个 状态表示 获取 和设置的成功与否。
// const res = target[key]; // 上节 代码 const res = Reflect . get ( target , key ) ; // 获取 target上 属性 key的值 // target[key] = value; // 上节 代码 const result = Reflect . set ( target , key , value ) ; // 设置目标对象key 属性 的值
上面的两段 代码 是 Vue3 中的源码,因为在源码中需要知道 获取 或赋值的结果,因为可能 获取 失败。在 ES5 中如果想要监听劫持 属性 操作的结果需要使用 try...catch 的方式。
try { Object . defineProperty ( obj , prop , descriptor ) ; // success } catch ( e ) { // failure }
Reflect 在操作对象时是有返回结果的,而 Object.defineProperty 是没有返回结果的,如果失败则会抛出异常,所以需要使用 try...catch 来捕获异常。
2.3 使用 函数 代替命令式
Object 中操作数据时,有一些是命令式的操作,如: delete obj.a 、 name in obj ,Reflect 则将一些命令式的操作如 delete , in 等使用 函数 来替代,这样做的目的是为了让 代码 更加好维护,更容易向下兼容;也避免出现更多的保留字。
// ES5 'assign' in Object // true // ES6 Reflect . has ( Object , 'assign' ) // true delete obj . name ; // ES5 Reflect . deleteProperty ( obj , 'name' ) ; // ES6
3. 静态 方法
Reflect 的出现是为了取代 Object 中一些属于语言层面的 API,这些 API 在 Object 上也是可以找到的,并且它们的 功能 基本是相同的。上面我们也提到了 Reflect 和 Proxy 中 handlers 的 方法 是一一对应的,在很多场景中它门都是配套使用的。这里我们就来学习一下 Reflect 提供的静态 方法 :
3.1 Reflect.get()
Reflect.get() 方法 是从对象中读取 属性 的值,类似 ES5 中 属性 访问器语法: obj[key] ,但是它是通过 调用 函数 来获得返回结果的。
语法:
Reflect . get ( target , propertyKey [ , receiver ] )
target:需要取值的目标对象; propertyKey:需要 获取 的值的键值; receiver:如果 target 对象中指定了 getter,receiver 则为 getter 调用 时的 this 值。
如果目标值 target 类型不是 Object ,则抛出 一个 TypeError 。
// Object var obj = { a : , b : } ; Reflect . get ( obj , "a" ) ; // 1 // Array Reflect . get ( [ "a" , "b" , "c" ] , ) ; // "one"
第三个参数 receiver 是 this 所在的上下文,不传时指的是当前对象,如果传如 一个 人对象则 this 指向该对象。下面我们来看个实例:
let obj = { name : 'imooc' , lesson : 'ES5 Wiki' , get info ( ) { console . log ( `这是 ${ this . lesson } ` ) ; return } } ; Reflect . get ( obj , 'info' ) ; // 这是 ES5 Wiki Reflect . get ( obj , 'info' , { lesson : 'ES6 Wiki' } ) ; // 这是 ES5 Wiki
3.2 Reflect.set()
Reflect.set() 是在 一个 对象上设置 一个 属性 ,类似 ES5 中 属性 设置语法: obj[key] = value ,它也是通过 调用 函数 的方式来对对象设置 属性 的。
语法:
Reflect . set ( target , propertyKey , value [ , receiver ] )
target:表示要操作的目标对象; propertyKey:表示要设置的 属性 名; value:表示设置的 属性 值; receiver:表示的是 一个 this 值,如果我们在设置值的时候遇到 setter 函数 ,那么这个 receiver 值表示的就是 setter 函数 中的 this 值。
这个 函数 会返回 一个 Boolean 值,表示在目标对象上设置 属性 是否成功。
// Object var obj = { } ; Reflect . set ( obj , "name" , "imooc" ) ; // true console . log ( obj . name ) ; // "imooc" // Array var arr = [ "a" , "b" , "c" ] ; Reflect . set ( arr , , "C" ) ; // true console . log ( arr ) ; // ["a", "b", "C"]
使用可以截断数组:
var arr = [ "a" , "b" , "c" ] ; Reflect . set ( arr , "length" , ) ; // true console . log ( arr ) ; // ["a", "b"]
当有 receiver 参数时,如果 receiver 对象中有 propertyKey 属性 ,则会使用 receiver 对象中的值。
Reflect . set ( obj , 'lession' , 'ES5 Wiki' , { lession : 'ES6 Wiki' , age : } ) ; console . log ( obj ) ; // {name: "imooc", lesson: "ES5 Wiki"}
3.3 Reflect.delete property()
Reflect.delete property() 方法 允许 删除 对象的 属性 。它类似 ES5 中的 delete 操作符,但它也是 一个 函数 ,通过 调用 函数 来实现。
语法:
Reflect . deleteProperty ( target , propertyKey )
target:表示要操作的目标对象; propertyKey:表示要 删除 的 属性 。
这个 函数 的返回值是 一个 Boolean 值,如果成功的话,返回 true;失败的话返回 false。我们来看下面的实例:
var obj = { name : 'imooc' , lession : 'ES6 Wiki' } ; var r1 = Reflect . deleteProperty ( obj , 'name' ) ; console . log ( r1 ) ; // true console . log ( obj ) ; // {lession: "ES6 Wiki"} var r2 = Reflect . deleteProperty ( Object . freeze ( obj ) , 'lession' ) ; console . log ( r2 ) ; // false
上面的例子中使用 Object.freeze() 方法 来冻结 obj 对象使之不能被 修改 。
3.4 Reflect.has()
Reflect.has() 方法 可以检查 一个 对象上是否含有特定的 属性 ,这个 方法 相当于 ES5 的 in 操作符。
语法:
Reflect . has ( target , propertyKey )
target:表示要操作的目标对象; propertyKey: 属性 名,表示需要检查目标对象是否存在此 属性 。
这个 函数 的返回结果是 一个 Boolean 值,如果存在就返回 true,不存在就返回 false。当然如果目标对象 (target) 不是 一个 对象,那么就会抛出 一个 异常。
Reflect . has ( { x : } , "x" ) ; // true Reflect . has ( { x : } , "y" ) ; // false // 如果该 属性 存在于原型链中,也返回true Reflect . has ( { x : } , "toString" ) ; // true
这 方法 也可检查构造 函数 的 属性 。
function A ( name ) { this . name = name || 'imooc' ; } // 在原型上 添加 方法 A . prototype . getName = function ( ) { return this . name ; } ; var a = new A ( ) ; console . log ( 'name' in a ) ; // true console . log ( 'getName' in a ) ; // true let r1 = Reflect . has ( a , 'name' ) ; let r2 = Reflect . has ( a , 'getName' ) ; console . log ( r1 , r2 ) ; // true true
3.5 Reflect.ownKeys()
Reflect.ownKeys() 返回 一个 由目标对象自身的 属性 键组成的数组。
语法:
Reflect . ownKeys ( target )
target:表示目标对象
如果这个目标对象不是 一个 对象那么这个 函数 就会抛出 一个 异常。这个数组的值等于 Object.g eto wnPropertyNames(target).concat(Object.g eto wnPropertySymbols(target)) 我们来看下面的实例:
let a = Symbol . for ( 'a' ) ; let b = Symbol . for ( 'b' ) ; let obj = { [ a ] : , [ b ] : , key1 : , key2 : } ; let arr1 = Object . g eto wnPropertyNames ( obj ) ; console . log ( arr1 ) ; // [ 'key1', 'key2' ] let arr2 = Object . g eto wnPropertySymbols ( obj ) ; console . log ( arr2 ) ; // [ Symbol(a), Symbol(b) ] let arr3 = Reflect . ownKeys ( obj ) ; console . log ( arr3 ) ; // [ 'key1', 'key2', Symbol(a), Symbol(b) ]
4. 小结
本节主要学习了 ES6 新增的全局对象 Reflect ,它的目的是为了分离 Object 中属于语言部分的 内容 ,每个使用 Reflect 下的 方法 操作的对象都要返回值。 Reflect 对象和 Proxy 下的 方法 是一一对应的,二者配合可以实现很多 功能 。Vue3 中的数据响应就是使用的它们。