
Object.preventExtensions() 或 Reflect.preventExtensions() 锁定。不过,它们在设计定位、参数容错性以及与 Proxy 的协作方式上,存在明显差异。
参数非对象时的处理方式不同
这是两者最直观的区别。当传入的参数不是一个标准对象(例如 42 或 "hello")时:
Reflect.isExtensible会直接抛出TypeError,态度非常明确——"我需要一个对象,你传入基本类型是无法处理的"。Object.isExtensible则会相对"温和"一些,它会先对参数进行强制类型转换,比如将字符串包装成String实例,然后再进行判断。结果通常也是false(包装对象默认不可扩展),但整个过程不会触发错误。
换言之,一个严格把关,一个隐式妥协。在某些边界场景下,这一差异会直接影响代码行为——如果你希望尽早发现参数类型错误,Reflect 直接抛出错误的方式反而更加友好。
调用风格与函数式编程一致性
从调用形式来看,Reflect.isExtensible(obj) 采用标准的函数式调用风格,参数清晰直观,可以像积木一样灵活组合到高阶工具函数中。Object.isExtensible(obj) 虽然也是静态方法,但 JavaScript 中的设计风格并不统一:例如对象判断使用 instanceof 操作符,属性检查使用 in 运算符——这些都不是函数。Reflect 将所有元操作统一为函数式风格,使代码更接近"纯函数",也便于在函数式编程库或管道中直接使用。
举个实际场景:如果你编写了一个通用验证器,需要判断任意输入是否为可扩展对象,Reflect.isExtensible 可以直接作为回调函数传入,而使用 Object.isExtensible 则需要额外包裹一层匿名函数。虽然代码量相差不大,但长期维护下来,统一的函数式接口能显著降低认知负担。
与 Proxy 的协同设计更自然
这一点才是两者最本质的区别。Proxy 的每个拦截钩子(如 isExtensible、set、get)都对应一个 Reflect 方法,且默认行为完全对齐。当你在编写 Proxy handler 时这样写:
isExtensible(target) { return Reflect.isExtensible(target); }
这是标准的"透传"写法,意图一目了然——既不改变默认行为,又保留了未来在中间插入日志、权限校验等扩展点的能力。如果你换成:
isExtensible(target) { return Object.isExtensible(target); }
虽然结果一致,但这种写法在语义上不够"名正言顺"。因为 Object 上的方法是历史遗留的设计,而 Reflect 是专门为 Proxy 量身打造的。从设计契约的角度来看,Reflect 才是 Proxy 的"官方搭档"。使用 Object 方法也能运行,但总归有些牵强。更关键的是,当给 Reflect.isExtensible 传入非对象参数时,它会直接抛出错误,这正好与 Proxy( target, handler ) 中 target 必须是对象的规则保持一致。而 Object.isExtensible 的隐式转换反而可能掩盖某些类型错误。
返回值逻辑完全一致
这一点很简单,两者都返回布尔值:
true表示对象目前仍然可以随时添加新属性,尚未被preventExtensions冻结。false表示对象已被锁定,不能再动态增加自有属性。此时如果尝试赋值(obj.x = 1),非严格模式下会静默失败,严格模式下则直接报错。
因此,在返回值层面你完全不用担心谁对谁错。不过需要注意,Object.isExtensible 在传入非对象参数时虽然不会报错,但实际返回的布尔值是基于转换后的包装对象判定的,通常也是 false。如果你依赖这一结果进行流程判断,最好确认入参类型为对象,否则可能得到意料之外的 false,而 Reflect.isExtensible 直接抛出错误反而能帮助你提前发现潜在问题。
