JS:在一行代码中初始化并推送到 multi-dimensional 数组?创建树结构

JS: Initialize & push to multi-dimensional array in a single line of code? Create a tree structure

解决方案

感谢 Dave 的优雅解决方案,下面的答案是解决方案。旁注:首先,戴夫在下面提供的额外见解或作业对新手非常有价值。帮助我们伸展。

此代码将遍历现有的 JSON 树,例如您出于任何原因想要解析每个值。它不建造,而是行走。在我的例子中,我正在将每条评论解析为更丰富的 class:

var db = [], instance = {}, commentCounter = [];

function hydrateComments(items, parent) {
     _.forEach(items, function(item) {
        _.has(item, 'descendants') ? hydrateComments(item.descendants, item) : 0;

        instance = new CommentModel(_.omit(item,['descendants']));
         // other parsers go here, example the counter for each level
         // parseCounter(comment);

        (parent['children'] = (parent['children'] || [])) && item.depth > 0 ?
            parent.children.push(instance) :
            parent.push(instance);
    });
}

hydrateComments(comments, storeComments);

ANGULAR 指令

对于那些使用此代码构建树的人,我包含了一个指令,可以帮助您使用上述树构建树。

请注意我已经删除了很多我自己的代码并且没有对此进行测试,但我知道我花了很多时间试图找到树和模板所以希望这对你有帮助。

buildTree.$inject = [];
function buildTree() {
        link.$inject = ["scope", "elem", "attrs"];
        function link(scope, elem, attrs) {

        }

        CommentController.$inject = ["$scope"];
        function CommentController($scope) {

          $scope.$watchCollection(function () {
                return CommentDataService.getComments();
            },
            function (newComments, oldValue) {

                if (newComments) {
                    $scope.comments.model = newComments;
                }
            }, true);

        }

        return {
            restrict: "A",
            scope: {
               parent: "=cmoParent"
            },
            template: [
                "<div>",
                "<script type='text/ng-template'",
                    "id=" + '"' + "{[{ parent.app_id }]}" + '"' + " >",

                    "<div class='comment-post-column--content'>",
                        "<div cmo-comment-post",
                            "post-article=parent",
                            "post-comment=comment>",
                        "</div>",
                    "</div>",

                    "<ul ng-if='comment.children'>",
                        "<li class='comment-post-column--content'",
                            "ng-include=",
                            "'" + '"' + "{[{ parent.app_id }]}" + '"' + "'",
                            "ng-repeat='comment in comment.children",
                            "track by comment.app_id'>",
                        "</li>",
                    "</ul>",
                "</script>",


                "<ul class='conversation__timeline'>",
                    "<li class='conversation__post-container'",
                        "ng-include=",
                        "'" + '"' + "{[{ parent.app_id }]}" + '"' + "'",
                        "ng-repeat='comment in comments.model[parent.app_id]",
                        "track by comment.app_id'>",
                    "</li>",                
                    "<ul>",
                "</div>"

            ].join(' '),
            controller: CommentController,
            link: link
        }
    }

奖金

我还发现了一个绝招。如何使用一行代码初始化和填充数组。在我的例子中,我有一个计数器方法,可以计算我使用提示的每个级别的每个评论:

parseCounter: function(comment) {
    var depth = comment.depth;
    (commentCounter[depth] = (commentCounter[depth] || [])) ? commentCounter[depth]++ : 0;
},

原始问题

下面的代码解析 object 的 multi-level 数组,目的是将所有 object 解析为 “CommentModel” 的实例,虽然在这个例子中很简单object class 更丰富,但为了简洁起见,我简化了 object/class.

现有堆栈交换内容:

关于设置 multi-dimensional 数组的内容很多,几乎都显示了示例,例如:

var item[‘level1’][‘level2’] = ‘value’;  

var item = [];
var item['level1'] = [];

var item = new Array([]) // and or objects

但是,没有这样的例子:

var item[‘level1’].push(object)

问题:

  1. 有没有办法在一行代码中初始化一个 2 级深度的 multi-dimensional 数组,同时推送给它?

    1.1 即在我下面的 parent[‘children’] 示例中,我不得不检查它是否存在,如果没有设置它。如果我尝试 parent[‘children’].push(instance) 我显然会收到未定义异常的推送。是否有单行或更好的方法来检查 属性 是否存在?我显然不能在每次迭代时都在 parent 上设置一个空数组,即 parent[‘children’] = [];和 parent[‘children’] = 值不起作用

  2. 是否可以将初始化和验证移动到 CommentModel instance? 我试着问 CommentModel.prototype['children'] = [];但随后所有 child ('descendants') object 被添加到原型 属性 中的每个 object 中 “children”,有道理。

  3. 附带问题 - 我认为我的树迭代代码 function hydrateComments(items, parent) 很简洁,但是我可以做些什么来进一步简化 lodash and/or angular?我见过的大多数例子往往很冗长,并没有真正走到树枝上。

插件和代码

https://plnkr.co/edit/iXnezOplN4hNez14r5Tt?p=preview

    var comments = [
        {
            id: 1,
            depth: 0,
            subject: 'Subject one'
        },
        {
            id: 2,
            depth: 0,
            subject: 'Subject two',
            descendants: [
                {
                    id: 3,
                    depth: 1,
                    subject: 'Subject two dot one'
                },
                {
                    id: 4,
                    depth: 1,
                    subject: 'Subject two dot two'
                }
            ]
        },
        {
            id: 5,
            depth: 0,
            subject: 'Subject three',
            descendants: [
                {
                    id: 6,
                    depth: 1,
                    subject: 'Subject three dot one'
                },
                {
                    id: 7,
                    depth: 1,
                    subject: 'Subject three dot two',
                    descendants: [
                        {
                            id: 8,
                            depth: 2,
                            subject: 'Subject three dot two dot one'
                        },
                        {
                            id: 9,
                            depth: 2,
                            subject: 'Subject three dot two dot two'
                        }
                    ]
                }
            ]
        }
    ];

    function hydrateComments(items, parent) {
        _.forEach(items, function (item) {
            // create instance of CommentModel form comment. Simply example
            var instance = new CommentModel(item);

            // if we have descendants then injec the descendants array along with the
            // current comment object as we will use the instance as the "relative parent"
            if (_.has(instance, 'descendants')) {
                hydrateComments(instance.descendants, instance);
            }

            // we check is parent has a property of children, if not, we set it
            // NOTE : 3 lines of code ? is there a more concise approach
            if (!_.has(parent, 'children')) {
                parent['children'] = [];
            }

            // if depth id greater than 0, we push all instances of CommentModel of that depth to the
            // parent object property 'children'. If depth is 0, we push to root of array
            if (item.depth > 0) {
                parent.children.push(instance);
            } else {
                parent.push(instance);
            }
        })
    }

    // simple example, but lets assume much richer class / object
    function CommentModel(comment) {
        this.id = comment.id;
        this.depth = comment.depth;
        this.subject = comment.subject;
        this.descendants = comment.descendants;
    }

    var output = [];
    // init - pass in data and the root array i.e. output
    hydrateComments(comments, output);

    // Tada - a hydrated multi-level array
    console.log('Iteration output for comments  : ', output)

要在单个语句中初始化数组,您可以按如下方式进行

Method 1: (To initialise parent['children']) ANS to Q#1

#1 的傻瓜:https://plnkr.co/edit/lmkq8mUWaVrclUY2CoMt?p=preview

function hydrateComments(items, parent) {
  _.forEach(items, function(item) {
    // create instance of CommentModel form comment. Simply example 
    var instance = new CommentModel(item);

    // if we have descendants then injec the descendants array along with the 
    // current comment object as we will use the instance as the "relative parent"
    _.has(instance, 'descendants') ? hydrateComments(instance.descendants, instance) : 0;

    //Less eff. and less readable then method #2
    (parent['children'] = (parent['children'] || [])) && item.depth > 0 ?
      parent.children.push(instance) :
      parent.push(instance);


  });
}

Method 2: (To initialise parent['children']) ANS to Q#2 -- I'd prefer this.

#2 的傻瓜:https://plnkr.co/edit/zBsF5o9JMb6ETHKOv8eE?p=preview

function CommentModel(comment) {
  this.id = comment.id;
  this.depth = comment.depth;
  this.subject = comment.subject;
  this.descendants = comment.descendants;
  //Initialise children in constructer itself! :)
  this.children = [];
}

function hydrateComments(items, parent) {
  _.forEach(items, function(item) {
    // create instance of CommentModel form comment. Simply example 
    var instance = new CommentModel(item);

    // if we have descendants then injec the descendants array along with the 
    // current comment object as we will use the instance as the "relative parent"
    _.has(instance, 'descendants') ? hydrateComments(instance.descendants, instance) : 0;

    item.depth > 0 ? parent.children.push(instance) : parent.push(instance);

  });
}

ANS to Q#3

我觉得你的代码没问题。但是如果深度增加太多,你可能会遇到 Whosebug。使用 trampolines 消除递归问题。但是如果你确定深度不是

我想引用上面文章中的几行:

What this graph doesn’t show is that after 30,000 recursive invocations the browser hung; to the point it had to be forcibly shut down. Meanwhile the trampoline continued bouncing through hundreds of thousands of invocations. There are no practical limits to the number of bounces a trampoline can make.

但只有用trampoline才知道深度够深,会导致栈溢出

希望对您有所帮助!