【问题标题】:Delete item from object by selector string通过选择器字符串从对象中删除项目
【发布时间】:2018-08-21 18:45:04
【问题描述】:

我试图通过将键传递给方法来从对象中删除项目。例如,我想删除a1,为此我将a.a1 传递给该方法。然后它应该从对象中删除a1,只留下对象的其余部分。

这是对象的结构:

this.record = {
  id: '',
  expiration: 0,
  data: {
    a: {
      a1: 'Cat'
    }
  }
}

然后我调用这个方法:

delete(key) {
  let path = key.split('.')
  let data = path.reduce((obj, key) => typeof obj == 'object' ? obj[key] : null, this.record.data)
  if(data) delete data
}

像这样:

let inst = new MyClass()
inst.delete('a.a1')

然而,这给了我以下错误:

delete data;
       ^^^^

SyntaxError: 在严格模式下删除不合格的标识符。

我认为data 在这一点上仍然是一个引用,或者不是?

也许reduce 不是在这里使用的正确方法。如何从对象中删除项目?

【问题讨论】:

  • 这可能不是一个有效的答案,但您可以展平对象,删除键,然后将其展平。有几个图书馆可以为您完成繁重的工作。这是一个:github.com/hughsk/flat

标签: javascript


【解决方案1】:

使用您的示例,data 在检查其真实性时的值是Cat,即您尝试删除的属性的。此时,data 只是一个引用字符串的常规变量,它不再位于 inst 的上下文中。

这是我设法使用您的 OP 中的解决方案作为基础的解决方案:

let path = key.split('.')
let owningObject = path.slice(0, path.length - 1)
    .reduce((obj, key) => typeof obj == 'object' ? obj[key] : null, this.record.data)

if (owningObject) delete owningObject[path[path.length - 1]]

这与您所拥有的主要区别在于 reduce 对路径段的 slice 进行操作,其中不包括最终标识符:这最终以 owningObject 为引用a 对象。 reduce 实际上只是沿着路径导航直到倒数第二段,它本身用作被删除的属性名称。

对于无效路径,它退出要么是因为 if (owningObject),要么是因为在未知属性上使用 delete 无论如何都是无操作的。

【讨论】:

  • 我花了很尴尬的时间才意识到owningObject 是如何计算的。你会说这在无效路径上是如何工作的?
  • 刚刚在a1.abc.efg 上测试过,它不会抛出错误,也不会看起来像胭脂和删除东西。
  • 我还更改了if(owningObject !== undefined),而不是设置null,而是设置undefined,因为如果owningObject 的值是falsenull,它不会删除
【解决方案2】:

我提出的解决方案是我不太喜欢但有效的解决方案,即循环遍历允许我像这样执行长键的项目

  • a.a1
  • a.a1.a1-1
  • a.a1.a1-1.sub

函数如下所示

let record = {
  data: {
    a: {
      a1: 'Cat',
      a2: {
        val: 'Dog'
      }
    }
  }
}

function remove(key) {
  let path = key.split('.')
  let obj = record.data
  for (let i = 0; i < path.length; i++) {
    if (i + 1 == path.length && obj && obj[path[i]]) delete obj[path[i]]
    else if(obj && obj[path[i]]) obj = obj[path[i]]
    else obj = null
  }
}

// Removes `a.a1`
remove('a.a1')
console.log(JSON.stringify(record))

// Removes `a.a2.val`
remove('a.a2.val')
console.log(JSON.stringify(record))

// Removes nothing since the path is invalid
remove('a.a2.val.asdf.fsdf')
console.log(JSON.stringify(record))

【讨论】:

  • 您不喜欢该解决方案的哪些方面?
  • 我刚刚尝试了我发布的 reduce 示例,其中包含与您在这里相同的测试用例,它给出了相同的结果。是什么让您从 reduce 方法切换到 for 循环?
【解决方案3】:

您可以使用 [] 引用删除键。

var foo = {
  a: 1,
  b: 2
};
var selector = "a";
delete foo[selector];
console.log(foo);

我不确定这是否对您有帮助,但它可能会对搜索此问题的人有所帮助。

【讨论】:

    【解决方案4】:

    这是另一种方法,它与 OP 自己的解决方案非常相似,但使用 Array.prototype.forEach 迭代路径部分。为了尽可能优雅地总结,我独立得出了这个结果。

    function TestRecord(id, data) {
        let record = {
            id : id,
            data : data
        };
    
        function removeDataProperty(key) {
            let parent = record.data;
            let parts = key.split('.');
            let l = parts.length - 1;
            parts.forEach((p, i) => {
                if (i < l && parent[p]) parent = parent[p];
                else if (i == l && parent[p]) delete parent[p];
                else throw new Error('invalid key');
            });
        }
    
      return {
        record : record,
        remove : function(key) {
            try {
                removeDataProperty(key);
            } catch (e) {
                console.warn(`key ${key} not found`);
            }
        }
      }
    }
    
    let test = new TestRecord('TESTA', {
      a : { a1 : '1', a2 : '2' },
      b : { c : { d : '3' } }
    });
    
    test.remove('a'); // root level properties are supported
    test.remove('b.c.d'); // deep nested properties are supported
    test.remove('a.b.x'); // early exit loop and warn that property not found
    
    console.log(test.record.data);

    在此示例中使用throw 是为了在路径的任何部分无效时尽早跳出循环,因为forEach 不支持break 语句。

    顺便说一句,有证据表明forEach 比简单的for 循环慢,但如果数据集足够小或者可读性与效率的权衡对于您的用例来说是可以接受的,那么这可能是一个不错的选择。

    https://hackernoon.com/javascript-performance-test-for-vs-for-each-vs-map-reduce-filter-find-32c1113f19d7

    【讨论】:

      【解决方案5】:

      这可能不是最优雅的解决方案,但您可以使用eval() 快​​速轻松地达到预期结果。

      function TestRecord(id) {
        let record = {
          id : id,
          data : {
            a : {
              a1 : 'z',
              a2 : 'y'
            }
          }
        };
      
        return {
          record : record,
          remove : function (key) {
            if (!key.match(/^(?!.*\.$)(?:[a-z][a-z\d]*\.?)+$/i)) {
              console.warn('invalid path');
              return;      
            } else {
              let cmd = 'delete this.record.data.' + key;
              eval(cmd);      
            }
          }    
        };
      }
      
      let t = new TestRecord('TESTA');
      t.remove('a.a1');
      console.log(t.record.data);
      

      我包含了一个regular expression from another answer,它根据命名空间格式验证用户输入,以防止滥用/误用。

      顺便说一句,我还使用了方法名称remove 而不是delete,因为delete 是reserved keyword in javascript

      此外,在反对评估的反对票开始涌入之前。来自:https://humanwhocodes.com/blog/2013/06/25/eval-isnt-evil-just-misunderstood/

      ...当你遇到 eval() 的情况时,你不应该害怕使用它 说得通。尝试先不要使用它,但不要让任何人吓到你 当 eval() 被认为是你的代码更脆弱或更不安全 使用得当。

      我并不是将 eval 推广为操作对象的最佳方式(显然,具有良好接口的定义明确的对象将是正确的解决方案),而是针对通过传递从对象中删除嵌套键的特定用例命名空间字符串作为输入,我不认为任何数量的循环或解析会更有效或更简洁。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-04-17
        • 2019-11-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多