前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库Proxy get 陷阱 链式调用应用
JavaScript对象与原型

Proxy 的 get 陷阱能做什么?怎么用它实现链式调用?

get 陷阱会拦下代理对象上的每一次属性读取,读到的值由它的返回值决定。做链式调用的关键是:读属性时返回一个函数,函数执行完再把代理本身返回,这样就能一直点下去。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#对象与原型
先看核心答案读代码示例
理解线索

`get` 陷阱的三个参数

  1. target被代理的原始对象,直接读写它不会再触发陷阱
  2. key属性名,可能是字符串,也可能是 Symbol
  3. receiver一般是代理本身,决定原对象上 getter 里的 this

get 连 then 和 toJSON 这类读取也会拦下来。对任何属性都返回函数的代理,被 await 或 JSON.stringify 时会出意外。

核心回答

先记住这个答案

Proxy 的 get 陷阱会在读取代理对象任意属性时被调用,签名是 get(target, key, receiver),它返回什么,读属性就得到什么。常见用途有给不存在的属性返回默认值、支持负数下标、记录访问日志,以及 Vue 3 的依赖收集。链式调用的做法是:读属性时返回一个函数,函数执行完返回代理本身,于是可以一直 .a().b().c() 点下去。还有一种是每读一层就返回一个新代理,把属性名攒起来,最后调用时一次性用上,常用来拼接口路径。

  • get 陷阱拦截属性读取
  • 返回代理本身,就能无限链式调用
  • 每读一层返回新代理,可以动态拼路径
  • 用 Reflect.get 正确转发读取

返回代理本身,实现链式调用

jQuery 那种 $(el).css(...).show() 的写法,靠的是每个方法最后 return this。用 Proxy 可以不提前写任何方法,读到什么属性就临时生成一个设置函数。

一个样式构建器JavaScript
const style = { color: '', size: 0 }

const builder = new Proxy(style, {
  get(target, key) {
    if (key === 'done') return () => ({ ...target })
    // 读到任何其它属性,都返回一个设置函数
    return (value) => {
      target[key] = value
      return builder // 返回代理本身,才能继续往下点
    }
  }
})

const result = builder.color('red').size(14).done()
console.log(JSON.stringify(result))
console.log(style.color)
查看输出与解释
{"color":"red","size":14}
red

读 builder.color 会触发 get,这时 key 是 'color',陷阱返回一个函数;调用它写入 target.color,再返回 builder。下一个 .size 又是对代理的一次读取,又进一次 get。整条链上每一步都回到代理,这就是链式调用能成立的原因。 注意函数里返回的是 builder 而不是 target。返回 target 的话,下一次 .size 读的是原始对象,拿到的是数字 0,调用就报错了。

每读一层返回新代理,动态拼路径

另一种链式用法不需要调用每一步,而是把读过的属性名攒起来。很多 RPC 客户端、接口封装就是这么做的,写 api.users.list() 就能请求 /users/list,不用提前定义任何方法。

用属性名拼接口地址JavaScript
function createPath(parts = []) {
  // 用函数当 target,代理才能既被点属性,又被直接调用
  return new Proxy(function () {}, {
    get(_, key) {
      return createPath([...parts, key]) // 每读一层,返回一个多一段路径的新代理
    },
    apply(_, __, args) {
      const query = args.length ? '?id=' + args[0] : ''
      return '/' + parts.join('/') + query
    }
  })
}

const api = createPath()
console.log(api.users.list())
console.log(api.users.detail(42))
console.log(api.orders.byUser.recent(7))
查看输出与解释
/users/list
/users/detail?id=42
/orders/byUser/recent?id=7

每次读属性都新建一个代理,并把属性名追加到 parts,互相之间不共享状态。最后一步是函数调用,触发的是 apply 陷阱而不是 get,所以 target 必须是函数,普通对象当 target 是没法被调用的。真实项目里,apply 里就是发请求的地方。

回答前,多想一步

容易答错的地方

转发时直接写 target[key]
如果原对象上有 getter,target[key] 会让 getter 里的 this 指向原始对象,绕过了代理,后面对 this 的读取就拦不到了。老实转发应该写 Reflect.get(target, key, receiver),其中的 receiver 保证 this 还是代理。
代理 Map 或 Set 后直接调方法
new Proxy(new Map(), {}) 之后调 .get('a') 会抛 TypeError。原因是 Map.prototype.get 要求 this 是真正的 Map,它依赖 Map 内部存储的数据,而这时 this 是代理。解决办法是在 get 里把取到的方法 bind(target) 后再返回,Vue 3 处理集合类型时也做了专门的包装。
对所有属性都返回函数
上面的构建器如果被 await,引擎会去读 then,拿到一个函数,就把它当成 Promise 去调用,结果完全不对。JSON.stringify 会读 toJSON,模板字符串会读 Symbol.toPrimitive,都会掉进同一个坑。实际写的时候要对这些 key 返回 undefined,或者只对白名单里的属性名生成函数。
试着用自己的话回答

面试官还会怎么问?

get 陷阱能拦截 in 和 Object.keys 吗?

不能。in 走的是 has 陷阱;Object.keys 走的是 ownKeys 和 getOwnPropertyDescriptor;删除属性走 deleteProperty 陷阱。get 只管读值。

Vue 3 的响应式在 get 里做了什么?

读取时先用 Reflect.get 拿到值,再记录「当前正在运行的副作用函数读了这个对象的这个属性」,也就是依赖收集。如果拿到的值还是对象,就在这时才把它包成响应式代理,所以深层对象是用到时才代理,不用初始化时全部递归。

代理对象作为别的对象的原型,get 还会触发吗?

会。子对象自身找不到属性时,查找沿原型链走到代理,就会触发代理的 get,这时 receiver 是子对象,不是代理。这也说明 receiver 表示的是最初被读取的那个对象。

从一道题,走向一组知识

把知识连起来

对象与原型

目标属性不可写又不可配置时,Proxy 的 set 陷阱能返回什么?

get 也有不变量,目标上不可写又不可配置的属性,get 必须原样返回它的值。

对象与原型

Object.freeze 冻住的对象,为什么里面的属性还能改?深度冻结怎么写?

冻结对象的属性全都不可写、不可配置,给它套 Proxy 时 get 就不能返回别的值。

参考资料

  • JavaScript:对象与原型参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 返回代理本身,实现链式调用
  3. 每读一层返回新代理,动态拼路径
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑