使用原生JS遍历对象并更改一个属性的值

61

我希望可以返回一组数据结果,并且只改变日期字段的格式,使其更易读,同时保留其他所有数据。我希望不使用第三方库实现。

目前我的进展如下:

//Get all the Tasks
function getAllTasks() {
  return models.gantt_tasks.findAll()
  .then(function(tasks){

    let results = tasks.map(task => task.start_date = task.start_date.format("YYYY-MM-DD"));
    return results;
  })
  .catch(function(e) {
    console.error(e);
    return e;
  });
}
我希望地图在返回数据之前能够完成。
7个回答

61
你可以使用 Object.assign(target, ...sources) (这里) 来仅更改一个值,同时保留其他值不变。
举个例子:
const object1 = {
 a: 1,
 b: 2,
 c: 3
};

现在假设您想要将b的值更改为22,您可以这样做:

const object2 = Object.assign({}, object1, {b: 22});

console.log(object1);  // { a: 1, b: 2, c: 3 } 
console.log(object2);  // { a: 1, b: 22, c: 3 } 

注意,这不会改变object1的值,而是在Object.assign()的第一个参数中定义一个新的空对象,并向该空对象添加更多参数,如果再次遇到同样的键,则更新该键的值。
通过这种方式,您可以更改一个或多个对象的值。 更好的方法 Airbnb的JavaScript编码风格指南() {}建议:
优先使用对象展开运算符而不是Object.assign来浅复制对象。
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this

// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }

// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }

您可以在此处查看完整的文档:这里

50

.map() 不是修改现有对象数组属性的常规方式。你可以这样做,但这不是 .map() 正常使用的方式。相反,它是一种创建新数组的方法。 .map() 回调返回一个值,这些返回的值被推入一个新数组中,最终由 .map() 返回。

你的 .map() 回调隐式地将格式化后的日期赋值给了 task.start_date 并返回它。这意味着你只创建了一个格式化日期的数组,而没有原始的 task 对象。如果你想在这里使用 .map(),它需要在对 task.start_date 进行赋值之后返回 task

但你可能希望在你的 .then() 回调中使用 .forEach()。这个方法就是为这个任务而设计的:它简单地迭代数组并允许你直接修改每个元素:

tasks.forEach( task => task.start_date = task.start_date.format("YYYY-MM-DD") );
return tasks;

同时,正如Dustin所提到的,从.then()返回一个值可能不会达到你想要的效果。最安全的方法是在那里对tasks进行操作,或调用另一个函数并将tasks作为参数传递进去。(不过我对Promise有点生疏...)


我同意关于forEach的观点,但即使返回一个新数组,在这种情况下它不也是相同的吗? - nikjohn
1
@DustinToothless:OP的原始.map()代码没有像他想要的那样返回一个task元素数组。它只返回格式化日期的数组(即task.start_date的值),因为这是箭头函数的隐式返回值。可以通过在分配后在.map()回调中返回task来解决这个问题,但此时使用.forEach()更简单。当然,我假设在原地修改原始的tasks数组是可以的,但原始代码也是这样做的。 - Michael Geary
3
谢谢 @MichaelGeary 为我澄清 .map() 的用法。我觉得周围对它的炒作让我误以为每次循环都需要用它 :) - jeff randall
这正是我所需要的,但我不知道为什么我总是默认使用map。 - Jacob Broughton

15
你可以使用map()Object.assign()编写一个通用函数,该函数接受对象数组并返回带有修改属性的对象数组。

const users = [
    { name: 'Jhon', logged: 'Tue Feb 04 2020 14:16:10 GMT+0200 (Eastern European Standard Time)'  },
    { name: 'Doe', logged: 'Tue Feb 04 2020 14:20:10 GMT+0200 (Eastern European Standard Time)'  }
];


const handleDates = (list, prop) => {
    return list.map(item => {
      const obj = Object.assign({}, item);
      obj[prop] = new Date(obj[prop]).toLocaleDateString();
      return obj;
    });
}

console.log(users)
console.log(handleDates(users, 'logged'))


5
您还可以使用map和数组解构:
//Get all the Tasks
function getAllTasks() {
  return models.gantt_tasks.findAll()
  .then(function(tasks){
     return tasks.map(task => {
         return {...task, start_date = task.start_date.format("YYYY-MM-DD")}
     }
  })
  .catch(function(e) {
    console.error(e);
    return e;
  });
}

1
你需要专注于箭头函数的简洁性。你可以使用大括号 {} 并在更改 start_date 后返回 task 对象。示例代码如下:
//Get all the Tasks
function getAllTasks() {
  return models.gantt_tasks.findAll()
  .then(tasks => {

    let results = tasks.map(task => {
      task.start_date = task.start_date.format("YYYY-MM-DD");
      return task;
    });
    return results;
  })
  .catch(e => {
    console.error(e);
    return e;
  });
}

没有任何阻碍你在箭头函数中使用括号和返回语句。


0
问题在于你正在从then函数的回调中返回数组,这并没有做任何事情。你想要将回调传递到then函数中,像这样:
function doSomething(tasks) {
  let results = tasks.map(task => task.start_date = task.start_date.format("YYYY-MM-DD"));
  return results;
}

function getAllTasks(callBack) {
  models.gantt_tasks.findAll()
  .then(callback)
  .catch(function(e) {
    console.error(e);
    return e;
  });
}

var results = getAllTasks(doSomething);

1
这与 OP 的代码没有什么不同,除了回调现在是一个单独的函数。 - Koen.
then的回调函数中返回某些内容确实有所作为——它将使得该返回值成为Promise的值。问题在于他返回了错误的内容——一个重新格式化日期的数组。 - user663031
@torazaburo - 没错,我只是在提取一个日期数组。 - jeff randall
@DustinToothless - 这不是传统的回调模式吗?而我正在尝试使用 Promise 的 .then() 可链式调用模式。 - jeff randall

0

对我来说,两种方法都有效。我认为在测试时我会使用forEach(),直到需要复制它,然后我将创建单独的函数。

这就是我最终得到的结果。

let moment = require('moment');

// Get all the Tasks
function getAllTasks() {
 return models.gantt_tasks.findAll()
 .then(function(tasks) {
   tasks.forEach( task => task.start_date = moment(task.start_date).format("YYYY-MM-DD"));
   return tasks;
 })
 .catch(function(e) {
   console.error(e);
   return e;
 });
}

router.get('/data', (req, res) =>
 getAllTasks()
  .then((tasks) => res.json({ data: tasks }))
)

最终我需要使用Moment库来转换我的日期,因为Sequelize无法让我轻松地操作日期。


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