【问题标题】:How to keep an array with objects immutable in javascript?如何在javascript中保持对象不可变的数组?
【发布时间】:2019-03-25 08:04:00
【问题描述】:

我想创建一个基于两个数组的数组——全局声明的“ideaList”和“endorsements”。由于在程序的其他部分使用了ideaList 和背书,我需要它们是不可变的,我认为 .map 和 .filter 会保持这种不变性。

function prepareIdeaArray(){
var preFilteredIdeas=ideaList
        .filter(hasIdeaPassedControl)
        .map(obj => {obj.count = endorsements
                                 .filter(x=>x.ideaNumber===obj.ideaNumber)
                                 .reduce((sum, x)=>sum+x.count,0); 
                     obj.like = endorsements
                                 .filter(x=>x.ideaNumber===obj.ideaNumber && x.who===activeUser)
                                 .reduce((sum, x)=>sum+x.count,0)===0?false:true
                     obj.position = generatePosition(obj.status)
                     obj.description = obj.description.replace(/\n/g, '<br>')  
                     return obj;});
preFilteredIdeas.sort(compareOn.bind(null,'count',false)).sort(compareOn.bind(null,'position',true))
return preFilteredIdeas;
}

但是,当我在执行完这个函数后console.logideaList时,我注意到数组的对象都有“count”、“like”、“position”属性和值,这证明数组已经被突变。

我尝试仅使用 .map,但结果相同。

您知道我可以如何防止ideaList 发生变异吗?另外我想避免使用 const,因为我首先全局声明了ideaList,然后在另一个函数中为其分配了一些数据。

【问题讨论】:

  • 制作副本:let clone = [...ideaList] BTW .map() 不会改变它会返回一个副本,并且原始数组不会受到影响。
  • 您至少有 5 个或更多未定义的变量都是全局变量吗?
  • ideaList,背书是全局数组,activeUser是全局字符串。 hasIdeaPassedControl 和 compareOn 是函数。

标签: javascript arrays mutability


【解决方案1】:

您不是在改变数组本身,而是改变数组包含的对象。 .map() 创建数组的副本,但其中包含的引用指向与原始对象完全相同的对象,您已通过直接向它们添加属性对其进行了变异。

您还需要制作这些对象的副本并将属性添加到这些副本中。一个巧妙的方法是在.map() 回调中使用对象传播:

    .map(({ ...obj }) => {
      obj.count = endorsements
                             .filter(x=>x.ideaNumber===obj.ideaNumber)
      ...

如果您的环境不支持对象传播语法,请使用Object.assign() 克隆对象:

    .map(originalObj => {
      const obj = Object.assign({}, originalObj);
      obj.count = endorsements
                             .filter(x=>x.ideaNumber===obj.ideaNumber)
      ...

【讨论】:

    【解决方案2】:

    在 JS 中,对象是被引用的。换句话说,当创建时,您让对象变量指向一个打算保存有意义值的内存位置。

    var o = {foo: 'bar'}
    

    变量 o 现在指向具有{foo: bar} 的内存。

    var p = o;
    

    现在变量p 也指向同一个内存位置。所以,如果你改变o,它也会改变p

    这就是你的函数内部发生的事情。即使您使用不会改变其值的数组方法,数组元素本身也是在函数内部被修改的对象。它创建了一个新数组 - 但元素指向对象的相同旧内存位置。

    var a = [{foo: 1}];     //Let's create an array
    
    //Now create another array out of it
    var b = a.map(o => {
       o.foo = 2;
       return o;
    })
    
    console.log(a);   //{foo: 2}
    

    一种方法是在操作期间为新数组创建一个新对象。这可以通过Object.assign 或最新的传播运算符来完成。

    a = [{foo: 1}];
    
    b = a.map(o => {
    
        var p = {...o};    //Create a new object
        p.foo = 2;
        return p;
    
    })
    
    console.log(a);  // {foo:1}
    

    【讨论】:

      【解决方案3】:

      为了帮助您牢记不变性,您可以将您的值视为原语。

      1 === 2 // false
      'hello' === 'world' // false
      

      您也可以将这种思维方式扩展到非原始人

      [1, 2, 3] === [1, 2, 3] // false
      { username: 'hitmands' } === { username: 'hitmands' } // false
      

      为了更好的理解,请看MDN - Equality Comparisons and Sameness


      如何强制不变性?

      总是返回给定对象的新实例!

      假设我们必须设置待办事项的属性status。以旧的方式,我们会这样做:

      todo.status = 'new status';
      

      但是,我们可以通过简单地复制给定对象并返回一个新对象来强制实现不变性。

      const todo = { id: 'foo', status: 'pending' };
      
      const newTodo = Object.assign({}, todo, { status: 'completed' });
      
      todo === newTodo // false;
      
      todo.status // 'pending'
      newTodo.status // 'completed'
      

      回到你的例子,我们不做obj.count = ...,而是做:

      Object.assign({}, obj, { count: ... }) 
      // or
      ({ ...obj, count: /* something */ })
      

      有一些库可以帮助您处理不可变模式:

      1. Immer
      2. ImmutableJS

      【讨论】:

        【解决方案4】:

        您在地图函数中分配这些属性,您需要更改它。 (只需声明一个空对象,而不是使用您当前的 obj

        【讨论】:

        • 举个例子会有所帮助
        【解决方案5】:

        您可以使用新的 ES6 built-in 不变性机制,或者您可以像这样在对象周围包裹一个漂亮的 getter

        var myProvider = {}
        function (context)
        {
            function initializeMyObject()
            {
                return 5;
            }
        
            var myImmutableObject = initializeMyObject();
        
            context.getImmutableObject = function()
            {
                // make an in-depth copy of your object.
                var x = myImmutableObject
        
                return x;
            }
        
        
        }(myProvider);
        
        var x = myProvider.getImmutableObject();
        

        这将使您的对象保持在全局范围之外,但 getter 将可以在您的全局范围内访问。

        你可以阅读更多关于这个编码模式here

        【讨论】:

          【解决方案6】:

          制作可变对象的“副本”的一种简单方法是将它们字符串化为另一个对象,然后将它们解析回一个新数组。这对我有用。

          function returnCopy(arrayThing) {
              let str = JSON.stringify(arrayThing);
              let arr = JSON.parse(str);
            return arr;
          }
          

          【讨论】:

          • 这比只执行[...original]original.slice() 的性能要差,而且如果数组中有函数、符号或bigints 的东西,它也不起作用。 Bigints 会抛出错误,符号和函数将被忽略。
          【解决方案7】:

          您使用freeze 方法提供您想要使其不可变的对象。

          const person = { name: "Bob", age: 26 }
          Object.freeze(person)
          

          【讨论】:

            【解决方案8】:

            其实可以使用spread opreator来让原数组保持不变,下面y是不可变数组的例子:

            const y = [1,2,3,4,5];
            function arrayRotation(arr, r, v) {
              for(let i = 0; i < r; i++) {
                arr = [...y]; // make array y as immutable array [1,2,3,4,5]
                arr.splice(i, 0, v);
                arr.pop();
                console.log(`Rotation ${i+1}`, arr);
              }
            }
            arrayRotation(y, 3, 5)
            

            如果不使用扩展运算符,y 数组将在循环运行时逐次更改。

            这里是可变数组结果:

            const y = [1,2,3,4,5];
            function arrayRotation(arr, r, v) {
              for(let i = 0; i < r; i++) {
                arr = y; // this is mutable, because arr and y has same memory address
                arr.splice(i, 0, v);
                arr.pop();
                console.log(`Rotation ${i+1}`, arr);
              }
            }
            arrayRotation(y, 3, 5)
            

            【讨论】:

              猜你喜欢
              • 2020-08-25
              • 2011-11-20
              • 2011-08-12
              • 2011-11-16
              • 1970-01-01
              • 2015-07-30
              • 1970-01-01
              • 1970-01-01
              • 2018-08-25
              相关资源
              最近更新 更多