You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript递归处理对象属性描述符时TypeError问题排查

递归获取对象属性描述符时遇到Object.assign的undefined问题

我正在编写一个递归函数,用于实现递归获取JavaScript对象的Object.getOwnPropertyDescriptors并扁平化的功能。

以下是我的代码:

let master = [];
function something (obj,str) {
    
    if (Object(obj)===obj) {
        pobj = Object.getOwnPropertyDescriptors(obj)
        for (let kk in pobj) {
            maststr = str;
        //console.log(obj);
        //console.log("line 8");
        //console.log(kk);
        if (master.includes(kk)||kk==="constructor"||kk==="globalThis"||kk==="global") { //circular reference // ||typeof obj[kk]==="function"
            return;
        } else {
            maststr = str + "." + kk ;
            console.log("line - 1st");
            console.log(maststr);
            console.log(kk);
            console.log(pobj[kk]);
            pobj[kk]["name"] = maststr;
            master.push(pobj[kk]);
            console.log("line - 2nd");
            console.log(maststr);
            if (maststr==="globalThis.Function.prototype.arguments"||maststr==="globalThis.Function.prototype.caller"
                ||maststr==="globalThis.Function.prototype.callee") continue;
            something(obj[kk], maststr);
        }
        
        }
    } else {
        //master.push(obj);
        return;
    }


    }


something(globalThis,"globalThis");
console.log(master);

代码运行一切正常,直到处理到Object.assign属性时,该属性意外变为undefined,并抛出如下错误:

/Users/user/Documents/playground/playgroundtests_js/E16_2.js:19
            pobj[kk]["name"] = maststr;
                             ^

TypeError: Cannot set properties of undefined (setting 'name')
    at something (/Users/user/Documents/playground/playgroundtests_js/E16_2.js:19:21)
    at something (/Users/user/Documents/playground/playgroundtests_js/E16_2.js:25:4)
    at Object.<anonymous> (/Users/user/Documents/playground/playgroundtests_js/E16_2.js:38:1)
    at Module._compile (node:internal/modules/cjs/loader:1191:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1245:10)
    at Module.load (node:internal/modules/cjs/loader:1069:32)
    at Function.Module._load (node:internal/modules/cjs/loader:904:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:22:47

在抛出错误前打印该属性,结果确实是undefined,但在Node REPL中测试Object.getOwnPropertyDescriptors(Object.assign)却能正常返回对象。

请问出现此问题的原因是什么,该如何解决?


问题原因

核心问题有三个:

  1. 全局变量被递归覆盖:你代码里的pobj、maststr没有用let/const声明,属于全局变量。递归调用时,新的赋值会直接覆盖外层循环的变量,导致外层循环里的pobj[kk]变成undefined。
  2. 循环引用判断逻辑完全错误:master数组存的是属性描述符对象,但你用master.includes(kk)判断(kk是字符串属性名),永远不会匹配,根本无法阻止循环引用,还会误判跳过正常属性。
  3. 错误使用return中断循环:遇到constructor等属性时直接return,会中断整个遍历循环,导致后续属性都无法处理。

解决方法

1. 修复全局变量问题

给pobj、maststr加上let/const声明,变成函数内局部变量,避免递归时被覆盖。

2. 重构循环引用判断

用Set存储已处理的对象本身(不是属性描述符),这样才能准确判断循环引用,而且Set.has()的效率远高于数组includes()。

3. 替换return为continue

遇到不需要处理的属性时,用continue跳过当前项即可,不要中断整个循环。

4. 增加防御性判断

在操作属性描述符前,先判断是否为undefined,避免报错。

完整修复后的代码

let master = [];
// 用Set存储已处理的对象,防止循环引用
let processedObjects = new Set();

function something(obj, str) {
    // 非对象或已处理过,直接返回
    if (Object(obj) !== obj || processedObjects.has(obj)) {
        return;
    }
    processedObjects.add(obj);

    const pobj = Object.getOwnPropertyDescriptors(obj);
    for (const kk in pobj) {
        // 跳过不需要处理的属性
        if (kk === "constructor" || kk === "globalThis" || kk === "global") {
            continue;
        }

        const maststr = str + "." + kk;
        const descriptor = pobj[kk];
        
        // 防御性判断,避免undefined
        if (!descriptor) {
            console.warn(`无属性描述符:${maststr}`);
            continue;
        }

        // 给描述符添加路径名称
        descriptor.name = maststr;
        master.push(descriptor);

        // 跳过特殊属性,不递归
        if (maststr === "globalThis.Function.prototype.arguments" || 
            maststr === "globalThis.Function.prototype.caller" ||
            maststr === "globalThis.Function.prototype.callee") {
            continue;
        }

        // 只递归处理数据属性的value(访问器属性无value,无需处理)
        if ("value" in descriptor) {
            something(descriptor.value, maststr);
        }
    }
}

something(globalThis, "globalThis");
console.log(master);

额外说明

  • 区分数据属性和访问器属性:访问器属性只有get/set,没有value,不需要递归处理其值。
  • 使用Set跟踪已处理对象,能高效避免循环引用导致的死递归。

内容的提问来源于stack exchange,提问作者forgodsakehold

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 22:48:12