脚本宝典收集整理的这篇文章主要介绍了ES6精华:Proxy & Reflect,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
导语
本文主要介绍了ES6中Proxy
和Reflect
的精华知识,并附有恰当实例。PRoxy
意为代理器,通过操作为对象生成的代理器,实现对对象各类操作的拦截式编程。Reflect
是一个包揽更为严格、健全的操作对象方法的模块。因为Proxy
所能代理的方法和Reflect
所包括的方法基本对应,而且在拦截方法里应该使用对应的Reflect
方法返回结果,所以将两者合并在一起分享。
1 Proxy
1.1 登堂
先想个问题,如何管控对象某一属性的读取和修改(不涉及闭包创建私有属性)?
先创建不应被直接改动的容器属性:_属性名,再设置相应的setter
和getter
函数或创建相应的操作方法。
--- 设置 setter 和 getter 函数
let obj = {
_id: undefined,
get id() {
return this._id;
},
set id(v) {
this._id = v;
}
};
obj.id = 3; // 相当:obj._id = 3
console.LOG(obj.id); // console.log(obj._id);
--- 创建获取及修改方法
let obj = {
_id: undefined,
id() {
if (!arguments.length) return this._id;
this._id = arguments[0];
}
};
obj.id(3); // 相当:obj._id = 3
console.log(obj.id()); // console.log(obj._id);
这样有明显的缺陷:要为每个需要管控的属性进行重复设置,而且实际上容器属性可以被任意修改。
如果要求升级,我们需要监听查看、删除、遍历对象属性的操作,怎么办?ES6之前只能凉拌,ES6之后Proxy
代你办。
let obj = { id: 0, name: 'Wmaker' };
let objProxy = new Proxy(obj, {
get(target, attr) {
console.log(`Get ${attr}`);
return target[attr];
},
set(target, attr, val) {
console.log(`Set ${attr}`);
target[attr] = val;
return true;
}
});
objProxy.id; // 打印出:Get id,相当:obj.id;
objProxy.name; // 打印出:Get name,相当:obj.name;
1.2 入室
如前节示例可知,Proxy
是生成代理器的构造函数。传入的第一个参数为需要被代理的对象,第二个参数是需要拦截操作的配置对象(之后会列出所有可拦截的操作及其意义和注意事项)。配置对象中的每个拦截操作,都有默认格式的传入参数,有些也要求有特定的返回值(下面会罗列出某些规律)。
生成的代理器是一个与被代理对象关联的代理实例,可以像操作普通对象一样对待它。即是说,可以被delete
掉某个属性,可以被遍历或获取原型。所有作用于代理器的操作,都相当直接作用于被代理对象,还可为其配置拦截操作。说的激愤点,苍老师能做好的,我们的小泽老师怎么会差呢?另外可代理的不单单是对象,属于对象的函数、数组都是无条件接受的。
为对象生成代理器之后,依然可以操作原对象,但这自然是不建议的。
参数
不同拦截函数的传入参数不尽相同,但一般为被代理对象,该操作需要的参数等和代理器对象。
不必记忆每个拦截函数的参数,为脑瓜减减负担,使用时先打印出arguments
查看便会一目了然。
返回值
在拦截方法里,应尽量使用Reflect
对应的方法进行操作,并返回该方法的返回值。一方面是简单,更重要的是在不同方法或某些环境下,对返回值有硬性的要求,否则直接报错。比如construct()
必须返回一个对象,否则报错。再比如set()
在严格模式下,必须返回true
或可转化成true
的值,无论操作是否成功。
"use strict";
--- 错误的做法
let obj = { id: 0 };
let objProxy = new Proxy(obj, {
set(target, attr, val) {
console.log(`Set ${attr}`);
return target[attr] = val;
}
});
objProxy.id = 1; // 操作成功。
objProxy.id = 0; // 操作已经成功,但报错,不再往下执行。
--- 推荐的做法
let obj = { id: 0 };
let objProxy = new Proxy(obj, {
set(target, attr, val) {
console.log(`Set ${attr}`);
return Reflect.set(target, attr, val);
}
});
objProxy.id = 1; // 操作成功。
objProxy.id = 0; // 操作成功。
拦截方法的返回值并不会直接反映到外部,JS
会进行某些验证和排除。
比如即便在ownKeys()
中返回全部的值,但实际到外部的只有相应的系列。
两次打印的结果不相等。
let obj = { id: 0, [Symbol.ITerator]() {} };
let objProxy = new Proxy(obj, {
ownKeys(target) {
let res = Reflect.ownKeys(target);
console.log('1', res);
return res;
}
});
console.log('2', Object.keys(objProxy));
限制性的延续
当被代理对象自身已有某些限制,比如不可扩展或属性不可写不可配置等。对象本身的操作已经受到了限制,这时如果执行相应的代理操作,自然会报错。比如当属性不可写时,如果代理并执行了set()
操作,则会直接报错。
let obj = { id: 0 };
Object.defineProPErty(obj, 'name', {
value: 'Wmaker'
});
let objProxy = new Proxy(obj, {
set(target, attr, val) {
return Reflect.set(target, attr, val);
}
});
objProxy.id = 1; // 操作成功。
objProxy.name = 'Limo'; // 报错。
this
有些原生对象的内部属性或方法,只有通过正确的this
才能访问,所以无法进行代理。
比如日期对象,new Proxy(new Date(), {}).getDate()
,报错提示:这不是个Date
对象。
也有变通的办法,比如对于需要正确 this 的方法可以这样做。
let p = new Proxy(new Date(), {
get(target, attr) {
const v = Reflect.get(target, attr);
return typeof v === 'function'
? v.bind(target)
: v;
}
});
p.getTime(); // 没有错误。
处于配置对象中的this
直接指向配置对象,不是被代理对象或代理器对象。
处于被代理对象中的this
,分为存在于方法和存在于getter/setter
中两种。两者获取this
的方式不同,我们以实例说明。
--- 例一,没有 set 拦截操作。
let obj = {
get id() {
console.log('o', this);
},
fn() {
console.log('o', this);
}
};
let objProxy = new Proxy(obj, {});
objProxy.id; // 打印出 objProxy 。
objProxy.fn(); // 打印出 objProxy 。
--- 例二,有 set 拦截操作。实际使用了 target[attr] 获取属性值。
let obj = {
get id() {
console.log('o', this);
},
fn() {
console.log('o', this);
}
};
let objProxy = new Proxy(obj, {
get(target, attr) {
console.log('p', this);
return target[attr];
}
});
objProxy.id;
// 打印出配置对象和 obj。
// 因为实际是通过被代理对象即 target 访问到 id 值的。
objProxy.fn();
// 打印出配置对象和 objProxy。
// 可以等价的将方法转化成 (objProxy.fn).call(objProxy)。
// 虽然方法也是通过 target 访问到的,但对于方法来说,环境变量一开始就确定了。
原型为代理器
如果对象的原型为代理器,当操作进行到原型时,实际是操作原型的代理器对象。这时,其行为和普通代理器一致。
let obj = Object.create(new Proxy({}, {
get(target, attr) {
console.log('In proxy.');
return Reflect.get(target, attr);
}
}));
obj.name; // 打印出 In proxy. 。
// 当在实例上找不到对应属性时,转到了原型上,这时便被拦截了。
1.3 代理类别
这里仅仅是罗列出某些重点,详细的请看手册(标准和行为处于变动中)。
set
拦截属性的赋值操作,包括数组赋值。
严格模式下,必须返回可转化为true
的值。
严格模式下,如果代理对象有某些限制(属性不可写等),执行相应的拦截操作自然会报错。
apply
拦截函数的调用、call
和apply
操作。
has
拦截判断对象是否具有某属性。
只对in
和Reflect.has()
方法有效,for in
属于遍历系列。
construct
拦截new
命令,必须返回一个对象。
deleteProperty
拦截delete
属性操作。
严格模式下,必须返回可转化为true
的值。
严格模式下,如果代理对象有某些限制(属性不可写等),执行相应的拦截操作自然会报错。
defineProperty
拦截Object.defineProperty()
,不会拦截defineProperties
。
严格模式下,如果代理对象有某些限制(属性不可配置等),执行相应的拦截操作自然会报错。
getOwnPropertyDescriptor
拦截Object.getOwnPropertyDescriptor()
,不会拦截getOwnPropertyDescriptors
。
必须返回一个对象或undefined
,即返回与原方法相同的返回值,否则报错。
getPrototypeOf
拦截获取对象原型,必须返回一个对象或者null
。
如果对象不可扩展,必须返回真实的原型对象。
直接访问__proto__
,通过各类方法等等都会触发拦截。
setPrototypeOf
拦截Object.setPrototypeOf()
方法。
isExtensible
拦截Object.isExtensible()
操作。
返回值必须与目标对象的isExtensible
属性保持一致,否则会抛出错误。
preventextensions
拦截Object.preventExtensions()
,返回值会自动转成布尔值。
ownKeys
拦截对象自身属性的遍历操作。
比如keys()
,getOwnPropertynames()
,getOwnPropertySymbols()
等等。
返回值必须是数组,项只能是字符串或Symbol
,否则报错。
返回值会根据调用方法的需求进行过滤,比如Object.keys()
里不会有symbole
。
如果目标对象自身包含不可配置的属性,则该属性必须被返回,否则报错。
如果目标对象不可扩展,返回值必须包含原对象的所有属性,且不能包含多余的属性,否则报错。
1.4 Proxy.revocable
此静态方法也用于生成代理器对象的,但它还会返回一个回收代理器的方法。
使用场景:不允许直接访问目标对象,必须通过代理访问。而且一旦访问结束,就收回代理权,不允许再次访问。
let {proxy, revoke} = Proxy.revocable(obj, {});
revoke();
此时其内部属性值 [[IsRevoked]] 为 true,不能再操作代理器,否则报错。
2 Reflect
2.1 作用
最终目的是成为语言内部方法的宿主对象。
比如说defineProperty, getPrototypeOf, preventExtensions
都很明显属于内部方法,不应挂在Object
下。
提供替代命令式操作的相应函数。
使用Reflect.has(obj, attr)
替代in操作
。
使用Reflect.deleteProperty(obj, attr)
替代delete操作
。
使函数的行为更加完善和严格。
在无法定义属性时,Reflect.defineProperty
返回false
而不是抛出错误。
在要求类型是对象的参数为非对象时,会直接报错而不是调用Object()
进行转化。
与Porxy
可拦截的方法对应,方便在实现自定义行为时,直接调用以完成默认行为。
2.2 静态方法
这里仅仅是罗列出某些重点,详细的请看手册。
Reflect.get
Reflect.get(obj, attr, receiver)
返回属性值,没有则返回undefined
。
receiver
是设置getter
和setter
里的this
指向,默认指向obj
。
Reflect.set
Reflect.set(obj, attr, value, receiver)
设置属性值,返回布尔值。
注意:当该属性不是getter
时,传入receiver
等同于设置receiver
对象上的属性值。
Reflect.has
Reflect.has(obj, attr)
等同attr in obj
。
Reflect.deleteProperty
Reflect.deleteProperty(obj, attr)
等同delete obj[attr]
。
Reflect.construct
Reflect.construct(func, args)
args
等同于使用apply
方法传入的参数数组。
提供了不使用new
来调用构造函数的方法,等同new func(...args)
。
Reflect.getPrototypeOf
作用及参数等同Object.getPrototypeOf(obj)
。
Reflect.setPrototypeOf
作用及参数等同Object.setPrototypeOf(obj, newProto)
。
Reflect.apply
作用及参数等同Function.prototype.apply.call(func, thisArg, args)
。
Reflect.defineProperty
作用及参数等同Object.defineProperty(obj, attr, descriptor)
。
Reflect.getOwnPropertyDescriptor
作用及参数等同Object.getOwnPropertyDescriptor(obj, attr)
。
Reflect.isExtensible
Reflect.isExtensible(obj)
返回一个布尔值,表示当前对象是否可扩展。
Reflect.preventExtensions
Reflect.preventExtensions(obj)
设置对象为不可扩展,返回布尔值。
Reflect.ownKeys
Reflect.ownKeys(obj)
返回对象本身的所有属性。
等同于Object.getOwnPropertyNames
与Object.getOwnPropertySymbols
之和。
以上是脚本宝典为你收集整理的ES6精华:Proxy & Reflect全部内容,希望文章能够帮你解决ES6精华:Proxy & Reflect所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。