目录
- 1. 概述
- 2. Proxy 实例的方法
- 2.1 handler.get()
- 2.2 handler.set()
- 2.3 handler.apply()
- 2.4 handler.has()
- 2.5 handler.construct()
- 2.6 handler.deleteProperty()
- 2.7 handler.defineProperty()
- 2.8 handler.getOwnPropertyDescriptor()
- 2.9 handler.getPrototypeOf()
- 2.10 handler.isExtensible()
- 2.11 handler.isExtensible()
- 2.12 handler.preventExtensions()
- 2.12 handler.setPrototypeOf()
- 3 Proxy.revocable()
- 4 参考链接
1. 概述
Proxy
用于修改某些操作的默认行为,属于一种“元编程”(meta programming)。先看下面一个例子。
var obj= new Proxy({}, {
get: function(target, pro){
console.info(`getting ${pro}....`);
return "jidi"
}
})
obj.name;
// getting name....
// "jidi"
上面代码,为一个空对象重新设置了属性的读取行为,对设置了Proxy
的对象obj
,读取他的属性,都会返回jidi
。
ES6 原生提供Proxy
构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
Proxy对象的所有用法都是上面这种形式。其中,new Proxy
表示生成一个Proxy
实例,target
表示要代理的对象,handler
拦截器,表示代理行为,也是一个对象。
下面是另一个例子。
var obj = new Proxy({}, {
get: function (target, propKey) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey);
},
set: function (target, propKey, value) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value);
}
});
obj.count = 1; // setting count!
obj.count; // 1
obj.count++; // getting count! setting count!
obj.count; // 2
上面代码中,给空对象设置代理,读写obj
的属性都会执行代理行为。
要使得Proxy起作用,必须针对Proxy
实例(上例是obj
对象)进行操作,而不是针对目标对象(上例是空对象{}
)进行操作。
如果handler
没有设置任何操作,那就等同于直接通向原对象。
let target = {};
var obj = new Proxy(target, {});
obj.name = "jidi"
target.name; // "jidi"
上面代码中,handler
是一个空对象,访问obj
就等同于访问target
。
Proxy
实例也可以作为其他对象的原型对象。
// 定义Proxy实例
var proxy = new Proxy({}, {
get: function(){
return "jidi"
}
})
// 将Proxy实例设置为原型对象
let x = {};
Object.setPrototypeOf(x, proxy);
x.y; // "jidi"
上面代码中,对象x
本身没有属性y
,当执行x.y
时,javaScript引擎就会在对象x
的原型链上面去寻找,所以会在proxy
对象上读取该属性,导致代理生效,返回jidi
。
同一个handler
,可以设置多个操作。
let handler = {
get: function(target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function(target, thisBinding, args) {
return { value: args[0] };
},
construct: function(target, args) {
return { value: args[1] };
}
};
var proxy = new Proxy(function(x, y) {
return x + y;
}, handler);
proxy(1, 2); // {value: 1}
new proxy(1, 2); // {value: 2}
proxy.prototype === Object.prototype; // true
proxy.foo === "Hello, foo"; // true
2. Proxy 实例的方法
Proxy ,一共支持13 种拦截操作 。
2.1 handler.get()
handler.get()
方法用于拦截对象的读取属性操作。
该方法会拦截目标对象的以下操作:
- 访问属性:
proxy[foo]
和proxy.bar
- 访问原型链上的属性:
Object.create(proxy)[foo]
语法:
var p = new Proxy(target, {
get: function(target, property, receiver) {
...
}
});
参数
以下是传递给get方法的参数,this
上下文绑定在handler
对象上.。
- target:目标对象
- property:被获取的属性名。
- receiver:
Proxy
或者继承Proxy
的对象(可选)
返回值
get
方法可以返回任何值。
约束
如果违背了以下的约束,会抛出 TypeError
- 如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同。
- 如果要访问的目标属性没有配置访问方法,即
get
方法是undefined
的,则返回值必须为undefined
。
示例一
let person = {
name:"jidi"
}
let proxy = new Proxy(person, {
get: function(target, property, receiver) {
if (property in target) {
return target[property];
} else {
throw new ReferenceError("该属性不存在!");
}
}
})
proxy.name; // "jidi"
proxy.sex; // Uncaught ReferenceError: 该属性不存在!
示例二
function getMemeberByIndexFromArray(...members) {
let handler = {
get:function(target, property, receiver){
// 得到数组下标
let index = Number(property);
if(index < 0 ){ // 下标为负数,从后面取
property= String(target.length + index);
}
return Reflect.get(target, property, receiver);
}
}
let target = [...members];
return new Proxy(target, handler);
}
let x = getMemeberByIndexFromArray("a", "b", "c");
x[-1]; // "c"
上面代码演示了,读取数组索引为负数的成员。
示例三
let proxy = new Proxy({}, {
get: function(target, property, receiver){
return receiver;
}
})
proxy.a === proxy; // true
// 以proxy为原型构造对象
let x = Object.create(proxy);
x.a === x; // true
上面代码,演示了handler.get()
第三个参数的使用。
示例四
var obj = {};
// 定义空对象属性name,为不可写不可配置
Object.defineProperty(obj, "name", {
configurable: false,
enumerable: false,
value: "jidi",
writable: false
})
// get方法返回值与定义时的value不一致
var proxy = new Proxy(obj, {
get: function(target, property, receiver){
return 10;
}
})
proxy.name; // Uncaught TypeError: 'get' on proxy: property 'name' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value
// get方法返回值与定义时的value一致
var proxy = new Proxy(obj, {
get: function(target, property, receiver){
return "jidi";
}
})
proxy.name; // "jidi"
上面代码演示了违反约束的情况。
2.2 handler.set()
handler.set()
方法用于拦截设置属性值的操作。
该方法会拦截目标对象的以下操作:
- 指定属性值:
proxy[foo] = bar
和proxy.foo = bar
- 指定继承者的属性值:
Object.create(proxy)[foo] = bar
Reflect.set()
语法
var proxy = new Proxy(target, {
set: function(target, property, value, receiver){
...
}
})
参数
以下是传递给set方法的参数,this
上下文绑定在handler
对象上。
target
:目标对象property
:被设置的属性名.value
:被设置的新值receiver
:最初被调用的对象。通常是proxy
本身,但handler
的set
方法也有可能在原型链上或以其他方式被间接地调用(不一定是proxy
本身)。
返回值
set
方法返回一个布尔值,返回true
表示属性设置成功,如果返回false
且设置属性操作发生在严格模式下,会抛出TypeError
。
约束
如果违背以下的约束条件,proxy
会抛出一个TypeError
。
- 若目标属性是不可写及不可配置的,则不能改变它的值。
- 如果目标属性没有配置存储方法,即
set
方法是undefined
的,则不能设置它的值。 - 在严格模式下,若
set
方法返回false
,则会抛出一个TypeError
异常。
示例一
var man= new Proxy({}, {
set: function(target, property, value, receiver){
if(property === "age"){
if(!Number.isInteger(value)){
throw new TypeError("年龄必须为整数!")
}
if(value > 150){
throw new RangeError("年龄不合法!")
}
}
target[property] = value
return true
}
})
man.age = "jidi"; // Uncaught TypeError: 年龄必须为整数! at Object.set
man.age = 1112; // Uncaught RangeError: 年龄不合法!at Object.set
man.age = 12; // 12
示例二
var proxy = new Proxy({}, {
set: function(target, property, value, receiver) {
target[property] = receiver;
return true;
}
})
proxy.name = "jidi"
proxy.name === proxy; // true
上面代码,演示了第四个参数receiver
的使用。一般情况下,是proxy
实例本身。
示例三
var proxy = new Proxy({}, {
set: function(target, property, value, receiver) {
target[property] = receiver;
return true;
}
})
var obj = {};
// 将proxy设置为空对象的原型对象
Object.setPrototypeOf(obj , proxy);
obj.name = "jidi";
obj.name === obj; // true
上面代码,设置obj.name
属性时,对象obj
并没有name
属性,会在它的原型链上去寻找该属性。obj
的原型对象是proxy
,它是一个Proxy实例,会触发set
方法,此时receiver
会指向原始赋值行为对象obj
。
示例四
"use strict"; // 开启严格模式
var proxy = new Proxy({}, {
set: function(target, property, value, receiver){
target[property] = value
return false
}
})
proxy.name = "jidi"; // Uncaught TypeError: 'set' on proxy: trap returned falsish for property 'name '
上面代码,演示了在严格模式中,set
函数返回false
报错的情况。
2.3 handler.apply()
handler.apply()
方法用于拦截函数的调用。
handler.apply()
方法会拦截目标对象的以下操作。
proxy(...args)
Function.prototype.apply()
和Function.prototype.call()
Reflect.apply()
语法
var proxy = new Proxy({}, {
apply: function(target, thisArg, argumentsList) {
...
}
})
参数
handler.apply()
方法接受三个参数,this
上下文绑定在handler
对象上.。
- target:目标对象(函数)
- thisArg:被调用时的上下文对象
- argumentsList:被调用时的参数数组
返回值
handler.apply()
方法可以返回任何值。
约束
如果违反了以下约束,将抛出一个TypeError
。
- target必须是一个函数对象。
示例
var target = function(x, y){
return x + y }
var proxy = new Proxy(target,
{
apply: function(target, thisArg, argumentsList) {
return [...argumentsList]
}
}
)
target(1, 3); // 4
proxy(1, 3); // [1, 3]
proxy.call(null, 1, 2); // [1, 2]
proxy.apply(null, [1, 2]); // [1, 2]
2.4 handler.has()
handler.has()
方法可以看作是针对 in
操作的钩子。
handler.has()
方法可以拦截以下操作。
- 属性查询:
property in proxy
- 继承的属性查询:
property in Object.create(proxy)
- with 检查:
with(proxy) { (property ); }
Reflect.has()
语法
var proxy = new Proxy(targrt, {
has: function(target, property) {
...
}
})
参数
下面是传递给 has
方法的参数, this
上下文绑定在handler
对象上.。
- target:目标对象
- property:需要检查是否存在的属性
返回值
handler.has()
方法返回一个布尔值。
约束
如果违反了下面这些规则, proxy
将会抛出 TypeError
。
- 如果目标对象的某一属性本身不可被配置,则该属性不能够被代理隐藏.
- 如果目标对象为不可扩展对象,则该对象的属性不能够被代理隐藏
示例一
var target = {
name: "jidi",
age: 22
}
var proxy = new Proxy(target, {
has: function(target, property){
if(property === "age"){
return false;
}
return property in target;
}
})
"name" in target; // true
"age" in target; // true
"name" in proxy ; // true
"age" in proxy ; // false
上面代码中,通过proxy
将age
属性隐藏起来,不会被in
运算发现。
示例二
var obj = { a: 10 };
Object.preventExtensions(obj);
var p = new Proxy(obj, {
has: function(target, prop) {
return false;
}
});
'a' in p; // Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible
上面代码演示违反约束的情况。
2.5 handler.construct()
handler.construct()
方法用于拦截 new
操作符。
该拦截器可以拦截以下操作。
new proxy(...args)
Reflect.construct()
语法
var proxy = new Proxy(target, {
construct: function(target, argumentsList, newTarget) {
...
}
});
参数
下面的参数将会传递给construct
方法,this
绑定在handler
上。
- target:目标对象
- argumentsList:
constructor
的参数列表 - newTarget:最初被调用的构造函数
返回值
construct
方法必须返回一个对象。
约束
如果违反以下约定,代理将会抛出错误TypeError
。
- 必须返回一个对象.
示例一
// 定义构造函数
function Person(name, age) {
if(!(this instanceof Person)) {
return new Person(name, age);
}
this.name = name;
this.age = age;
}
var proxy = new Proxy(Person, {
construct: function(target, argumentsList, newTarget) {
return { ...argumentsList }
}
})
new proxy("jidi", 22); // {0: "jidi", 1: 22}
示例二
function Person(name, age) {
if(!(this instanceof Person)) {
return new Person(name, age);
}
this.name = name;
this.age = age;
}
var proxy = new Proxy(Person, {
construct: function(target, argumentsList, newTarget) {
return newTarget;
}
})
new proxy("jidi", 22) === proxy; // true
上面代码,演示了第三个参数newTarget
的使用,在上面例子中,newTarget
就是proxy
。
示例三
var proxy = new Proxy({}, {
construct: function(target, argumentsList, newTarget) {
return {};
}
});
new proxy(); // Uncaught TypeError: proxy is not a constructor
上面代码未能正确初始化Proxy。Prxoy在初始化时,target
必须是一个有效的constructor
供new
操作符调用。
示例四
function Person(name, age) {
if(!(this instanceof Person)) {
return new Person(name, age);
}
this.name = name;
this.age = age;
}
var proxy = new Proxy(Person, {
construct: function(target, argumentsList, newTarget) {
return true;
}
})
new proxy("jidi", 22); // Uncaught TypeError: 'construct' on proxy: trap returned non-object ('true')
上面代码中,construct
方法返回值不是对象,直接报错。
2.6 handler.deleteProperty()
handler.deleteProperty()
方法用于拦截对对象属性的 delete
操作。
该方法会拦截以下操作.
- 删除属性:
delete proxy[foo]
和delete proxy.foo
Reflect.deleteProperty()
语法
var p = new Proxy(target, {
deleteProperty: function(target, property) {
...
}
});
参数
deleteProperty
方法将会接受以下参数。this
被绑定在handler
上。
- target:目标对象
- property:待删除的属性名
返回值
deleteProperty
必须返回一个 Boolean
类型的值,表示了该属性是否被成功删除。
约束
如果违背了以下约束,代理将会抛出一个 TypeError
。
- 如果目标对象的属性是不可配置的,那么该属性不能被删除。
示例一
var person = {
name:"jidi",
age: 22
}
var proxy = new Proxy(person, {
deleteProperty: function(target, property) {
if(property === "age") {
throw new ReferenceError("该属性不能删除!")
}
return delete target[property]
}
})
delete proxy.age; // Uncaught ReferenceError: 该属性不能删除! at Object.deleteProperty
delete proxy.name; // true
person; // {age: 22}
示例二
var person ={};
Object.defineProperty(person, "name", {
value: "jidi",
configurable: false
})
var proxy = new Proxy(person, {
deleteProperty: function(target, property) {
delete target[property]
return true;
}
})
delete proxy .name; // Uncaught TypeError: 'deleteProperty' on proxy: trap returned truish for property 'name' which is non-configurable in the proxy target
上面代码演示了目标对象(target
)自身的不可配置(configurable = false
)的属性,被deleteProperty
方法删除的情况。
2.7 handler.defineProperty()
handler.defineProperty()
用于拦截对对象的 Object.defineProperty()
操作。
该方法会拦截目标对象的以下操作 :
Object.defineProperty()
Reflect.defineProperty()
proxy.property='value'
语法
var proxy = new Proxy(target, {
defineProperty: function(target, property, descriptor){
...
}
})
参数
下列参数将会被传递给 defineProperty
方法。this
绑定在 handler
对象上。
- target:目标对象
- property:属性名
- descriptor:待定义或修改的属性的描述符
返回值
defineProperty
方法必须以一个 Boolean
返回,表示定义该属性的操作成功与否。
约束
如果违背了以下的约定,proxy
会抛出 TypeError
:
- 如果目标对象不可扩展,则不能增加目标对象上不存在的属性,否则会报错。
- 如果目标对象的某个属性不可写或不可配置,则不得改变这两个设置。
- 在严格模式下,
false
作为返回值将会抛出TypeError
异常.
示例
var person = {
name: "jidi"
}
var proxy = new Proxy(person, {
defineProperty: function(target, property, descriptor) {
if(property === "age") {
return false;
}
Object.defineProperty(target, property, descriptor)
return true
}
})
proxy.age = 12;
person; // { name: "jidi" }
proxy.sex = "男";
person; // { name: "jidi", sex: "男" }
上面例子中,如果设置属性age
,defineProperty
返回false
,将不会成功。设置属性sex
,目标对象会增加一个属性sex
。
2.8 handler.getOwnPropertyDescriptor()
handler.getOwnPropertyDescriptor()
方法是 Object.getOwnPropertyDescriptor()
的钩子。
该方法可以拦截以下操作:
Object.getOwnPropertyDescriptor()
Reflect.getOwnPropertyDescriptor()
语法
var proxy = new Proxy(target, {
getOwnPropertyDescriptor: function(target, property) {
...
}
});
参数
下列参数将会被传递给 getOwnPropertyDescriptor
方法。this
绑定在 handler
对象上。
- target:目标对象
- property:返回属性名称的描述
返回值
getOwnPropertyDescriptor
方法必须返回一个属性描述对象或 undefined
。
示例
var proxy = new Proxy({ name: "jidi"}, {
getOwnPropertyDescriptor: function(target, property) {
return { configurable: true, enumerable: true, value: 10 };
}
});
Object.getOwnPropertyDescriptor(proxy, "name"); // {value: 10, writable: false, enumerable: true, configurable: true}
2.9 handler.getPrototypeOf()
handler.getPrototypeOf
方法主要用来拦截获取对象原型。
该方法会拦截下面这些操作:
Object.getPrototypeOf()
Reflect.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
语法
var proxy = new Proxy(target, {
getPrototypeOf(target) {
...
}
});
参数
下列参数将会被传递给 getPrototypeOf
方法。this
绑定在 handler
对象上。
- target:被代理的目标对象
返回值
getPrototypeOf
方法的返回值必须是一个对象或者 null
。
约束
如果违背了以下的约定,proxy
会抛出 TypeError
:
getPrototypeOf()
方法返回的不是对象也不是null
。- 目标对象是不可扩展的,且
getPrototypeOf()
方法返回的原型不是目标对象本身的原型。
示例
var proto = {};
var proxy = new Proxy({}, {
getPrototypeOf(target) {
return proto;
}
});
Object.getPrototypeOf(proxy ) === proto // true
2.10 handler.isExtensible()
handler.isExtensible()
用于拦截对对象执行Object.isExtensible()
。
该方法会拦截目标对象的以下操作:
Object.isExtensible()
Reflect.isExtensible()
语法
var proxy = new Proxy(target, {
isExtensible: function(target) {
...
}
});
参数
下列参数将会被传递给isExtensible
方法。 this
绑定在handler
对象上。
- target:目标对象
返回值
isExtensible
方法必须返回一个 Boolean
值或可转换成Boolean
的值。
约束
如果违背了以下的约束,proxy
会抛出TypeError
:
Object.isExtensible(proxy)
必须同Object.isExtensible(target)
返回相同值。也就是必须返回true
或者为true
的值,返回false
和为false
的值都会报错。
示例一
var proxy = new Proxy({}, {
isExtensible: function(target) {
return true;
}
});
Object.isExtensible(proxy); // true
示例二
var proxy = new Proxy({}, {
isExtensible: function(target) {
return false;
}
});
Object.isExtensible(proxy ); // Uncaught TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true') at Function.isExtensible
2.11 handler.isExtensible()
ownKeys
方法用来拦截对象自身属性的读取操作。
该方法可以拦截以下操作:
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
for...in
循环
语法
var proxy = new Proxy(target, {
ownKeys: function(target) {
...
}
});
参数
下面的参数被传递给ownKeys
。this
被绑定在handler
上。
- target:目标对象
返回值
ownKeys
方法必须返回一个可枚举对象
约束
如果违反了下面的约束,proxy
将抛出错误 TypeError
:
ownKeys
的结果必须是一个数组.- 数组的元素类型要么是一个
String
,要么是一个Symbol
- 结果列表必须包含目标对象的所有不可配置、自有属性的
key
- 如果目标对象不可扩展,那么结果列表必须包含目标对象的所有自有属性的
key
,不能有其它值
示例
var proxy = new Proxy({}, {
ownKeys: function(target) {
return ['a', 'b', 'c'];
}
});
Object.getOwnPropertyNames(proxy ); // [ 'a', 'b', 'c' ]
2.12 handler.preventExtensions()
handler.preventExtensions()
方法用于设置对Object.preventExtensions()
的拦截。
该方法可以拦截以下操作:
Object.preventExtensions()
Reflect.preventExtensions()
语法
var proxy = new Proxy(target, {
preventExtensions: function(target) {
...
}
});
参数
以下参数传递给 preventExtensions
方法. this
会绑定到这个handler
- target: 所要拦截的目标对象.
返回值
preventExtensions
方法返回一个布尔值
约束
如果违反了下列约束, proxy
会抛出一个 TypeError
:
- 如果
Object.isExtensible(proxy)
是false
,Object.preventExtensions(proxy)
只能返回true
。
2.12 handler.setPrototypeOf()
handler.setPrototypeOf()
方法主要用来拦截 Object.setPrototypeOf()
这个方法可以拦截以下操作:
Object.setPrototypeOf()
Reflect.setPrototypeOf()
语法
var proxy = new Proxy(target, {
setPrototypeOf: function(target, prototype) {
...
}
});
参数
以下参数传递给 setPrototypeOf
方法
- target:被拦截目标对象
- prototype:对象新原型或
null
返回值
如果成功返回 true
,否则返回 false
约束
如果违反了下列规则,则proxy
将抛出一个TypeError
:
- 如果
target
不可扩展, 原型参数必须与Object.getPrototypeOf(target)
的值相同
3 Proxy.revocable()
Proxy.revocable()
方法可以用来创建一个可撤销的代理对象。一旦某个代理对象被撤销,它将变得几乎完全不可调用,在它身上执行任何的可代理操作都会抛出TypeError
异常。
语法
Proxy.revocable(target, handler);
参数
以下参数传递给 revocable
方法:
- target:将用 Proxy 封装的目标对象。可以是任何类型的对象。
- handler:一个对象,其属性是一批可选的函数,这些函数定义了对应的操作被执行时代理的行为。
返回值
返回一个对象。该对象结构为{"proxy": proxy, "revoke": revoke}
,其中:
- proxy:表示新生成的代理对象本身,和用
new Proxy(target, handler)
创建的代理对象没什么不同,只是它可以被撤销掉。 - revoke:撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。
示例
// 可撤销的proxy对象
var revocable = Proxy.revocable({}, {
get(target, name) {
return "[[" + name + "]]";
}
});
var proxy = revocable.proxy;
proxy.name; // "[[name]]"
// 执行撤销方法
revocable.revoke();
proxy.sex = "男"; // Uncaught TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.age; // Uncaught TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
4 参考链接
本篇博文是我自己学习笔记,原文请参考:ECMAScript 6 入门,MDN网站。
如有问题,请及时指出!
欢迎沟通交流,邮箱:jidi_jidi@163.com。