なぜJavaScriptで不変性が重要ですか?


JavaScriptでは、プリミティブデータ型(数値、文字列、等)は不変ですが、オブジェクトや配列が変更可能な場合には、オブジェクトと配列を宣言した場合はconst であろうconstant これは変更できません.
const obj = {
  a: "apple"
}
const updatedObj = obj
updatedObj.a = "banana"
console.log(obj.a) // "banana'
我々が更新するとき、あなたが見ることができるようにupdatedObj 's値、元のオブジェクト変数obj 同様に.オブジェクトのコピーの背後にある理由by reference , というのはconst updatedObj = obj updatedObj 参照/ポインティングobj 'sメモリアドレスupdatedObj 我々は更新obj なぜなら、彼らは同じ値を指しているからです.しかし、プリミティブデータ型(数値、文字列、ブール、未定義など)の場合は逆です.

All primitives are immutable, i.e., they cannot be altered. It is important not to confuse a primitive itself with a variable assigned a primitive value. The variable may be reassigned a new value, but the existing value can not be changed in the ways that objects, arrays, and functions can be altered. ~ MDN


ここでは、文字列と数字が変化しない例を見ることができます.
const num = 39
let updatedNum = num

updatedNum = 45
console.log(num) // 39

const str = "lion"
let updatedStr = str

updatedStr = "tiger"
console.log(str) // "lion"
なぜ我々は不変性を気にするのか?JavaScriptがこのように構築されたならば、理由がなければなりません.JavaScriptはOHPとして使用できるマルチパラダイム言語であるため、FP(関数型プログラミング)として使用できます.
機能的プログラミングは不変性と重み付けを包含するpersistent data structure . また、ReduxやReduxのような新しいライブラリは、imduabilityの利点を持っています.store つの巨大な、プレーンJSオブジェクト、不変の1つであり、これはredux time travel どこで前の状態を見ることができる/変更または反応では、あなたのローカル州の以前の値を確認することができます、彼らはすべてオブジェクトの不可解さから来ている.
ここでは、JSにおける不変のオブジェクトを作成する簡単な例を示します.
const obj = {
  a: "apple"
}
const updatedObj = Object.assign({}, obj)
updatedObj.a = "banana"

console.log(obj.a) // "apple"
console.log(updatedObj.a) // "banana"
現在、我々は我々の原物を突然変異しませんobj .

We will have more practical examples on "How not to mutate your objects and arrays" in the next articles.

More about Object.assign()


質問をする🙋‍♂️ , 我々が我々のオブジェクト値を突然変異しないならば、待ちますあなたは間違っていない!
そこが来るstructural sharing , あなたがしたくないdeep copy オブジェクトshallow copy それ.まさにgit コードの全バージョンをコピーしませんが、前のコミットで変更されていないファイルを共有しません.Object.assign() メソッドはshallow copying . しかし、入れ子になっているオブジェクトのプロパティを持っている場合、それらの欠点はありません.
const obj = {
  a: "apple",
  b: {
    c: "lemon"
  }
}
const updatedObj = Object.assign({}, obj)
updatedObj.a = "mango"
updatedObj.b.c = "banana"

console.log(obj.a) // "apple"
console.log(obj.b.c) // "banana"

b: { c: "lemon" } is not immutable here as it's nested property, we will see examples of how to make objects and arrays immutable including nested (complex structures) ones as well.


So shallow copying 多くのメモリ消費量を取ることはありません.

変更不能オブジェクト
  • 使用Object.assign()
  • let obj = {
      a: "apple"
    }
    let updatedObj = Object.assign({}, obj)
    updatedObj.a = "banana"
    
    console.log(obj.a) // "apple"
    console.log(updatedObj.a) // "banana"
    
  • 使用Object Spread Operators :
  •  let obj = {
      a: "apple"
    }
    let updatedObj = { ...obj }
    updatedObj.a = "banana"
    
    console.log(obj.a) // "apple"
    console.log(updatedObj.a) // "banana"
    
    Spread Operators 新しいES 6構文Object.assign() 浅いコピーを行います.
    複合データ構造体
    let obj = {
      a: "apple",
      b: {
         c: "lemon"
      }
    }
    let updatedObj = {...obj, b: { ...obj.b } };
    updatedObj.a = "banana"
    updatedObj.b.c = "peach"
    
    console.log(obj.a) // "apple"
    console.log(obj.b.c) // "lemon"
    console.log(updatedObj.a) // "banana"
    console.log(updatedObj.b.c) // "peach"
    
    入れ子になったオブジェクトのプロパティlet updatedObj = {...obj, b: { ...obj.b } }; プロパティ名を入れ子に展開できます.

    不変の配列
    1 .Array Spread Operators
    let arr = [1, 2, 3, 4]
    let updatedArr = [...arr]
    updatedArr[2] = 5
    
    console.log(arr[2])// 3
    console.log(updatedArr[2])// 5
    
    配列スプレッド演算子はオブジェクト拡散演算子と同じですlearn more here .
    利用slice() メソッド:
    let arr = [1, 2, 3, 4]
    let updatedArr = arr.slice(0, arr.length);
    updatedArr[2] = 5
    
    console.log(arr[2]) // 3
    console.log(updatedArr[2]) // 5
    console.log(updatedArr) // [1, 2, 5, 4]
    
    slice() インデックス(最初の引数)から配列を切り取ります.(インデックスは2番目の引数).があるsplice() 配列のメソッドは、slice() 元の配列の内容を変更するlearn more on slice here , learn more on splice .
    利用map() , filter() :
    let arr = [1, 2, 3, 4]
    
    let updatedArr = arr.map(function(value, index, arr){
      return value;
    });
    updatedArr[2] = 5
    
    console.log(arr[2]) // 3
    console.log(updatedArr[2]) // 5
    console.log(updatedArr) // [1, 2, 5, 4]
    
    map() 新しい配列を返し、引数としてコールバック関数を受け取り、元の配列のすべての要素に対して呼び出します.コールバック関数value (現在の値)index (現在のインデックス)array (元の配列)引数learn more here .filter()
    let arr = [1, 2, 3, 4]
    
    let updatedArr = arr.filter(function(value, index, arr){
      return value;
    });
    updatedArr[2] = 5
    
    console.log(arr[2]) // 3
    console.log(updatedArr[2]) // 5
    console.log(updatedArr) // [1, 2, 5, 4]
    
    filter() and map() 同じように働くlearn more here .

    They both return a new array. map() returns a new array of elements where you have applied some function on the element so that it changes the element. filter() returns a new array of the elements of the original array (with no change to the elements). filter() will only return elements where the function you specify returns a value of true for each element passed to the function.


    配列のもう一つの方法がありますreduce() , 新しい配列は返されませんが、元の配列では操作不能になります.
    let arr = [1, 2, 3, 4];
    // 1 + 2 + 3 + 4
    const reducer = (accumulator, currentValue) => accumulator + currentValue;
    
    let updatedArr = arr.reduce(reducer)
    console.log(updatedArr) // 10
    
    reduce() 最初は混乱するかもしれませんが、できるだけ簡単に説明しようと思います.以下の例を見てみましょう.
    let sum = 0;
    let i = 0;
    while (i<arr.length){
      sum+=arr[i]; // 1 + 2 + 3 + 4
      i++;
    }
    
    console.log(sum) // 10
    
    配列のすべての値を合計するループです.我々は、同じことをしようとしていますreduce() .reduce() takes reducer 関数であるコールバックは4つの引数をとります.accumulator , currentValue , currentIndex , originalArray . アキュムレータは最後の反復から返される値を保存しますsum ループの例で変数arr[i] . それでreduce learn more here .
    私の望み🤞 それはすべて意味をなす.
    余分な資源
    This answer here gives a great explanation on "why is immutability important?" ,
    ,
    More on immutable methods of array and object