原型链仅负责隐式属性查找,无法劫持;proxy 才能拦截访问行为。二者结合时,在 proxy 的 get 陷阱中手动遍历原型链,实现可控的属性来源识别、日志记录与错误抛出。

原型链本身不直接参与属性劫持,它负责的是属性查找路径;而 Proxy 是真正执行拦截动作的机制。两者结合的关键在于:用 Proxy 拦截对目标对象的访问,同时在 get 陷阱中主动沿原型链查找属性,从而把“是否命中”和“从哪一层取值”的逻辑显式控制起来——这比默认的隐式原型查找更可控、可扩展。
为什么不能只靠原型链实现劫持
原型链只是 JavaScript 的属性访问规则:读取 obj.x 时,引擎自动从 obj 开始,逐级向上查到 Object.prototype,直到找到或返回 undefined。这个过程是隐式的、不可中断的,也无法插入日志、校验或错误抛出。所以单靠原型链做不到“劫持”,必须引入 Proxy 来捕获访问行为。
Proxy + 原型链的典型协作方式
在 Proxy 的 get 陷阱中,不直接返回 target[prop],而是手动模拟原型链查找,并决定何时拦截、何时放行:
- 调用
Object.getPrototypeOf(target)获取当前对象的原型 - 用
Object.prototype.hasOwnProperty.call(obj, prop)判断属性是否存在于当前层级(自有属性) - 若不存在,递归检查原型,直到原型为
null - 若整条链都未找到,可抛出
ReferenceError,而不是返回undefined - 若找到,可记录来源层级、触发响应式通知、或包装返回值
一个实用示例:带原型链感知的严格读取代理
下面函数 strictWrap(obj) 返回一个 Proxy,在读取任意属性时,会明确告知该属性来自哪一层:
function strictWrap(target) {
return new Proxy(target, {
get(target, prop, receiver) {
// 先查自有属性
if (Object.prototype.hasOwnProperty.call(target, prop)) {
console.log(`✅ 自有属性: ${prop}`);
return target[prop];
}
// 再沿原型链查找
let proto = Object.getPrototypeOf(target);
let depth = 1;
while (proto !== null) {
if (Object.prototype.hasOwnProperty.call(proto, prop)) {
console.log(`? 继承自第 ${depth} 级原型: ${prop}`);
return proto[prop];
}
proto = Object.getPrototypeOf(proto);
depth++;
}
// 全链未找到
throw new ReferenceError(`Property "${prop}" not found in object or its prototype chain`);
}
});
}
const parent = { shared: 'from parent' };
const child = Object.create(parent);
child.own = 'from child';
const proxy = strictWrap(child);
console.log(proxy.own); // ✅ 自有属性: own → "from child"
console.log(proxy.shared); // ? 继承自第 1 级原型: shared → "from parent"
console.log(proxy.missing); // ReferenceError
注意事项与边界情况
实际使用中需留意几个关键点:
-
receiver参数很重要:当访问的是 getter 属性时,receiver是调用上下文(通常是代理本身),应传给Reflect.get以保证 this 正确 - 避免无限递归:不要在
get中再次访问proxy.xxx,否则会反复触发陷阱 - 冻结对象影响:若
target被Object.freeze(),其自有属性仍可被 Proxy 拦截,但无法新增属性(set陷阱中需配合Reflect.set并检查返回值) - 数组和内置对象:原型链结构不同(如
Array.prototype),手动遍历时要兼容Array.isArray或类型判断











