如何手写根据路径 get 和 set 对象属性?
简化版
根据路径 get/set 对象属性,就是支持用 a.b[0].c 这样的字符串读取或设置深层值。实现思路是先把路径解析成 key 数组,再逐层访问。get 遇到 null/undefined 返回默认值;set 遇到不存在的中间层就创建对象或数组。边界包括数组下标、空路径、默认值、原型污染风险。
详细版
关键步骤是路径标准化。
| 输入路径 | 解析结果 |
|---|---|
a.b.c | ["a", "b", "c"] |
a[0].b | ["a", "0", "b"] |
user.name | ["user", "name"] |
function parsePath(path: string) {
return path.replace(/\[(\w+)\]/g, '.$1').split('.').filter(Boolean)
}
function get(obj: any, path: string, defaultValue?: unknown) {
const keys = parsePath(path)
let cur = obj
for (const key of keys) {
if (cur == null) return defaultValue
cur = cur[key]
}
return cur === undefined ? defaultValue : cur
}
这类工具函数不要忘了安全边界,
__proto__、constructor、prototype不能随便 set。
完整版教学
一、题目考察点
这题考对象访问、字符串解析、边界处理和安全意识。很多表单库、配置系统和校验库都会用类似能力。
例如:
get(user, 'profile.address.city')
set(form, 'items[0].name', 'Book')
二、路径解析
最常见做法是把中括号写法转换成点写法。
function parsePath(path: string) {
return path
.replace(/\[(\w+)\]/g, '.$1')
.split('.')
.filter(Boolean)
}
这能覆盖大多数面试场景,但完整 lodash 级别路径解析会更复杂,例如引号、转义字符和特殊 key。
三、get 实现
get 逐层访问即可。关键是遇到 null 或 undefined 要停止,避免报错。
function getValue(obj: any, path: string, defaultValue?: unknown) {
let cur = obj
for (const key of parsePath(path)) {
if (cur == null) return defaultValue
cur = cur[key]
}
return cur === undefined ? defaultValue : cur
}
注意 null 是否返回默认值,要看约定。常见实现只在最终结果为 undefined 时返回默认值。
四、set 实现
set 要创建中间对象。
function setValue(obj: any, path: string, value: unknown) {
const keys = parsePath(path)
let cur = obj
keys.forEach((key, index) => {
if (index === keys.length - 1) {
cur[key] = value
return
}
const nextKey = keys[index + 1]
if (cur[key] == null) cur[key] = /^\d+$/.test(nextKey) ? [] : {}
cur = cur[key]
})
return obj
}
如果下一个 key 是数字,可以创建数组,否则创建对象。
五、原型污染风险
如果允许用户传任意 path,set(obj, '__proto__.polluted', true) 可能污染对象原型。
因此 set 时要拦截危险 key:
const dangerous = new Set(['__proto__', 'prototype', 'constructor'])
命中危险 key 应直接拒绝。
六、工程扩展
成熟工具还会支持:
- 路径数组:
['a', 'b', 0, 'c']。 - 带引号路径:
a["b.c"]。 - 不修改原对象的 immutable set。
- TypeScript 类型推导。
| 版本 | 特点 |
|---|---|
| 面试基础版 | 点路径 + 中括号数字 |
| 工程增强版 | 安全拦截 + immutable |
| 库级实现 | 完整路径语法 + 类型 |
七、常见误区与追问
- 误区:直接 split 点号就够。
a[0].b、空路径和特殊 key 都要考虑。 - 误区:set 可以无条件创建任意 key。 用户输入路径时必须防原型污染。
- 误区:get 遇到 falsy 就返回默认值。
0、false、空字符串都是有效值,不能误判。 - 追问:如何支持数组路径? 如果 path 本身是数组,就直接遍历,不需要解析字符串。
- 追问:如何 immutable set? 沿路径复制每一层对象或数组,最后返回新对象。
- 追问:为什么
constructor危险? 它可能通向原型链,造成原型污染风险。
八、加强记忆
路径 get/set 记成“先解析,再逐层走”。get 要安全短路,set 要补中间层,还要拦截原型污染。不要把 falsy 当成不存在。