簡體   English   中英

如何在javascript中保持對象不可變的數組?

[英]How to keep an array with objects immutable in javascript?

我想基於兩個數組創建一個數組 - “ideaList”和“endorsements”全局聲明。 由於ideaList 和endorsements 用於程序的其他部分,我需要它們是不可變的,我認為 .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,然后在另一個函數中為其分配一些數據。

你不是在改變數組本身,而是改變數組包含引用的對象。 .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)
      ...

為了幫助牢記不變性,您可以將您的值視為原始值。

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. 伊默
  2. 不可變JS

在 JS 中,對象是被引用的。 換句話說,在創建時,您將對象變量指向一個打算保存有意義值的內存位置。

var o = {foo: 'bar'}

變量 o 現在指向具有{foo: bar}的內存。

var p = o;

現在變量p也指向相同的內存位置。 所以,如果你改變o ,它也會改變p

這就是你的函數內部發生的事情。 即使您使用不會改變其值的 Array 方法,數組元素本身也是在函數內部被修改的對象。 它創建一個新數組 - 但元素指向對象的相同舊內存位置。

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或 latest spread 運算符來完成。

a = [{foo: 1}];

b = a.map(o => {

    var p = {...o};    //Create a new object
    p.foo = 2;
    return p;

})

console.log(a);  // {foo:1}

您在地圖功能中分配這些屬性,您需要更改它。 (只需聲明一個空對象,而不是使用您當前的obj

你可以使用新的 ES6 內置不變性機制,或者你可以像這樣在你的對象周圍包裹一個很好的 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。

您可以在此處閱讀有關此編碼模式的更多信息

制作可變對象“副本”的一種簡單方法是將它們字符串化為另一個對象,然后將它們解析回一個新數組。 這對我有用。

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

您使用freeze方法提供要使其不可變的對象。

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

其實可以使用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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM