JavaScript 动态删除对象属性

7
我有以下代码来获取给定对象的密钥。所以对于对象 {"hello": {"data": {"a": "world", "b": "random"}}},如果传入的密钥是hello.data.a,则输出将是world
(object, key) => {
    const keyParts = key.split(".");
    let returnValue = object;
    keyParts.forEach((part) => {
        if (returnValue) {
            returnValue = returnValue[part];
        }
    });
    return returnValue;
}

我正在尝试找出如何以相同的方式动态地删除属性。例如,对于同一对象和键,它将使对象变异为{"hello": {"data": {"b": "random"}}}

基本上我想要与delete object.hello.data.a相同的行为。但问题是hello.data.a部分是通过动态字符串传递的。当然,像delete object[key]这样的东西是行不通的,因为键有嵌套级别。如果只有1个深度级别,它将起作用,但对于嵌套项目将不起作用。

另一个重要说明是,在这种情况下性能非常重要。因此,尽管创建一个新对象并复制所有属性(除了要删除的属性)可能可行,但我非常担心其对性能产生的影响,与delete关键字相比。

由于其他外部因素,我也不想将属性值设置为nullundefined。我希望将键实际从对象中删除。

我该如何实现这一点?

4个回答

6

要删除,可以使用 .pop() 去除最后一个键来确定需要移除的属性。如果你想避免使用 delete 关键字,则还需要将该键所在的最后一个对象替换掉,而不是更改它,因此还需要弹出下一个最后一个键。

然后,使用您当前的方法(或 .reduce)访问最后一个 外部对象。要删除其中的一个键:

  • 要使用 delete 进行更改,请使用保存的键,使用 delete[nextLastKey][lastKey]
  • 要创建一个全新的对象,但不包含保存的键,请使用剩余语法在创建新对象时排除该 lastKey,然后将新对象分配给父对象的 nextLastKey

const fn = (object, key) => {
    const keys = key.split(".");
    const lastKey = keys.pop();
    const nextLastKey = keys.pop();
    const nextLastObj = keys.reduce((a, key) => a[key], object);
    
    // delete version:
    // delete nextLastObj[nextLastKey][lastKey]
    
    // non-delete version:
    const { [lastKey]: _, ...rest } = nextLastObj[nextLastKey];
    nextLastObj[nextLastKey] = rest;
    return object;
}

const obj = {"hello": {"data": {"a": "world", "b": "random"}}};
const result = fn(obj, 'hello.data.a');
console.log(result);


这将适用于任意嵌套对象或键的深度,对吗? - Charlie Fish
是的,键可以任意深入。但是,它不能任意“浅” - 例如{ foo: 'foo' }无法在不使用delete的情况下删除foo属性。 - CertainPerformance
明白了!为此,我可能只需编写一个条件语句来检查key变量中是否存在“.”,然后通过执行delete object[key]来正常删除它。 - Charlie Fish
这个解决方案目前看起来很棒,并且通过了我编写的测试。需要进行一些定制才能使其与我的用例配合使用,但我认为它将完美地工作。如果可能的话,希望能够更详细地解释一下为什么会这样工作。看起来这是由于nextLastObj是一个引用而不是值的副本,删除它会起作用?如果您可以进一步阐述一下您的答案,那么我和其他人就可以更好地理解为什么会这样工作以及其中涉及的基本概念。非常感谢您的帮助! - Charlie Fish
您需要替换最后一个对象来删除一个键,而不使用 delete。仅使用最后一个键无法替换最后一个对象(出于同样的原因,{ foo: 'foo' } 加上 'foo' 也行不通),但是通过使用倒数第二个键,您可以在其父级上替换最后一个对象。通过保存倒数第二个键,您可以重新分配给父对象。 - CertainPerformance

1
实际上,你的答案就在你提供的代码片段中。它只需要稍加修改,就像这样:

const delProp = (object, key) => {
    const keyParts = key.split(".");
    let returnValue = object;
    
    let parent, lastKey;  // added
    keyParts.forEach((part) => {
        if (returnValue) {
            parent = returnValue; // added
            lastKey = part;  // added
            returnValue = parent[lastKey];
        }
    });
    
    if(parent) {
      delete parent[lastKey];  // added
    }
    
    return returnValue;
}


const obj = {"hello": {"data": {"a": "world", "b": "random"}}};

console.log(delProp(obj, 'hello.data.a'));
console.log(obj)

还要注意的是,在JS中,for循环在本质上比forEach数组函数更有效率。因此,我会像这样修改相关部分:
for (const i = 0, i = keyParts.length; i++) {
    // ...
}

0
你可以使用 lodash 库中的 _.omit 函数。 _omit(object, path)

https://lodash.com/docs/4.17.15#omit

要小心,因为此函数不会改变对象,而是返回一个新的对象,其中不包含已删除的键。


-2

您仍然可以使用括号语法进行删除。

我添加了另一个示例,因为一些反对者似乎认为这是不可能的。只要您知道基本对象,它就没有任何区别。我将把按“.”拆分作为一个简单的练习留在这里,因为它不是核心问题的一部分。

var thing = {"hello": {"data": {"a": "world", "b": "random"}}};
delete thing.hello.data["a"];
console.log(thing);


var thing2 = {"hello": {"data": {"a": "world", "b": "random"}}};
delete thing2["hello"]["data"]["a"];
console.log(thing2);


1
请记住,输入字符串为hello.data.a,它是动态的,随时可能会改变。 - Charlie Fish
2
我对这个答案进行了负投票,因为正如问题提问者所说,属性路径是动态的,因此标准方法delete thing.hello.data["a"];并不能解决问题。 - Dahou
1
@MarkSchultheiss 我不认为你的回答能够在这里有所帮助,即使问题包含了你发布的相同代码并且说它不起作用,你写道分割部分不是问题的核心,但实际上它是。基本上你把整个答案留给读者自己去练习! - Dahou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接