参考阮一峰博客:https://es6.ruanyifeng.com/#docs/proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
1、生成Proxy实例
1)ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
Proxy 对象的所有用法,都是上面这种形式,不同的只是handler
参数的写法。
new Proxy()
表示生成一个Proxy
实例;target
参数表示所要拦截的目标对象;handler
参数也是一个对象,用来定制拦截行为
2)实例proxy
- 调用
new Proxy()
,可以创建一个代理proxy用来替代拦截后的目标对象target
。代理允许拦截在目标对象上的底层操作(比如读取或获取target对象的属性值) - 没有拦截行为时,代理对象
p
会将所有应用到它的操作转发到目标对象target
上。
let target = {};
let p = new Proxy(target, {});
p.a = 37; // 操作转发到目标
console.log(target.a); // 操作已经被正确地转发
2、Proxy 支持的部分拦截操作。即handler
参数
- get(target, propKey, receiver):拦截对象属性的读取。如
proxy.foo
- set(target, propKey, value, receiver):拦截对象属性的设置,如
proxy.foo = v
,返回一个布尔值。 - apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。 - construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)
。 - has(target, propKey):拦截
HasProperty
操作,即判断对象是否具有某个属性。 - ownKeys(target):拦截
Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。
2.1 get
get
方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
1)作用
- 可以创造一些本来没有的属性
- 可以自定义一些语法糖操作
- get返回一个函数的话可以把一个属性转换成方法
- 可以在取数据时对数据进行验证和转换
2)针对作用,举例
// 定义要拦截的目标对象
var base = {
a : 520,
small : "pink cc!!"
}
// 1. 生成proxy实例。base是所要拦截的目标对象
var proxy = new Proxy( base, {
//target: 目标对象(如base); property:目标对象的属性名(如a)。
get(target, property, receiver){
//(1) 创建本来不存在的属性
if(property === "ghost"){
return "ghostValue"
}
//(2) 自定义语法糖
if(property.includes("_")){ // 如果属性名包含_,则根据_后面的值进行相应操作
const active = property.split("_")[1]; //操作
const prop = property.split("_")[0]; //属性
switch(active){
case "Big":
return receiver[prop].toLocaleUpperCase(); //将属性prop的值转为大写
default:
break;
}
}
//(3) 将属性转换为方法。返回函数
if(property === "fn"){ //若访问属性名fn,返回函数
return function(value){
console.log(value)
}
}
//(4) 验证属性值
if(!(property in target)){ // 对象target没有属性property
throw new ReferenceError("属性"+property+"不存在");
}
// 未经操作的属性,则直接返回对象target中对应的属性值。
return target[property]; //由于property是字符串,只能用[]访问
}
})
// 1) 创建不存在的属性ghost。
console.log(proxy.ghost); // ghostValue
// 2) 自定义语法糖,将属性值转化为大写。
console.log(proxy.small_Big); // PINK CC!!
// 3) 将属性转换为方法。返回函数
console.log(proxy.fn); // ƒ (value){console.log(value)}
// 执行返回的函数
console.log(proxy.fn("将属性转化为方法")); //将属性转化为方法
// 4) 验证属性值。属性hh不存在,抛出错误:test.html:63 Uncaught ReferenceError: 属性hh不存在
console.log(proxy.hh);
// 访问存在的属性。520
console.log(proxy.a); // 520
2.2 set
set
方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。注意:
set
代理应当返回一个布尔值。严格模式下,set
代理如果没有返回true
,就会报错。
1)作用:
- 可以用来监听数据更改事件
- 可以用来更改数据格式
- 防止这些内部属性被外部读写。比如以"_"开头的私有变量
- 可以用来验证属性是否符合要求
2)针对作用,举例:
const target = {
age: 18,
_money : "一千万"
};
const proxy = new Proxy(target, {
set(obj, prop, value, receiver){
// 1) 屏蔽对私有变量的读写操作。比如以"_"开头的私有变量
if(prop[0] === "_"){ //私有变量
throw new Error("禁止读写内部属性:"+ prop);
}
// 2) 验证属性值是否符合要求。对年龄属性验证
if(prop === "age"){
if(!Number.isInteger(value)){ // 不是数字
throw new TypeError("年龄不是数字");
}
if(value > 200){ // 年龄超过范围
throw new RangeError("年龄值无效");
}
}
obj[prop] = value; // 对于满足条件的属性直接保存
return true;
}
});
// 1) 验证属性值是否符合要求
proxy.age = 20; //设置有效的年龄
console.log(proxy.age); //20
proxy.age = 201; //设置无效的年龄, 抛出错误 Uncaught RangeError: 年龄值无效
//2) 屏蔽对私有变量的读写操作
proxy._money = "一毛"; // 抛出错误 Uncaught Error: 禁止读写内部属性:_money
2.3 apply
apply
方法拦截函数的调用、call
和apply
操作。可以接受三个参数,分别是目标对象、目标对象的上下文对象(
this
)和目标对象的参数数组。
举个栗子:
变量p
是 Proxy 的实例,当它作为函数调用时(p()
),就会被apply
方法拦截,返回一个字符串。
// 目标对象
var target = function () {
return 'I am the target';
};
// 代理
var p = new Proxy(target, {
// apply操作。拦截target函数的调用
apply: function () {
return 'I am the proxy';
}
});
// 拦截了target函数的调用。执行了apply中的函数
console.log(p()) // "I am the proxy"
2.4 construct
- 用于拦截
new
命令。construct()
方法返回的必须是一个对象,否则会报错。- 另外,由于
construct()
拦截的是构造函数,所以它的目标对象必须是函数,否则就会报错。construct()
方法中的this
指向的是handler
,而不是实例对象- 可以接受三个参数。
target
:目标对象。args
:构造函数的参数数组。newTarget
:创造实例对象时,new
命令作用的构造函数(下面例子的p
)
1)举个栗子:
const p = new Proxy(function () {}, {
// target:目标对象; args: new对象时传入的参数
construct: function(target, args) {
console.log('called: ' + args.join(', ')); // 打印输入的参数
return { value: args[0] * 10 }; //目标对象的value属性值
}
});
(new p(1)).value // 10
/*
"called: 1"
*/
3、代理对象proxy 和 拦截操作中的this指向问题
1)代理对象proxy的this指向
一旦目标对象target被代理之后,他的this就指向了代理对象proxy
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true。目标函数的this指向proxy
2)拦截操作中的this指向
- handler定义的拦截操作中this指向 handler(
get()
和set()
拦截函数内部的this
,指向的都是handler
对象) - receiver指向的是代理对象proxy
const target = {
m: 100
};
const handler = {
get(target, property, receiver) {
console.log(this === handler) // true。 拦截操作中的this指向 handler
console.log(receiver === proxy) // true。receiver 指向 代理对象proxy
return target[property]
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.m)