本文参考
本文参考的是MDN文档https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
对其进行了一定的整理以及编排,以便符合讲解的顺序。
Proxy是什么?
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
如果说数据劫持是对对象中某个属性进行了劫持,那Proxy可以理解为对一个对象进行劫持。
语法以及参数
const p = new Proxy(target, handler)
target
要使用 Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。``
handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
handler的方法
handler.getPrototypeOf()
是Object.getPrototypeOf方法的捕捉器。
当读取代理对象的原型时,使用该函数进行劫持。
const handler = {
getPrototypeOf(target) {
return {name:'8888'};
}
};
const proxy1 = new Proxy(obj, handler);
handler.setPrototypeOf()
是Object.setPrototypeOf 方法的捕捉器。
当设置代理对象的原型时,使用该函数进行劫持,如果不想设置一个新的原型,可以返回false
var handlerReturnsFalse = {
setPrototypeOf(target, newProto) {
return false;
}
};
var newProto = {}, target = {};
var p1 = new Proxy(target, handlerReturnsFalse);
Object.setPrototypeOf(p1, newProto); // throws a TypeError
Reflect.setPrototypeOf(p1, newProto); // returns false
handler.preventExtensions()
Object.preventExtensions 方法的捕捉器。
Object.preventExtensions 是让对象变得不可拓展的函数,当设置不可拓展时,会触发handler.preventExtensions
注意,如果对象是可拓展的,那么只能返回false
var p = new Proxy({}, {
preventExtensions: function(target) {
console.log('called');
Object.preventExtensions(target);
return true;
}
});
console.log(Object.preventExtensions(p)); // "called"
// false
handler.isExtensible()
Object.isExtensible 方法的捕捉器。
该方法用于拦截Object.isExtensible()。,而Object.isExtensible用于判断对象是否是一个可拓展的对象
注意,该函数返回必须为true
var p = new Proxy({}, {
isExtensible: function(target) {
console.log('called');
return true;//也可以return 1;等表示为true的值
}
});
console.log(Object.isExtensible(p)); // "called"
// true
handler.getOwnPropertyDescriptor()
handler.getOwnPropertyDescriptor()方法是 Object.getOwnPropertyDescriptor()的钩子。
该方法用于拦截Object.getOwnPropertyDescriptor(),Object.getOwnPropertyDescriptor获取对象自有属性的描述符,自有属性指直接赋予对象的属性,而且原型链中的属性
getOwnPropertyDescriptor 必须返回一个 object 或 undefined。
如果属性作为目标对象的不可配置的属性存在,则该属性无法报告为不存在。
如果属性作为目标对象的属性存在,并且目标对象不可扩展,则该属性无法报告为不存在。
如果属性不存在作为目标对象的属性,并且目标对象不可扩展,则不能将其报告为存在。
属性不能被报告为不可配置,如果它不作为目标对象的自身属性存在,或者作为目标对象的可配置的属性存在。
Object.getOwnPropertyDescriptor(target)的结果可以使用 Object.defineProperty 应用于目标对象,也不会抛出异常。
var p = new Proxy({ a: 20}, {
getOwnPropertyDescriptor: function(target, prop) {
console.log('called: ' + prop);
return { configurable: true, enumerable: true, value: 10 };
}
});
console.log(Object.getOwnPropertyDescriptor(p, 'a').value); // "called: a"
// 10
configurable
当且仅当该属性的 configurable
键值为 true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。**
**默认为** false
。**
enumerable
当且仅当该属性的 enumerable
键值为 true
时,该属性才会出现在对象的枚举属性中。**
**默认为 false
**。**
以下代码则违反了规则。
var obj = { a: 10 };
Object.preventExtensions(obj);
var p = new Proxy(obj, {
getOwnPropertyDescriptor: function(target, prop) {
return undefined;
}
});
Object.getOwnPropertyDescriptor(p, 'a'); // TypeError is thrown
handler.defineProperty(
Object.defineProperty 方法的捕捉器。
Object.defineProperty是啥我就不多说了
var p = new Proxy({}, {
defineProperty: function(target, prop, descriptor) {
console.log('called: ' + prop);
return true;
}
});
var desc = { configurable: true, enumerable: true, value: 10 };
Object.defineProperty(p, 'a', desc); // "called: a"
handler.has()
in操作符的捕捉器。
如果指定的属性在指定的对象或其原型链中,则**in
运算符****返回true
。**
const handler1 = {
has(target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
const monster1 = {
_secret: 'easily scared',
eyeCount: 4
};
const proxy1 = new Proxy(monster1, handler1);
console.log('eyeCount' in proxy1);
// expected output: true
console.log('_secret' in proxy1);
// expected output: false
console.log('_secret' in monster1);
// expected output: true
handler.get()
属性读取操作的捕捉器。get的意思与Object.defineProperty 中的get一致
需要注意的是
如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同。
如果要访问的目标属性没有配置访问方法,即get方法是undefined的,则返回值必须为undefined。
var p = new Proxy({}, {
get: function(target, prop, receiver) {
console.log("called: " + prop);
return 10;
}
});
console.log(p.a); // "called: a"
// 10
handler.set()
属性设置操作的捕捉器。set的意思与Object.defineProperty 中的set一致
const monster1 = { eyeCount: 4 };
const handler1 = {
set(obj, prop, value) {
if ((prop === 'eyeCount') && ((value % 2) !== 0)) {
console.log('Monsters must have an even number of eyes');
} else {
return Reflect.set(...arguments);
}
}
};
const proxy1 = new Proxy(monster1, handler1);
proxy1.eyeCount = 1;
// expected output: "Monsters must have an even number of eyes"
console.log(proxy1.eyeCount);
// expected output: 4
proxy1.eyeCount = 2;
console.log(proxy1.eyeCount);
// expected output: 2
handler.deleteProperty()
delete操作符的捕捉器。
**delete
操作符****用于删除对象的某个属性;如果没有指向这个属性的引用,那它最终会被释放。**
如果目标对象的属性是不可配置的,那么该属性不能被删除。
var p = new Proxy({}, {
deleteProperty: function(target, prop) {
console.log('called: ' + prop);
return true;
}
});
delete p.a; // "called: a"
handler.ownKeys()
Object.getOwnPropertyNames方法和 Object.getOwnPropertySymbols方法的捕捉器。
Object.getOwnPropertyNames()
**方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。**
var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]
// 类数组对象
var obj = { 0: "a", 1: "b", 2: "c"};
console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]
Object.getOwnPropertySymbols()
方法返回一个给定对象自身的所有 Symbol 属性的数组。(常规开发涉及较少,这里就不做讲解了)
约束
ownKeys 的结果必须是一个数组.
数组的元素类型要么是一个 String ,要么是一个 Symbol.
结果列表必须包含目标对象的所有不可配置(non-configurable )、自有(own)属性的key.
如果目标对象不可扩展,那么结果列表必须包含目标对象的所有自有(own)属性的key,不能有其它值.
var p = new Proxy({}, {
ownKeys: function(target) {
console.log('called');
return ['a', 'b', 'c'];
}
});
console.log(Object.getOwnPropertyNames(p)); // "called"
// [ 'a', 'b', 'c' ]
var obj = {};
Object.defineProperty(obj, 'a', {
configurable: false,
enumerable: true,
value: 10 }
);
var p = new Proxy(obj, {
ownKeys: function(target) {
return [123, 12.5, true, false, undefined, null, {}, []];
}
});
console.log(Object.getOwnPropertyNames(p));
// TypeError: proxy [[OwnPropertyKeys]] 必须返回一个数组
// 数组元素类型只能是String或Symbol
handler.apply()
函数调用操作的捕捉器。
handler.apply
** 方法用于拦截函数的调用。**
var p = new Proxy(function() {}, {
apply: function(target, thisArg, argumentsList) {
console.log('called: ' + argumentsList.join(', '));
return argumentsList[0] + argumentsList[1] + argumentsList[2];
}
});
console.log(p(1, 2, 3)); // "called: 1, 2, 3"
// 6
handler.construct()
new操作符的捕捉器。
handler.construct()
方法用于拦截new 操作符. 为了使new操作符在生成的Proxy对象上生效,用于初始化代理的目标对象自身必须具有[[Construct]]内部方法(即 new target
必须是有效的)。
注意,必须返回一个对象
var p = new Proxy(function() {}, {
construct: function(target, argumentsList, newTarget) {
console.log('called: ' + argumentsList.join(', '));
return { value: argumentsList[0] * 10 };
}
});
console.log(new p(1).value); // "called: 1"
// 10
下面的代码违反了约定.
var p = new Proxy(function() {}, {
construct: function(target, argumentsList, newTarget) {
return 1;
}
});
new p(); // TypeError is thrown
下面的代码未能正确的初始化Proxy。Proxy初始化时,传给它的target
必须具有一个有效的constructor供new
操作符调用。
var p = new Proxy({}, {
construct: function(target, argumentsList, newTarget) {
return {};
}
});
new p(); // TypeError is thrown, "p" is not a constructor
结语
那么到这里你已经了解Proxy的handle函数以及操作方法,如果有不明白的可以先放一放,了解如何使用就可以了,以后在使用的过程中可以慢慢查阅。
祝大家有一个愉快的油猴编程之旅