惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
D
Docker
GbyAI
GbyAI
宝玉的分享
宝玉的分享
Jina AI
Jina AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Vercel News
Vercel News
博客园_首页
Recent Announcements
Recent Announcements
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
S
SegmentFault 最新的问题
Microsoft Security Blog
Microsoft Security Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
美团技术团队
V
V2EX
WordPress大学
WordPress大学
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
Visual Studio Blog
IT之家
IT之家
Apple Machine Learning Research
Apple Machine Learning Research
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

博客园 - 彭成刚

"editor.cursorSurroundingLines": 3, 光标周围行数 设置光标在第几行的时候滚动页面滚动条 Prettier - Code formatter BUG 读不到 根目录的 .prettierrc.js prettier.configPath 格式化失效 vue2 mounted后面括号应该没有空格 _.flow 管道函数 lodash 同步函数列表很哇塞啊 _.flow(caozuo1, caozuo2, caozuo3)(my_data) vue2 iview 组件 ref 起名规则 应该用文件名 <shouye_book_left ref="shouye_book_left" /> iview 日期组件 BUG 赋值变量选年度 然后当前年会有个蓝色底, 解决方案 :key="ccValue" ⭐️⭐️⭐️ 自定义组件 表单验证 v-model 自动更新验证 trigger: 'change' 不触发,解决方案 加 <Input v-model="formData.abc" /> vue2 iview ComfyUiApi comfyui_xy 安装-使用 mergeLeft 左合并json函数 alt+k vscode 执行 ccdev - 在editor 和 terminal 之间切换 splitTwo 分割2个元素,主要分割文件名和id等 pi.dev 试试这个cli - deepseek-flash-v4 C盘清理空间软件 c盘没空间了 Wise Disk Cleaner kceve 鼠标2口控制器 kvm 鼠标同步器 穿屏幕 快捷键 是 s + * 操作列css每个按钮a后面加竖线.css operation_class .prettierrc.js 我发现 这个格式化的配置 默认就不建议放全局,都是跟项目走的 grid-gap 在 chrome58 支持,然后chrome58 不支持 gird的gap,不支持flex的gap iview table columns.js 表头 带* 带星 renderHeader alt+F9 设置 open in terminal 打开当前文件terminal vscode idea 都设置成这个快捷键 配合 ccdev使用 ccdev go开发 cli开发 读取本地模版复制到剪切板 where.exe node 查看命令位置 powershell 注意不是 where node (新建了一个ccwhere.bat) vue2 vModel 组件绑定数据写法 ccValue reloadenv 函数 `code $PROFILE` vscode在不关闭下刷新环境变量 volta 安装记录 node多版本控制 && @native-sdk OmniVoice 记录 我觉得音色还原还不错 voiceclone-env 记录 vscode pwsh 全选快捷键 ctrl+a (powershell) ai专属使用 aitree 命令 ai配合重要命令 powershell vscode code $PROFILE 备份,配合conda使用vscode $setHideObjProp 设置js json对象属性 隐藏变量 不可枚举,post的时候不带这个变量 GPT-SoVITS 环境搭建及应用
[ai问答] js对象 某个属性找到父节点 __parent__ 一键注入函...
彭成刚 · 2026-06-11 · via 博客园 - 彭成刚

我主要想实现 js 对象 某一个属性 寻找它的父节点

想要实现“寻找一个属性的父节点”,我们需要先厘清 JavaScript 中两种不同的“父子”关系。

在 JS 中,有两种非常容易混淆的“父级”概念:

  1. 原型链上的父级(Prototype):即你刚才提到的 protoAa 的父节点。
  2. 嵌套结构中的父级(Nested Object):比如 const a = { b: { c: 1 } },此时对象 b 是属性 c 的物理父节点。

针对这两种截然不同的场景,实现“寻找父节点”的方法如下:


场景一:寻找原型链上的“父节点”(属性是在哪一代祖先上找到的)

如果你想知道一个属性到底是从哪一层原型链上继承过来的,我们可以写一个辅助函数,顺着 Object.getPrototypeOf() 一层一层往上爬,直到找到拥有该属性的那个原型对象。

function findPrototypeParent(obj, propName) {
  let current = obj;
  
  while (current !== null) {
    // 检查当前这一层对象自身是否含有这个属性
    if (Object.prototype.hasOwnProperty.call(current, propName)) {
      return current; // 找到了!返回这个父节点原型
    }
    // 没找到,继续往上一层原型爬
    current = Object.getPrototypeOf(current);
  }
  
  return null; // 整个原型链都没找到
}

// === 测试你的设想 ===
const protoA = { customProp: "我是定制属性" };
const a = Object.create(protoA); // a 继承 protoA

// 1. 查找 a 自身的属性
console.log(findPrototypeParent(a, 'customProp') === protoA); // true (成功找到父节点 protoA)

// 2. 查找原生方法
console.log(findPrototypeParent(a, 'hasOwnProperty') === Object.prototype); // true (成功找到老祖宗)


场景二:寻找嵌套对象中的“父节点”(JSON 树状结构)

如果你问的是:有一个深层嵌套的对象(像树状数据、JSON),怎么知道某个属性的上一级对象是谁?

const company = {
  name: "科技公司",
  department: {
    name: "研发部",
    leader: {
      name: "张三",
      age: 35
    }
  }
};

⚠️ 核心痛点:在 JavaScript 中,底层的引用是单向的。leader 对象只知道自己有 name: "张三",它绝对不知道自己的上级叫 department

如果你想在不知道完整路径的情况下,在整个大对象里肉眼搜索某个属性并拿到它的父节点,就需要用到递归深度搜索(DFS)

function findNestedParent(root, targetKey) {
  // 如果当前不是对象,直接返回 null
  if (typeof root !== 'object' || root === null) return null;

  // 如果当前对象直接拥有这个属性,那么当前对象就是父节点
  if (Object.prototype.hasOwnProperty.call(root, targetKey)) {
    return root;
  }

  // 否则,递归遍历当前对象的所有子属性
  for (let key in root) {
    if (Object.prototype.hasOwnProperty.call(root, key)) {
      const result = findNestedParent(root[key], targetKey);
      if (result) return result; // 如果在子树里找到了,层层返回
    }
  }

  return null; // 没找到
}

// === 测试嵌套查找 ===
const parent = findNestedParent(company, "age");
console.log(parent); // 输出: { name: '张三', age: 35 } (成功拿到了 age 的直接父节点)


💡 工程实践小建议

如果你的项目频繁需要寻找嵌套对象的“父节点”,最优雅的做法通常是在创建/初始化数据的时候,手动给子节点挂载一个隐藏的父级引用:

// 手动建立双向绑定
company.department.leader.__parent__ = company.department;
company.department.__parent__ = company;

// 这样你随时可以通过属性直接找爸爸了:
console.log(company.department.leader.__parent__); // 直接拿到 department

你目前面对的数据结构更偏向于哪一种呢?是原型链的探究,还是在处理类似树状(Tree)的业务数据(比如多级菜单、组织架构)?

company.department.leader.parent = company.department;

这个是我要的 但是我需要 parent 不是显示的 是隐藏的,比如我要将这个leader对象 作为 params 给到一个post api 就不显示 parent

太赞了,你的直觉非常敏锐,而且直接切中了前端开发在处理树状数据时最经典的一个痛点:循环引用与数据污染

如果你直接用 leader.__parent__ = company.department,当你把 leader 传给 JSON.stringify() 或 POST API 时,不仅会把 __parent__ 传给后端,甚至还会因为 department 里面又有 leaderleader 里面又有 __parent__,从而导致浏览器直接抛出爆栈错误:TypeError: Converting circular structure to JSON(无法序列化循环结构)。

要做到“平时能用属性找爸爸,但发请求、打印时又完全隐形”,JavaScript 提供了两种非常完美的“隐形斗篷”技术。


方案一:使用 Object.defineProperty(经典、推荐)

这是 JS 最正统的做法。在 JS 中,对象的属性其实有一个隐藏的开关叫 enumerable(可枚举性)。如果把这个开关关掉,这个属性就会变成“隐形”的:

  • JSON.stringify() 读不到它。
  • for...in 循环、Object.keys() 遍历不到它。
  • console.log 打印时它会变灰或者不显示。
  • 能正常通过 obj.__parent__ 点出来。
const company = {
  department: {
    name: "研发部",
    leader: { name: "张三", age: 35 }
  }
};

// 核心:用 defineProperty 来绑定“隐形”的父节点
Object.defineProperty(company.department.leader, '__parent__', {
  value: company.department, // 指向父节点
  enumerable: false,         // ❌ 不可枚举(隐形的关键!)
  writable: true,
  configurable: true
});

// === 测试效果 ===

// 1. 你可以正常用它找父节点:
console.log(company.department.leader.__parent__.name); // 输出: "研发部"

// 2. 模拟 POST API 发送数据(序列化):
const apiParams = JSON.stringify(company.department.leader);
console.log(apiParams); 
// 输出: {"name":"张三","age":35}  <-- 干净漂亮!__parent__ 自动消失了!


方案二:使用 Symbol 作为属性名(现代、优雅)

在 ES6 中,引入了 Symbol 类型。用 Symbol 作为对象的键名,天然就具备极强的隐蔽性。它同样不会被 JSON.stringify() 和普通的 for...in 遍历到。

// 1. 定义一个全局唯一的“隐形标记”
const ParentSymbol = Symbol('parent');

const company = {
  department: {
    name: "研发部",
    leader: { name: "张三", age: 35 }
  }
};

// 2. 挂载父节点
company.department.leader[ParentSymbol] = company.department;

// === 测试效果 ===

// 1. 通过 Symbol 找父节点:
console.log(company.department.leader[ParentSymbol].name); // 输出: "研发部"

// 2. 模拟 POST API 发送数据:
console.log(JSON.stringify(company.department.leader));
// 输出: {"name":"张三","age":35}  <-- Symbol 属性自动被忽略了!


🛠️ 终极自动化:写一个“全自动隐形绑定”工具函数

在实际业务中,手动一个个去写 Object.defineProperty 太麻烦了。既然你想要树状结构,我们可以写一个通用的初始化函数,只要把你的原始数据扔进去,它就会自动递归把所有子节点都绑上隐形的 __parent__

function buildTreeRelations(obj, parent = null) {
  if (typeof obj !== 'object' || obj === null) return;

  // 如果有父节点,绑定隐形的 __parent__
  if (parent) {
    Object.defineProperty(obj, '__parent__', {
      value: parent,
      enumerable: false, // 隐形
      configurable: true,
      writable: true
    });
  }

  // 递归遍历子属性
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      buildTreeRelations(obj[key], obj);
    }
  }
}

// === 业务使用 ===
const myData = {
  name: "集团",
  sub: {
    name: "子公司",
    dept: { name: "财务部" }
  }
};

// 一键注入隐形父节点
buildTreeRelations(myData);

// 现在任何一层都可以直接找爸爸了:
const target = myData.sub.dept;
console.log(target.__parent__.name);           // "子公司"
console.log(target.__parent__.__parent__.name); // "集团"

// 发送给后端,依然是绝对纯净的数据:
// axios.post('/api', target) -> 实际发送的就是 { name: "财务部" }

这两种方案里,方案一(Object.defineProperty)是最符合你直觉的,因为你依然可以用小写字符串 .__parent__ 去访问它,代码可读性最高。

你觉得写成 buildTreeRelations 这种自动化函数,能完美适配你现在的业务场景吗?