Proxy 语法
const p = new Proxy(targer, handler);
-
targer 参数说明
要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
-
handler 参数说明
此函数的功能主要设置对代理对象属性操作的方法,参数类型为对象。
Proxy 对象中 handler 对象所拥有的方法
-
applay(目标对象, 被调用时的上下文对象, 被调用时的参数数组)
拦截函数的调用。这里需要注意目标对象必须是函数。假如这里目标对象设置为对象的话,程序在运行的时候会报错。
-
construct(目标对象, constructor 的参数列表, 最初被调用的构造函数)
用于拦截 new 操作符。为了使 new 操作符在生成的 Proxy 对象上生效,用于初始化代理的目标对象自身必须具有 [[Construct]] 内部方法(即 new target 必须是有效的)。
-
defineProperty(目标对象, 待检索其描述的属性名, 待定义或修改的属性的描述符)
用于拦截对象的 Object.defineProperty() 操作。
-
deleteProperty(目标对象, 待删除的属性名)
用于拦截对对象属性的 delete 操作。
-
get(目标对象, 被获取的属性名, Proxy 或者继承 Proxy 的对象)
用于拦截对象的读取属性操作。
-
getOwnPropertyDescriptor(目标对象, 返回属性名称的描述)
此方法是 Object.getOwnPropertyDescriptor() 的钩子函数。
-
getPrototypeOf(被代理的目标对象)
当读取代理对象的原型时,该方法就会被调用。
-
has(目标对象, 需要检查是否存在的属性)
in
操作符的代理方法。 -
isExtensible(目标对象)
用于拦截对对象的 Object.isExtensible()。
-
ownKeys(目标对象)
用于拦截 Reflect.ownKeys()。
-
preventExtensions()所要拦截的目标对象
方法用于设置对 Object.preventExtensions()的拦截。
-
set(目标对象, 将被设置的属性名或 Symbol)
设置属性值操作的捕获器。
-
setPrototypeOf(被拦截目标对象, 对象新原型或为 null)
主要用来拦截 Object.setPrototypeOf()
Proxy 对象代码例子
通过上述的方法描述,我们结合如下几个方法来理解方法的使用。
1、基础例子
const handler = {
get: (obj, prop) => {
return prop in obj ? obj[prop] : 37;
},
};
const _prosyObj = new Proxy({}, handler);
_prosyObj.a = 1;
_prosyObj.b = 2;
console.log(_prosyObj.a);
console.log(_prosyObj.b);
console.log(_prosyObj.c);
2、检测赋值
const validator = {
set: (obj, pro, value) => {
if (pro === "age") {
if (!Number.isInteger(value)) {
throw new TypeError("The age is not an integer");
}
if (value > 200) {
throw new RangeError("The age seems invalid");
}
}
obj[pro] = value;
return true;
},
};
const _prosyObj = new Proxy({}, validator);
_prosyObj.age = 123;
_prosyObj.age = 300; // RangeError: The age seems invalid
_prosyObj.age = "jhkdjs"; // TypeError: The age is not an integer
3、扩展构造函数
function extend(sup, base) {
let descriptor = Object.getOwnPropertyDescriptor(
base.prototype,
"constructor"
);
base.prototype = Object.create(sup.prototype);
let handler = {
construct: function (target, args) {
var obj = Object.create(base.prototype);
this.apply(target, obj, args);
return obj;
},
apply: function (target, that, args) {
sup.apply(that, args);
base.apply(that, args);
},
};
let proxy = new Proxy(base, handler);
descriptor.value = proxy;
Object.defineProperty(base.prototype, "constructor", descriptor);
return proxy;
}
const Person = function (name) {
this.name = name;
};
const Boy = extend(Person, function (name, age) {
this.age = age;
});
Boy.prototype.sex = "M";
const Peter = new Boy("Peter", 13);
console.log(Peter.sex); // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age); // 13
4、修改及附加属性
const _targetObj = {
books: [
"C++ Primer",
"Javascript权威指南",
"JavaScript高级程序设计",
"编码:隐匿在计算机软硬件背后的语言",
],
};
const handler = {
get: (target, prop) => {
// 灵活扩展一个属性,通过此属性获取最后一个元素
if (prop === "latestBrowser") {
return target.books[target.books.length - 1];
}
return target[prop];
},
set: (target, prop, value) => {
// 灵活扩展一个属性,把设置的值添加对代理对象中
if (prop === "latestBrowser") {
target.books.push(value);
return;
}
// 如果不是数组,则进行转换
if (typeof value === "string") {
value = [value];
}
// 默认行为是保存属性值
target[prop] = value;
// 表示成功
return true;
},
};
const _proxyObj = new Proxy(_targetObj, handler);
console.log(_proxyObj.books); // ['C++ Primer','Javascript权威指南','JavaScript高级程序设计','编码:隐匿在计算机软硬件背后的语言']
_proxyObj.books = "重构改善既有代码的设计";
console.log(_proxyObj.books); // [ '重构改善既有代码的设计' ]
_proxyObj.latestBrowser = "代码整洁之道";
console.log(_proxyObj.books); // [ '重构改善既有代码的设计', '代码整洁之道' ]
console.log(_proxyObj.latestBrowser); // 代码整洁之道
5、通过属性查找数组中的特定对象
const _books = [
{ id: 1, name: "疯狂设计模式", price: 90, autor: "Tom" },
{ id: 2, name: "怎么编写Bug", price: 100, autor: "Mary" },
{ id: 3, name: "Bug修改技术", price: 19, autor: "Bean" },
{ id: 4, name: "Bug修改技术", price: 29, autor: "Jack" },
];
const handler = {
get: (obj, prop) => {
// 判断是否为数组下标或者数组相关属性
if (prop in obj) {
return obj[prop];
}
let result,
names = {};
// 重新整理出带有name属性的数据
for (let product of obj) {
if (product.name === prop) {
result = product;
}
if (names[product.name]) {
names[product.name].push(product);
} else {
names[product.name] = [product];
}
}
// 通过 name 获取 product
if (result) {
return result;
}
// 通过 name 获取 products
if (prop in names) {
return names[prop];
}
// 获取 product name
if (prop === "name") {
return Object.keys(names);
}
return undefined;
},
};
const _proxyObj = new Proxy(_books, handler);
console.log(_proxyObj[0]); // { id: 1, name: '疯狂设计模式', price: 90, autor: 'Tom' }
console.log(_proxyObj["怎么编写Bug"]); // { id: 2, name: '怎么编写Bug', price: 100, autor: 'Mary' }
console.log(_proxyObj.name); // [ '疯狂设计模式', '怎么编写Bug', 'Bug修改技术' ]
console.log(_proxyObj.price); // undefined
console.log(_proxyObj.Bug修改技术); // { id: 4, name: 'Bug修改技术', price: 29, autor: 'Jack' }
console.log(_proxyObj.length); // 4
总结
通过上述的例子,可以看出 Proxy 对象的作用主要是拦截代理对象的某些属性和运行期间的方法,从而对原对象进行功能上的扩展。
在日常业务代码编写时,我们遇到不能修改某个对象或者要对某个数组进行扩展方法时,我们就可以采用 Proxy 对象来进行扩展,从而不会污染之前的业务对象和方便对数组进行方法扩展。