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
等对象)。
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
console.log(Reflect.get(obj, 'name')); // imooc
const res = Reflect.set(obj, 'age', );
console.log(res); // true
console.log(obj); // {name: "imooc", lession: "ES6 Wiki", age: 7}
console.log(Object.keys(obj)); // ["name", "lession"]
console.log(Reflect.ownKeys(obj)); // ["name", "lession", Symbol(a)]
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属性的值
try {
Object.defineProperty(obj, prop, descriptor);
// success
} catch (e) {
// failure
}
Reflect 在操作对象时是有返回结果的,而 Object.defineProperty 是没有返回结果的,如果失败则会抛出异常,所以需要使用 try...catch
来捕获异常。
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()@H_404_36@
语法:
Reflect.get(target, propertyKey[, receiver])
如果目标值 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(target, propertyKey, value[, receiver])
// 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(target, propertyKey)
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
3.4 Reflect.has()@H_404_36@
语法:
Reflect.has(target, propertyKey)
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(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) ]