3

我最近看到有人问这个问题,其中 OP 想要找到对象属性的路径,所以我用伪代码回答了这个问题,说我没有足够的时间来实际编写解决方案。然而,这个问题对我来说太有趣了,以至于我最终还是试图写一个解决方案。到目前为止,这是我想出的:

function isEmpty(obj) {
    for (var prop in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, prop)) {
            return false;
        }
    }
    return true;
}

function Node(obj, parent, searchTarget) {
    this.parent = parent;
    this.obj = obj;
    this.searchTarget = searchTarget;

    this.searchNode = function() {
        if(this.obj == this.searchTarget) {

            //return this.reconstructPathRecursive();
        }

        if (!isEmpty(this.obj)) {
            var children = [];

            for (prop in this.obj) {
                if (this.obj.hasOwnProperty(prop)) {
                    children.push(new Node(this.obj[prop], this, searchTarget));
                }
            }

            var path;
            for(var i = 0, len = children.length; i < len; i++) {
                path = children[i].searchNode();
                if(path) return path;
            }
        }
    }

    this.reconstructPathRecursive = function() {
        var path = [this], curObj = this.parent;

        while (curObj != undefined) {
            path.push(curObj);
            curObj = curObj.parent;
            if(curObj == undefined) break;
        }

        return path;
    }

    this.findPath = function() {
        return this.searchNode();
    }
}

var myObj = {
    nullRoot: "gotcha!",
    path1: {
        myFunc: function() {
            alert("Success!");
        }
    }
}

function findFunctionPath(obj, func) {
    return new Node(obj, undefined, func).findPath();
}
var thisFunc = myObj.path1.myFunc;
    console.log("--");

console.log(findFunctionPath(myObj, thisFunc));

这个想法是我会调用this.searchNode()代表每个对象属性的 Node 对象。searchNode()将在每个结果属性节点上调用自身,并将当前对象作为parent每个子节点上的 传递。如果我找到要搜索的函数,我会调用reconstructPathRecursive(),这几乎就是这样做的,使用每个节点上的 parent 属性。

但是,我收到“超出最大调用堆栈大小”。运行此实时测试时出错。我认为这意味着我不小心以某种方式编写了一个无限循环。我的逻辑缺陷在哪里,无限循环又在哪里潜入?console.log表明它searchNode被一遍又一遍地调用,而我只在对象不为空时调用它,并且我没有在任何地方给对象引用自身(我不认为......),所以我真的有点难住了。

编辑:我稍微更新了代码以isEmpty从节点函数更改为全局函数,以便我可以this.objsearchNode()函数中调用它。以前,它只会在节点上调用它(它总是至少有两个属性,从而导致无限循环),而不是引用的对象。这已修复,但错误仍然存​​在。

另一个编辑:发现并更正了另一个错误(见 Satyajit 的回答)。不过,仍然会陷入无限循环。

4

2 回答 2

2

该属性nullRoot是一个字符串,而不是一个 javascript 对象。在其上运行您的isEmpty函数将永远不会返回 false 并将其投入无限循环。不过,您将“gotcha”作为其价值几乎是可以预见的。

于 2012-04-27T20:07:19.560 回答
1

这将失败

var arr = [];
arr[0] = arr;

因为它包含自己作为一个孩子。该Node函数最终创建了一个无界的节点列表,其中父节点与子节点相同。

要处理循环对象图,您需要跟踪您已经访问过的内容并避免重新访问它。这在 JavaScript 中很困难,因为您没有对象集。

您可以保留您访问过的对象的列表,并检查是否obj出现在其中,或者您可以尝试使用特殊的对象属性 ( breadcrumb ) 来判断您是否访问过某个对象。如果您没有正确清理或其他人使用该属性,或者其他代码使用 EcmaScript 5 冻结,则面包屑会失败。


编辑:

当您真正在孩子中找到路径时,您还应该跳出孩子循环。

path = children[i].searchNode();

应该

 path = children[i].searchNode();
 if (path) { return path; } 

编辑:

正如 Satyajit 指出的那样,"gotcha"是一个属性值。

从那时起"gotcha"[0] === "g""g"[0] === "g"您很快就会到达一个周期。

当我做

alert("gotcha".hasOwnProperty(0));
for (var k in "gotcha") { alert(k); }

在最近的 Chrome 中,我收到警报“true”、“0”、“1”、...、“5”。

这是现代浏览器的标准行为,如15.5.5.2节所述:

字符串对象使用 [[GetOwnProperty]] 内部方法的变体,用于其他原生 ECMAScript 对象 (8.12.1)。这种特殊的内部方法用于添加对与 String 对象的单个字符相对应的命名属性的访问。

由于该部分的第 9 条,这些属性是可枚举的。

于 2012-04-27T19:33:47.997 回答