在使用JavaScript Proxy包装第三方库函数时,开发者可能会在开发环境运行正常,但在生产构建中遭遇TypeError,尤其涉及Reflect.get的receiver参数。本文深入探讨了Proxy#get和Reflect.get中receiver参数的正确用法,解释了为何不当使用会导致生产环境报错,并提供了正确的代码实践,以确保Proxy行为在所有环境中一致且健壮。
在JavaScript中,Proxy对象允许你拦截并自定义对目标对象的各种操作。其中,get陷阱(trap)用于拦截属性读取操作。当一个属性被读取时,Proxy的get方法会被调用,其签名如下:
get(target, property, receiver)
在get陷阱内部,我们常常会使用Reflect.get来获取目标对象的原始属性值。Reflect.get的签名如下:
Reflect.get(target, propertyKey, receiver)
当尝试使用Proxy来修改MUI的styled函数时,原始代码示例如下:
import * as muiSystem from '@mui/system'; type CreateMUIStyled = typeof muiSystem.styled; type MUIStyledParams = Parameters; const system = new Proxy(muiSystem, { get(target, prop, receiver) { if (prop === 'styled') { // 问题所在:将Proxy的receiver直接传递给了Reflect.get const styled = Reflect.get(target, prop, receiver); return function (...args: Parameters ) { const newOptions = getComposedOptions(args[1]); const newArgs: MUIStyledParams = [...args]; newArgs.splice(1, 1, newOptions); const styledResult = styled(...newArgs); return function ( ...newestArgs: Parameters > ) { return styledResult(...newestArgs); }; }; } // 原始代码此处返回undefined,这会导致访问其他属性时失败 return Reflect.get(target, prop, receiver); // 应该处理其他属性的访问 }, }); export const styled = system.styled;
这段代码在开发环境中可以正常运行,但在生产环境中却抛出了一个TypeError:
Uncaught TypeError: 'get' on proxy: property 'styled' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '(o,s={})=youjiankuohaophpcn{n6e这个错误信息非常关键,它指出Proxy的get陷阱未能返回目标对象muiSystem上styled属性的“实际值”。这通常发生在以下情况:
在原始代码中,Reflect.get(target, prop, receiver)这一行是问题的核心。这里的receiver是Proxy实例本身。当Reflect.get尝试从target(即muiSystem)获取styled属性时,如果styled是一个需要特定this上下文(例如muiSystem本身)的getter或方法,而你传入了Proxy实例作为receiver,那么styled内部的逻辑可能会因为this上下文不匹配而失败。对于一些只读且不可配置的属性,JavaScript引擎会更严格地检查Proxy的get陷阱是否返回了与目标属性完全相同的值。如果Reflect.get由于receiver不正确而未能正确地“激活”或获取到styled的真实行为,就会触发此错误。
为了确保Reflect.get能够正确地获取到目标属性的值,尤其当该属性是getter或方法时,其receiver参数应该指向目标对象本身,或者一个能够正确模拟目标对象this上下文的对象。
正确的做法是将target(即muiSystem)作为Reflect.get的第三个参数:
Reflect.get(target, propertyKey, target)
这样可以保证当styled属性被访问时,如果它是一个需要this上下文的函数,其this将正确地绑定到muiSystem对象上,从而避免TypeError。
基于上述分析,对Proxy的get陷阱进行修改,确保Reflect.get使用正确的receiver:
import * as muiSystem from '@mui/system'; type CreateMUIStyled = typeof muiSystem.styled; type MUIStyledParams = Parameters; const system = new Proxy(muiSystem, { get(target, prop, receiver) { if (prop === 'styled') { // 修正:将target作为Reflect.get的receiver参数 const styled = Reflect.get(target, prop, target); // 确保 styled 是一个函数,以防万一 if (typeof styled !== 'function') { console.warn(`muiSystem.styled is not a function. Prop: ${String(prop)}`); return styled; // 返回原始值 } return function (...args: Parameters ) { // 假设 getComposedOptions 存在且逻辑正确 const newOptions = getComposedOptions(args[1]); const newArgs: MUIStyledParams = [...args]; newArgs.splice(1, 1, newOptions); const styledResult = styled(...newArgs); return function ( ...newestArgs: Parameters > ) { return styledResult(...newestArgs); }; }; } // 对于其他属性,也应该使用Reflect.get并传递target作为receiver return Reflect.get(target, prop, target); }, }); export const styled = system.styled; // 假设 getComposedOptions 函数的定义 function getComposedOptions(options: any) { // 实现你的自定义逻辑 return { ...options, someCustomProperty: true }; }
代码解释:
p === 'styled')之外返回undefined,这意味着任何其他对muiSystem属性的访问都将失败。正确的做法是也通过Reflect.get来获取它们,并同样传递target作为receiver,以保持一致性。这种开发环境正常、生产环境报错的现象在前端开发中并不少见,通常有以下几个原因: