【问题标题】:Advanced Javascript: Partial cloning of an object graph高级 Javascript:对象图的部分克隆
【发布时间】:2015-02-08 15:37:09
【问题描述】:

在客户端,我使用了一个复杂的 Javascript 对象。但是当将它发送到服务器时,我只想发送它的一部分。

我想克隆它,但只克隆我需要的那些部分。我想要一个功能,就像

var wireFriendly = reduce(original, "id, name, related.id, related.name");
// or
var wireFriendly = reduce(original, ["id", "name", "related.id", "related.name"]);

到目前为止我做了什么

我已经创建了这个函数的大部分,当对象图只包含对象时,它可以满足我的要求。如果我里面有数组,它将无法工作。但这是我目前得到的:

function (entity, keepMembersList) {
    // check entity that it's not undefined, null and that it's an actual Object instance
    if (Object.prototype.toString.call(entity) !== "[object Object]")
    {
        throw "'entity' parameter should be a non-null object."
    }

    // code in switch statement only normalizes function arguments when
    // they are provided either as a string or as an array of string
    switch (Object.prototype.toString.call(keepMembersList))
    {
        case '[object String]':
            keepMembersList = keepMembersList.split(',').map(function (value) {
                return value.trim();
            });
        case '[object Array]':
            keepMembersList = keepMembersList.map(function (value) {
                return value.split(".");
            });
            break;
        default:
            throw "Parameter 'keepMembersList' should either be a comma delimited list of members' names to keep or an array of them.";
    }

    // from this point on, cloning is taking place
    var result = {};

    for (var i = 0; i < keepMembersList.length; i++)
    {
        for (var j = 0, r = result, e = entity; j < keepMembersList[i].length - 1; j++)
        {
            // ----------------------------------------------------------
            // this part should be changed to also detect array notations
            // ----------------------------------------------------------
            r = r[keepMembersList[i][j]] = r[keepMembersList[i][j]] || {};
            e = e[keepMembersList[i][j]];
        }
        r[keepMembersList[i][j]] = e[keepMembersList[i][j]];
    }

    return result;
}

我希望它也适用于这两个示例

// clone all array elements with specific members
var wireFriendly = reduce(original, "id, name, related[].id, related[].name");
// clone specific array element with specific members
var wireFriendly = reduce(original, ["id", "name", "related[0].id", "related[0].name"]);

但我至少可以接受第一个示例,其中数组的所有元素都被减少了成员。第二个仅克隆特定数组元素的示例对我的情况来说并不重要,但我想拥有它也会很棒。当然,在这种情况下,结果数组的索引不必匹配,否则我们会遇到未定义的元素:

//in case related[3].id would be specified we'd end up with
related:[undefined, undefined, undefined, { id: 1 }];

示例数据

将此对象用作需要部分克隆的原始实体:

var original = {
    "related":[{
        "related":[],
        "id":1759807323,
        "name":"Related name",
        "interest":{
            "id":1314962015,
            "name":"Ideas",
            "isLocked":false,
            "isPrivileged":false
        },
        "details":"Lengthy related details that will not be sent to server"
    }],
    "name":"My name",
    "interest":{
        "id":1314962015,
        "name":"Ideas",
        "isLocked":false,
        "isPrivileged":false
    },
    "details":"Some lengthy details"
}

您可以在this JSFiddle 中使用此函数测试一个工作示例。

【问题讨论】:

  • 你能覆盖toJSON 并返回你需要的吗?你在实现你想要的东西方面还有什么问题?
  • 附带说明,我认为使用 typeof 命令而不是 toString 方法更可靠。有人可以覆盖 toString。但他们不能覆盖 typeof。
  • 你能举一个你的original var 的例子吗?
  • @Eyal:这不是... Null、数组、日期都作为对象返回。因此,我使用toString 来确定某些东西正是所需的类型。
  • @helpermethod: _.pick 仅适用于第一级对象成员。它不适用于深度(er)对象图。我也不确定它如何处理数组成员...

标签: javascript cloning


【解决方案1】:

解决方案

我正在提供我自己的答案以供将来参考。最终的解决方案是一个两步过程:

  1. 将多级对象层次结构扁平化为一个级别,同时将属性过滤到所需的集合
  2. 将单级对象展开回多级实例

请注意,这会创建原始对象的简化浅表副本,因为我不会进一步操作它意外更改原始对象。如果您需要克隆,您可以通过检测日期、正则表达式和其他实际需要创建新实例的其他函数来为 flattenunflatten 函数添加额外的功能。

测试对象(问题上方)在展平后看起来像这样:

var flattened = {
    "name": "My name",
    "details": "Some lengthy details",
    "interest.id": 1314962015,
    "interest.name": "Ideas",
    "interest.isLocked": false,
    "interest.isPrivileged": false,
    "related[0].related": [],
    "related[0].id": 1759807323,
    "related[0].name": "Related name",
    "related[0].interest.id": 1314962015,
    "related[0].interest.name": "Ideas",
    "related[0].interest.isLocked": false,
    "related[0].interest.isPrivileged": false,
    "related[0].details": "Lengthy related details that will not be sent to server"
};

属性被重命名以表示多级对象。

扁平化函数

我尝试了几种不同的版本,从使用堆栈的迭代到递归。这个表现最好。

// "keep" is a HashMap-like object detailed later
function flatten(obj, keep) {

    // normalize parameters
    keep = keep || { contains: function () { return true; } };

    var result = {};

    var traverse = function (current, prefix) {
        switch (Object.prototype.toString.call(current))
        {
            case "[object Object]":
                for (var prop in current)
                {
                    traverse(current[prop], (prefix.length ? prefix + "." : "") + prop);
                }
                // when there were no properties it's an empty object instance
                if (!prop && keep.contains(prefix))
                {
                    result[prefix] = {};
                }
                break;
            case "[object Array]":
                // arrays
                for (var i = 0, l = current.length; i < l; i++)
                {
                    traverse(current[i], (prefix.length ? prefix : "") + "[" + i + "]");
                }
                // when there were no elements it's an empty array instance'
                if (l === 0 && keep.contains(prefix))
                {
                    result[prefix] = [];
                }
                break;
            case "[object Null]":
            case "[object Undefined]":
            case "[object Function]":
                // don't use nulls, undefineds or functions
                break;
            default:
                // primitive values: string, number, boolean, date, regexp
                if (keep.contains(prefix))
                {
                    result[prefix] = current;
                }
        }
    };

    traverse(obj, "");
    return result;
};

展开函数

最快的展开是使用indexOfsubstring 函数而不是split 并遍历生成的数组。

// "obj" is a flattened object instance
var unflatten = function unflatten(obj) {
    var result = {},
        current,
        prop,
        currIdx,
        normalized;

    for (var props in obj)
    {
        normalized = props.replace(/\[(\d+)\]/gi, ".$1");
        current = result;
        currIdx = -2;
        while (currIdx !== -1)
        {
            prop = normalized.substring(
                ++currIdx,
                (currIdx = normalized.indexOf(".", currIdx)) !== -1 ? currIdx : undefined
            );
            if (currIdx > 0)
            {
                current = current[prop] || (current[prop] = isNaN(parseInt(normalized.substring(currIdx + 1))) ? {} : []);
            }
        }
        current[prop] = obj[props];
    }

    return result;
};

类HashMap类型

正如您在 flatten 函数中看到的那样,keep 参数的类型为 HashMap,它具有 contains 函数。当您不提供任何 keep 参数时,flatten 将保留所有属性,因此默认 keep 实例位于函数开头。

var hashMapRx = /\[\d*\]/gi;

// Fast dictionary-like searching
var HashMap = function constructor(stringArray) {
    this.hash = {};

    for (var i = 0, l = stringArray.length; i < l; i++)
    {
        this.hash[this.normalize(stringArray[i])] = true;
    }
};

HashMap.prototype.contains = function contains(value) {
    var val = this.normalize(value);
    return !!this.hash[val] || this.any(val);
};

HashMap.prototype.normalize = function normalize(value) {
    return value.replace(hashMapRx, "[]");
};

HashMap.prototype.any = function any(value) {
    var result = false;
    for (var key in this.hash)
    {
        if (value === key || value.substr(0, key.length) === key)
            return true;
    }
    return false;
};

当实例化HashMap并以

的形式提供一个字符串数组时
["id", "name", "addresses[0].street", "addresses[].country" ...]

这些名称已标准化。我特意在这里提供了一个带有数组索引 (0) 的属性,而另一个没有。它们都被标准化为没有索引的版本。因此,当发生展平时,所有数组项都根据其属性定义包含在内。

还原变得像调用一样简单:

var reducedObj = unflatten(flatten(obj, keep));

【讨论】:

    猜你喜欢
    • 2013-01-03
    • 1970-01-01
    • 2015-02-08
    • 2011-07-18
    • 2010-10-22
    • 2012-05-06
    • 2017-05-22
    • 1970-01-01
    • 2014-07-08
    相关资源
    最近更新 更多