【问题标题】:Update if exists or add new element to array of objects - elegant way in javascript + lodash如果存在则更新或向对象数组添加新元素 - javascript + lodash 中的优雅方式
【发布时间】:2014-11-04 01:27:55
【问题描述】:

所以我有一个这样的对象数组:

var arr = [
  {uid: 1, name: "bla", description: "cucu"},
  {uid: 2, name: "smth else", description: "cucarecu"},
]

uid 是此数组中对象的唯一 ID。如果我们有具有给定uid, 的对象或添加一个新元素,如果数组中不存在呈现的uid,我正在寻找修改对象的优雅方法。我想这个函数在 js 控制台中的行为是这样的:

> addOrReplace(arr, {uid: 1, name: 'changed name', description: "changed description"})
> arr
[
  {uid: 1, name: "bla", description: "cucu"},
  {uid: 2, name: "smth else", description: "cucarecu"},
]
> addOrReplace(arr, {uid: 3, name: 'new element name name', description: "cocoroco"})
> arr
[
  {uid: 1, name: "bla", description: "cucu"},
  {uid: 2, name: "smth else", description: "cucarecu"},
  {uid: 3, name: 'new element name name', description: "cocoroco"}
]

我目前的方式似乎不是很优雅和实用:

function addOrReplace (arr, object) {
  var index = _.findIndex(arr, {'uid' : object.uid});
  if (-1 === index) {
    arr.push(object);
  } else {
    arr[index] = object;
  }
} 

我正在使用 lodash,所以我正在考虑使用自定义相等检查修改 _.union 之类的东西。

【问题讨论】:

    标签: javascript arrays underscore.js lodash


    【解决方案1】:

    在您的第一种方法中,由于findIndex(),不需要 Lodash:

    function upsert(array, element) { // (1)
      const i = array.findIndex(_element => _element.id === element.id);
      if (i > -1) array[i] = element; // (2)
      else array.push(element);
    }
    

    例子:

    const array = [
      {id: 0, name: 'Apple', description: 'fruit'},
      {id: 1, name: 'Banana', description: 'fruit'},
      {id: 2, name: 'Tomato', description: 'vegetable'}
    ];
    
    upsert(array, {id: 2, name: 'Tomato', description: 'fruit'})
    console.log(array);
    /* =>
    [
      {id: 0, name: 'Apple', description: 'fruit'},
      {id: 1, name: 'Banana', description: 'fruit'},
      {id: 2, name: 'Tomato', description: 'fruit'}
    ]
    */
    
    upsert(array, {id: 3, name: 'Cucumber', description: 'vegetable'})
    console.log(array);
    /* =>
    [
      {id: 0, name: 'Apple', description: 'fruit'},
      {id: 1, name: 'Banana', description: 'fruit'},
      {id: 2, name: 'Tomato', description: 'fruit'},
      {id: 3, name: 'Cucumber', description: 'vegetable'}
    ]
    */
    

    (1) 其他可能的名称:addOrReplace()addOrUpdate()appendOrUpdate()insertOrUpdate()...

    (2) 也可以用array.splice(i, 1, element)完成

    请注意,这种方法是“可变的”(相对于“不可变的”):这意味着它不是返回一个新数组(不接触原始数组),而是直接修改原始数组。

    【讨论】:

    • 第一次失败
    【解决方案2】:

    您可以使用对象而不是数组

    var hash = {
      '1': {uid: 1, name: "bla", description: "cucu"},
      '2': {uid: 2, name: "smth else", description: "cucarecu"}
    };
    

    键是 uids。现在你的函数addOrReplace 很简单:

    function addOrReplace(hash, object) {
        hash[object.uid] = object;
    }
    

    更新

    除了数组之外,还可以将对象用作索引
    这样您就有了快速查找和一个工作数组:

    var arr = [],
        arrIndex = {};
    
    addOrReplace({uid: 1, name: "bla", description: "cucu"});
    addOrReplace({uid: 2, name: "smth else", description: "cucarecu"});
    addOrReplace({uid: 1, name: "bli", description: "cici"});
    
    function addOrReplace(object) {
        var index = arrIndex[object.uid];
        if(index === undefined) {
            index = arr.length;
            arrIndex[object.uid] = index;
        }
        arr[index] = object;
    }
    

    看看 jsfiddle-demo(你会发现一个面向对象的解决方案here

    【讨论】:

    • 是的,我也喜欢这种结构,但是我使用的一些库不支持它。比如Angular typeahead,只能遍历数组。
    • 您只能将此结构用作索引。这样你就可以快速查找,并且需要一个额外的数组来存储数组之类的东西。
    • 点赞!有 2 个问题,我需要按时间戳排序的键,并且我需要能够以数组方式访问所有项目以计算统计度量
    【解决方案3】:

    我个人不喜欢修改原始数组/对象的解决方案,所以这就是我所做的:

    function addOrReplaceBy(arr = [], predicate, getItem) {
      const index = _.findIndex(arr, predicate);
      return index === -1
        ? [...arr, getItem()]
        : [
          ...arr.slice(0, index),
          getItem(arr[index]),
          ...arr.slice(index + 1)
        ];
    }
    

    你会像这样使用它:

    var stuff = [
      { id: 1 },
      { id: 2 },
      { id: 3 },
      { id: 4 },
    ];
    
    var foo = { id: 2, foo: "bar" };
    stuff = addOrReplaceBy(
      stuff,
      { id: foo.id },
      (elem) => ({
        ...elem,
        ...foo
      })
    );
    

    我决定做的是让它更灵活:

    1. 通过使用lodash -> _.findIndex()谓词可以是多个东西
    2. 通过传递回调getItem(),您可以决定是完全替换项目还是进行一些修改,就像我在示例中所做的那样。

    注意:此解决方案包含一些 ES6 功能,例如解构、箭头函数等。


    还有第二种方法。我们可以使用 JavaScript Map 对象,它“保存键值对并记住键的原始插入顺序”加上“任何值(对象和原始值)都可以用作键或值。”

    let myMap = new Map(
      ['1', { id: '1', first: true }] // key-value entry
      ['2', { id: '2', second: true }]
    )
    
    myMap = new Map([
      ...myMap, 
      ['1', { id: '1', first: true, other: '...' }]
      ['3', { id: '3', third: true }]
    ])
    

    myMap 将按顺序包含以下条目:

    ['1', { id: '1', first: true, other: '...' }]
    ['2', { id: '2', second: true }]
    ['3', { id: '3', third: true }]
    

    我们可以利用 Maps 的这一特性来添加或替换其他元素:

    function addOrReplaceBy(array, value, key = "id") {
      return Array.from(
        new Map([
          ...array.map(item => [ item[key], item ]),
          [value[key], value]
        ]).values()
      )
    }
    

    【讨论】:

      【解决方案4】:

      也许

      _.mixin({
          mergeById: function mergeById(arr, obj, idProp) {
              var index = _.findIndex(arr, function (elem) {
                  // double check, since undefined === undefined
                  return typeof elem[idProp] !== "undefined" && elem[idProp] === obj[idProp];
              });
      
              if (index > -1) {
                  arr[index] = obj; 
              } else {
                  arr.push(obj);
              }
      
              return arr;
          }
      });
      

      var elem = {uid: 3, name: 'new element name name', description: "cocoroco"};
      
      _.mergeById(arr, elem, "uid");
      

      【讨论】:

        【解决方案5】:

        如果您最终不介意项目的顺序,那么更简洁的功能 es6 方法如下:

        function addOrReplace(arr, newObj){ 
         return [...arr.filter((obj) => obj.uid !== newObj.uid), {...newObj}];
        }
        
        // or shorter one line version 
        
        const addOrReplace = (arr, newObj) => [...arr.filter((o) => o.uid !== newObj.uid), {...newObj}];
        

        如果item存在则排除,最后添加新的item,基本上就是replace,如果没有找到item,最后添加新的对象。

        通过这种方式,您将拥有不可变的列表。 唯一要知道的是,如果您要在屏幕上呈现列表,则需要进行某种排序以保持列表顺序。

        希望这对某人有用。

        【讨论】:

          【解决方案6】:

          老帖子,为什么不用过滤功能呢?

          // If you find the index of an existing uid, save its index then delete it
          //      --- after the filter add the new object.
          function addOrReplace( argh, obj ) {
            var index = -1;
            argh.filter((el, pos) => {
              if( el.uid == obj.uid )
                delete argh[index = pos];
              return true;
            });
          
            // put in place, or append to list
            if( index == -1 ) 
              argh.push(obj);
            else 
              argh[index] = obj;
          }
          

          这是一个jsfiddle,展示了它的工作原理。

          【讨论】:

            【解决方案7】:

            如果数组的索引与uid 相同呢?例如:

            arr = [];
            arr[1] = {uid: 1, name: "bla", description: "cucu"};
            arr[2] = {uid: 2, name: "smth else", description: "cucarecu"};
            

            这样你就可以简单地使用

            arr[affectedId] = changedObject;
            

            【讨论】:

            • 如果我保证 uuid 始终为 int, 会很好用。我想有时情况并非如此。但即使我有这个保证,我也会对缺少这样的元素的数组感到不舒服:arr[2134], arr[2135],当 arr[0] 不存在时。
            • @ganqqwerty 所以经典数组使用关联一个(对象),这样你就可以使用非整数索引并且在你的数组中没有“洞”
            【解决方案8】:

            Backbone.Collection 正好提供了这个功能。尽量省力!

            var UidModel = Backbone.Model.extend({
                idAttribute: 'uid'
            });
            
            var data = new Backbone.Collection([
                {uid: 1, name: "bla", description: "cucu"},
                {uid: 2, name: "smth else", description: "cucarecu"}
            ], {
                model: UidModel
            });
            
            data.add({uid: 1, name: 'changed name', description: "changed description"}, {merge: true});
            
            data.add({uid: 3, name: 'new element name name', description: "cocoroco"});
            
            console.log(data.toJSON());
            

            【讨论】:

              【解决方案9】:

              非常复杂的解决方案:D 这是一个班轮:

              const newArray = array.filter(obj => obj.id !== newObj.id).concat(newObj)
              

              【讨论】:

              • 如果对象已经存在,则用给定的 id 替换它,而不是更新它。
              • 没错,但显然新对象已经有了更新的值。但是是的,如果你想保持数组中项目的顺序,他必须使用 findindex
              • 据我了解,新对象的属性需要与旧对象的属性合并。一些需要保留的属性可能不会出现在新对象中。
              猜你喜欢
              • 2020-10-25
              • 2021-06-10
              • 1970-01-01
              • 2013-09-23
              • 2020-11-29
              • 1970-01-01
              • 2019-09-15
              • 1970-01-01
              • 2014-12-03
              相关资源
              最近更新 更多