defineProperty 陷阱函数要求在操作后返回一个布尔值用于判断操作是否成功,如果返回了 false 则抛出错误,故可以使用该功能来限制哪些属性可以被Object.defineProperty() 方法定义。
例如,如果想阻止定义Symbol符号类型的属性,你可以检查传入的属性值,若是则返回 false:
/* 定义代理 */ let proxy = new Proxy({}, { defineProperty(trapTarget, key, descriptor) { if (typeof key === "symbol") { return false; } return Reflect.defineProperty(trapTarget, key, descriptor); } }); Object.defineProperty(proxy, "name", { value: "proxy" }); console.log(proxy.name); // "proxy" let nameSymbol = Symbol("name"); // 抛出错误 Object.defineProperty(proxy, nameSymbol, { value: "proxy" })
四、函数代理
4.1 构造函数 & 立即执行
函数的两个内部方法:[[Call]] 与[[Construct]]会在函数被调用时调用,通过代理函数来为这两个内部方法设置陷阱,从而控制函数的行为。
[[Construct]]会在函数被使用new运算符调用时执行,代理触发construct()陷阱函数,并和Reflect.construct()一样接收到下列两个参数:
trapTarget :被执行的函数( 即代理的目标对象) ;
argumentsList :被传递给函数的参数数组。
[[Call]]会在函数被直接调用时执行,代理触发apply()陷阱函数,它和Reflect.apply()都接收三个参数:
trapTarget :被执行的函数( 代理的目标函数) ;
thisArg :调用过程中函数内部的 this 值;
argumentsList :被传递给函数的参数数组。
每个函数都包含call()和apply()方法,用于重置函数运行的作用域即 this 指向,区别只是接收参数的方式不同:call()的参数需要逐个列举、apply()是参数数组。
显然,apply 与 construct 要求代理目标对象必须是一个函数,这两个代理陷阱在函数的执行方式上开启了很多的可能性,结合使用就可以完全控制任意的代理目标函数的行为。
4.2 验证函数的参数
看到apply()和construct()陷阱的参数都有被传递给函数的参数数组argumentsList,所以可以用来验证函数的参数。
例如需要保证所有参数都是某个特定类型的,并且不能通过 new 构造使用,示例如下:
/* 定义 sum 目标函数 */ function sum(...values) { return values.reduce((previous, current) => previous + current, 0); } /* 定义 apply 陷阱函数 */ function applyRef (trapTarget, thisArg, argumentList) { argumentList.forEach((arg) => { if (typeof arg !== "number") { throw new TypeError("All arguments must be numbers."); } }); return Reflect.apply(trapTarget, thisArg, argumentList); } /* 定义 construct 陷阱函数 */ function constructRef () { throw new TypeError("This function can't be called with new."); } /* 定义 sumProxy 代理函数 */ let sumProxy = new Proxy(sum, { apply: applyRef, construct: constructRef }); console.log(sumProxy(1, 2, 3, 4)); // 10 // console.log(sumProxy(1, "2", 3, 4)); // TypeError: All arguments must be numbers. // let result = new sumProxy() // TypeError: This function can't be called with new.
sum() 函数会将所有传递进来的参数值相加,此代码通过将 sum() 函数封装在 sumProxy() 代理中,如果传入参数的值不是数值类型,该函数仍然会尝试加法操作,但在函数运行之前拦截了函数调用,触发apply陷阱函数以保证每个参数都是数值。
出于安全的考虑,这段代码使用 construct 陷阱抛出错误,以确保该函数不会被使用 new 运算符调用
实例对象 instance 对象会被同时判定为 proxy 与 target 对象的实例,是因为 instanceof 运算符使用了原型链来进行推断,而原型链查找并没有受到这个代理的影响,因此 proxy 对象与 target 对象对于 JS 引擎来说就有同一个原型。
4.3 调用类的构造函数