代理基础

代理是目标对象的抽象。代理类似于c++的指针,可以通过代理操作对象,但是它又是一个独立的变量。在代理上的修改会反映到目标对象上,在目标对象上的修改也会对代理起作用

创建

const target = 
{
id: 'target'
};

const handler = {};

const proxy = new Proxy(target, handler);//两个参数,第一个是操作的对象,第二个是处理程序对象,用来定义捕获器

console.log(proxy.id);//target

可以通过revocable()定义撤销代理,并且还可以通过一个返回值进行撤销,撤销完成后再使用代理会产生异常

const target = {foo: 'bar'};
const handler = { get(){ return 'intercepted';}};
const {proxy, revoke} = Proxy.revocable(traget, handler);
console.log(proxy.foo);//intercepted
console.log(target.foo);//bar

revoke();//执行撤销,是Proxy.revocable的一个返回值
console.log(proxy.foo);

代理的一些不足

由于代理和原对象毕竟是两个对象,所以在涉及this时可能会出现问题

const wm = ew WeakMap();

class User
{
constructor(user_id)
{
wm.set(this.user_id);
}

set id(user_id)
{
wm.set(this, user_id);
}

get id()
{
wm.get(this);
}
}

const user = new User(123);
console.log(user.id);//123

const userInstanceProxy = new Proxy(user, {});
console.log(userInstanceProxy.id);//undefine
这是因为user和userInstanceProxy对象不同,因此this不同,导致无法检索

捕获器(trap)

使用代理主要就是为了定义捕获器。捕获器是target中基本操作的拦截器。每次调用代理时,首先会调用捕获器函数,从而进行拦截和修改。捕获器类似于sql中的触发器

例如

const target = 
{
foo: 'bar'
}

const handler =
{
get()
{
return 'handler override';
}
}
const proxy = new Proxy(target, handler);

console.log(proxy.foo);//handler override
console.log(proxy['foo']);//handler override
console.log(Object.create(proxy)['foo']);//handler override

通过定义get()捕获器,之后每次执行get操作时,都会触发get捕获器.诸如.[]等操作都认为是get操作。但是在原对象上执行的操作不受影响。

捕获器可以访问当前是在操作哪个变量,从而执行相应的操作,例如

const target =
{
foo: 'bar'
};

const handler =
{
get(rapTarget, property, receiver)//trapTarget是代理的目标对象,receiver是代理自己
{
console.log(trapTarget === target);
console.log(property);
console.log(receiver === proxy);
}
};

const proxy = new Proxy(target, handler);
proxy.foo;//true, foo, true

捕获器不变式

捕获器可以捕获大部分基本操作,但是有一些操作会导致异常。捕获器不变式就是保证不会出现异常的约束。

例如:目标对象有一个不可写的属性,如果捕获器中返回了一个其他值,那么就会出现TypeError

const target = {};
Object.defineProperty(target, 'foo',{
configurable: false,
writable: false,
value: 'bar'
});

const handler =
{
get()
{
return 'qux';
}
}
const proxy = new Proxy(target, handler);
console.log(proxy.foo);//TypeError

捕获器总共可以捕获13种基本操作

get()

get会拦截获取属性值的操作,对应的反射方法为Reflect.get()

  • get(target, property, receiver): target是目标对象,property是操作属性的名字,receiver是带你对象

拦截的操作有:

  • proxy.property
  • proxy[property]
  • Object.create(proxy)[property]
  • Reflect.get(proxy, property, receiver)//反射是调用原函数

set

set拦截设置属性值的操作,反射方法为Reflect.set()

  • bool set(target, property, value, receiver): value是设置的值,返回值为true代表操作成功,false表示操作失败

拦截的操作:

  • proxy.property = value
  • proxy[property] = value
  • Object.create(proxy)[property] = value
  • Reflect.set(proxy, property, value, receiver)

捕获器不变式(约束):

  • 如果target的property不可写且不可配置,则不能修改目标属性的值
  • 如果target.property不可配置并且Post not found: Set为undefined,则不能修改属性值

has

has会捕获in操作符中的操作,反射为Reflect.has()

  • bool has(target, property): 返回值表示这个属性是否存在

拦截的操作:

  • property in proxy
  • property in Object.create(proxy)
  • with(proxy) { (property);}
  • Reflect.has(proxy, property);

不变式:

  • 如果target.property存在且不可配置,则必须返回true
  • target.property存在且目标对象不可扩展(不能添加新的属性,通过Object.preventExtensions(object)设置),则必须返回true

defineProperty

拦截Object.defineProperty(),反射方法为Reflect.defineProperty()

  • bool defineProperty(target, property, descriptor): 返回值表示是否成功定义。descriptor有enumrable、configurable、writable、value、get、set
const my Target = {};
const proxy = new Proxy(myTarget, {
defineProperty(target, property, descriptor)
{
return Reflect.defineProperty(...arguments);
}
});

不变式:

  • 如果目标对象不可扩展,则无法定义属性
  • 如果有一个可配置属性,不可添加同名的不可配置属性
  • 如果有一个不可配置属性,不可添加同名的配置属性

getWonPropertyDescriptor

捕获Object.getOwnPropertyDescriptor()。反射方法为Relfect.getOwnPropertyDescriptor()

  • Object getOwnPropertyDescriptor(target, property): 返回描述对象,如果对象不存在则返回undefined

不变式:

  • target.property存在且可配置,这必须返回对应对象
  • target.property存在且target不可扩展,则处理程序必须返回一个表示该属性存在的对象
  • target.property不存在,则返回undefined

deleteProperty

会在delete操作符中调用,对应的反射方法为Reflect.deleteProperty()

  • bool deleteProperty(target, property): 返回值表示删除属性是否成功

不变式:

  • 如果target.property存在且不可配置,则处理程序不能删除这个属性

onwKeys

拦截Object.keys()(返回对象属性的属性名),反射方法为Reflect.ownKeys()

  • list ownKyes(target): 返回可枚举对象

不变式:

  • 返回的枚举对象必须包含所有的不可配置的自有属性
  • 如果对象不可扩展,则返回可枚举对象必须准确包含自有属性键

getPrototypeOf

捕获获得prototype的操作。反射API为Reflect.getPrototypeOf()

  • object getPrototypeOf(target): 返回值是原型对象或者是NULL

拦截的操作:

  • Object.getPrototypeOf(proxy)
  • Reflect.getPrototypeOf(proxy)
  • proxy.__proto__
  • Object.prototype.isPrototypeOf(proxy)
  • proxy instanceof Object

捕获器不变式:

  • 如果target不可扩展,则唯一有效的返回值是Object

setPrototypeOf

捕获Object.setPrototypeOf(),反射的API为Reflect.setPrototypeOf()

  • bool setPrototypeOf(target, prototype): 返回值表示设置是否成功

不变式:

  • 如果target不可扩展,则唯一有效的参数是这个对象的原型

isExtensible

捕获Object.isExtensible,反射API为Reflect.isExtensible()

  • bool isExtensible(target): 返回值表示target是否可扩展

不变式:

  • 如果target可扩展,则处理程序必须返回true
  • 如果target不可扩展,则处理程序必须返回false

preventExtensions

返回值表示target是否已经不可扩展

不变式: 如果Object.isExtensible(proxy)是false,那么处理程序必须返回true

apply

它会在调用函数时调用,反射方法为Reflect.apply()

  • apply(target, thisArg, argumentsList): thisArg是调用函数时的this参数,返回值任意

拦截的操作:

  • proxy(…argumentList)
  • Fuction.prototype.apply(thisArg, argumentList)
  • Function.prototype.call(thisArg, …argumentsList)
  • Reflect.apply(target, thisArgument, argumentList)

不变式:

  • target必须是一个函数对象

construct

它会在new操作符中被调用,反射方法为Reflect.construct()

  • Object construct(target, argumentsList, newTarget): target是目标构造函数,newTarget是最初被调用的构造函数(可能子类调用了父类的构造函数)。返回值是构造完成的对象

不变式:

  • target必须可以被用作构造函数

代理的应用

跟踪属性访问

我们可以通过捕获get、set和has等操作,对对象进行监控

const user = 
{
name: 'Jake'
};

const proxy = new Proxy(user, {
get(target, property, receiver)
{
console.log('getting ${property}');
return Reflect.get(...arguments);
}

set(target, property, value, receiver)
{
console.log('setting ${property}=${value}');
return Reflect.set(...arguments);
}
});

proxy.name;//getting name
proxy.age = 27 // setting age=27

隐藏属性

因为代理内部对外部代码是不可见的,所以可以很方便的隐藏对象属性.此外,还可以通过set拒绝属性访问,通过apply对函数参数进行审查。

const hidden = ['foo', 'bar'];
const targetObject =
{
foo: 1,
bar: 2,
baz: 3
}

const proxy = new Proxy(targetObject, {
get(target, property)
{
if(hidden.includes(property))
{
return Reflect.get(...arguments);
}
else
{
return undefined;
}
}
});

数据绑定

可以在构造时就添加入列表,避免错误。

const list = []
class User
{
constructor(name)
{
this.name_ = name;
}
}

const proxy = new Proxy(User, {
constructor()
{
const newUser = Reflect.construct(...arguments);
list.push(newUser);
return newUser;
}
});

反射

在java或c#语言中,反射的含义是通过函数名字字符串找到对应的函数.例如

const target = 
{
foo: 'bar';
};

const handler =
{
get()
{
return Reflect.get(...arguments);//通过Reflect.get反射到了target的get方法并且可以直接访问原对象
}
};

const proxy = new Proxy(target, handler);
console.log(proxy.foo);//bar
console.log(proxy.foo);//bar

我们可以使用这种方法获得本应该获得的结果,并且还可以加上一些修饰,例如
get()
{
return Reflect.get(...arguments) + "!";
}