ES6+ Reflect一

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 基本用法@H_404_36@

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 返回值@H_404_36@

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 使用函数代替命令式@H_404_36@

Object 中操作数据时,有一些是命令式的操作,如:delete obj.aname in obj ,Reflect 则将一些命令式的操作如 deletein 等使用函数来替代,这样做的目的是为了让代码更加好维护,更容易向下兼容;也避免出现更多的保留字。

// 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()@H_404_36@

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()@H_404_36@

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.deleteproperty()@H_404_36@

Reflect.deleteproperty() 方法允许删除对象的属性。它类似 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()@H_404_36@

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()@H_404_36@

Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组。

语法:

Reflect.ownKeys(target)
  • target:表示目标对象

如果这个目标对象不是一个对象那么这个函数就会抛出一个异常。这个数组的值等于 Object.getownPropertyNames(target).concat(Object.getownPropertySymbols(target)) 我们来看下面的实例:

let a = Symbol.for('a');
let b = Symbol.for('b');

let obj = {
    [a]: ,
    [b]: ,
    key1: ,
    key2: 
};

let arr1 = Object.getownPropertyNames(obj);
console.log(arr1); // [ 'key1', 'key2' ]
let arr2 = Object.getownPropertySymbols(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 中的数据响应就是使用的它们。