【JavaScript】破壊と非破壊的処理の違いは何か?どっちがいいのか(使うべき)?変換する方法|for-ofやspliceの書き換え実例(mutableとimmutableとは何か?)

javascript-prograshi(プロぐらし)-kv JavaScript
記事内に広告が含まれていることがあります。

JavaScriptではメソッドやfor文などの繰り返し処理を行ったときに、処理対象の要素自体が置き換わる破壊処理と、処理対象はそのままで新たに処理後の要素を作る非破壊処理があります。

ここでは破壊的処理とは何か?非破壊的処理とは何かや、破壊と非破壊のどちらを使うべきか、破壊的処理を非破壊的な処理に変換する方法を実例を交えて解説しています。


破壊と非破壊処理とは何か?

そもそも破壊的処理と非破壊的処理って何?という方もいるかもしれません。

破壊的処理とは何か?

破壊的処理とは、メソッドなどを実行したときに元の要素自体を書き換える処理のことです。

例えば、push, pop, spliceといったメソッドがあります。


実例

[ 1, 2, 3, 4 ]という配列「arr」に対して、pushメソッドで「a」という文字列を追加すると、「arr」自体が書き換わります。

arr = [ 1, 2, 3, 4 ]

arr.push( "a" )

console.log(arr)
//出力
 [1, 2, 3, 4, 'a']

すなわち、破壊されたということです。

なお、「arr.push(“a”)」の処理を他の変数に代入しても、処理結果がその中に入るわけではありません。

arr = [ 1, 2, 3, 4 ]

brr = arr.push( "a" )

console.log(brr)
//出力
6


非破壊的処理とは何か?

非破壊的処理とは、メソッドなどを実行したときに元の要素自体はそのままで、新たに処理結果を作る処理のことです。

例えば、slice、filter、mapメソッド、スプレッド構文(…)などがあります。


実例

[ 1, 2, 3, 4 ]という配列「arr」に対して、スプレッド構文を使って「a」という文字列を追加しても、元の「arr」自体は変化しません。

arr = [ 1, 2, 3, 4 ]

brr = [
  ...arr,
  "a"
]

console.log(arr)
//出力
 [1, 2, 3, 4 ]

console.log(brr)
//出力
 [1, 2, 3, 4, "a" ]

つまり、破壊されていない=非破壊処理ということです。

処理を代入した変数「brr」の中身を見ると、 [1, 2, 3, 4, “a” ]となり、「a」が追加されています。

なお、元の変数自体を置き換えたい場合は、処理結果を元の変数自体に代入する必要があります。

arr = [ 1, 2, 3, 4 ]

arr = [
  ...arr,
  "a"
]

console.log(arr)
//出力
 [1, 2, 3, 4, "a" ]


破壊と非破壊どちらを使うべきか?

基本的には非破壊が安全

基本的には非破壊処理を使っておくのが安全です。

対象の要素が1ヵ所だけでなく、他の場所でも参照され処理に使われている場合、どこかで破壊的処理が行われると、変更を検知して他の処理が作動して、その処理の中でも対象の要素の値が書き換わるといった望んでいない連鎖的な処理が発生する可能性があるためです。

これが発生するとデータが壊れてしまいます。

非破壊処理を使えば、元のデータ自体はそのままなので、安全に処理を行うことができます。

また、非破壊処理を元の要素自体に代入する場合でも、非破壊の処理が完全に終わった結果を代入することになります。

一方、for文などの繰り返しで破壊的処理を行うと、処理の途中の破壊的処理の度に、要素が置き換わることになってしまいます。


破壊的処理を使うメリット

非破壊的処理だけが安全で、破壊的処理は必要ないというわけではありません。破壊的処理には破壊的処理のメリットがあります。

データ量を少なく抑えられる

非破壊の処理は新たなデータを作成するため、データ量が多い場合メモリが圧迫され処理速度に影響が出やすくなります。

一方、破壊的処理はデータは常に1つのため、メモリの圧迫による処理速度の低下を避けることができます。


破壊的処理は非破壊的処理に変換できる

非破壊的処理で書かれているコードを、破壊的処理に変換するためには、処理自体を大きく書き直さなければいけません。

一方、破壊的処理で書かれているコードは、シャローコピーやディープコピーすることで、非破壊的処理に変更することができます。

シャローコピーとは何か?

シャローコピーとは、浅いコピーのことです。

オブジェクトなど入れ子になった要素を別の変数に代入して、新たなオブジェクトを作成した場合に、外側のプロパティは別物になりますが、入れ子になった深いオブジェクトは元のオブジェクトと共通になります。

シャローとは英語で「shallow」浅いと言う意味です。

ディープコピーとは何か?

ディープコピーとは、深いコピーのことです。

オブジェクトなど入れ子になった要素などを、ごっそり丸ごと新しいオブジェクトとしてコピーします。

シャローコピーと比較して、再帰的行ったコピー処理です。


合わせて読みたい

シャローコピーとディープコピーのことについて詳しく知りたい方は下記をご参考ください。

【JavaScript】シャローコピーとディープコピーとは何か?意味や変換・書き換え方法を実例で解説


破壊処理と非破壊処理を使う時の注意点

破壊的処理と非破壊的処理を区別せずに、使いやすい方やたまたま思いついた方を使うというのは避けるべきです。

後々大きなエラーや望まない挙動を発生させないためにも、なぜ非破壊的処理を使うのか、なぜ破壊的処理をつかうのか、あるいは、ここはなぜ破壊的処理でもいいのかを理解しながらコードを作成することが重要です。


主な破壊的処理の一覧

主な破壊的処理は以下のようなものがあります。

メソッド・文内容
splice配列の既存の要素を取り除いたり、置き換えたり、新しい要素を追加する。
push配列の末尾に指定した要素を追加する。
pop配列の最後の要素を取り除く。
shift配列の最初の要素を取り除く。
unshift配列の最初に1つ以上の要素を追加する。
sort配列の要素を並べ替える。
reverse配列の要素を反転させる。
fill配列の要素を指定した値で埋める。
for指定した回数繰り返す。


主な非破壊的処理の例

主な非破壊的処理には次のようなものがあります。

メソッド内容
slice配列の指定した箇所だけを抜き出す。
filter配列の要素を1つ1つ取り出し、条件式と照らし合わせて、新しい配列を作成する。
map配列の要素を1つ1つ取り出し、処理を加えて、新しい配列を作成する。
reduce配列の要素を1つ1つ取り出し、処理を実行し、一意の値を返す。
concat配列に要素を追加する。
find配列の中から指定された条件にマッチする要素を抜き出す。
findIndex配列の中から指定された条件にマッチする要素のインデックス番号を返す。なければ-1を返す。
indexOf配列の中で指定した要素があればその要素のインデックス番号を返す。なければ-1を返す。
includes配列の中に指定した要素が含まれていればtrueを、なければfalseを返す。
join配列の各要素を指定した文字列で結合して、一つの文字列を返す
…(スプレッド構文)配列の外側の[ ]を取り除く


(参考)mutableとimmutable

エラーの内容に「mutable」(ミュータブル)と「immutable」(イミュータブル)という単語が出てくることがあります。

これは「破壊的」と「非破壊的」のことです。

mutableは日本語で「破壊的な」、immutableは「非破壊的な」という意味です。

「mutable」(ミュータブル)と「immutable」(イミュータブル)という単語がでてきたら、破壊的処理と非破壊的処理のことをいっているなという認識で問題ありません。


破壊的処理 → 非破壊的処理への変換1

for-ofによる破壊的処理

例えば、以下のような配列「arr」があるとします。

arr = [
  { "key": 1 },
  { "key": 2 },
  { "key": 3 }
]

これをfor文を使って要素を一つ一つ取り出し、オブジェクトの値が1以上の時は10を追加する処理を行う場合は次のようになります。

for ( obj of arr ){
  if( obj.key > 1 ){
    obj.key += 10
  }
}

この処理は破壊的処理となるため元の変数「arr」は以下のように置き換わります。

console.log(arr)

//出力
arr = [
  { "key": 1 },
  { "key": 12 },
  { "key": 13 }
]


mapで非破壊処理に変換

for-ofの破壊的処理を非破壊的処理に置き換えたい場合は、mapメソッドを使います。

brr = arr.map( elem => {
  if( elem.key > 1 ){
    return { "key": elem.key += 10 }
  }else { return { "key": elem.key } }
} )

この処理は非破壊的処理となるため元の変数「arr」は元のままとなります。

console.log(arr)

//出力
arr = [
  { "key": 1 },
  { "key": 2 },
  { "key": 3 }
]

mapメソッドを使った処理結果を代入した変数「brr」は以下のようになります。

console.log(brr)

//出力
arr = [
  { "key": 1 },
  { "key": 12 },
  { "key": 13 }
]
合わせて読みたい

mapメソッドの引数や詳しい使い方については下記をご参考ください。

【JavaScript】mapメソッドとは何か?使い方を実例で解説。非破壊で各要素に同じ処理を加える方法


非破壊処理の注意点(map + 破壊処理)

mapメソッドのように非破壊の処理がありますが、その処理の中で破壊的処理を使った場合は、元の変数の値は破壊されるので注意が必要です。

例えば、以下のような配列「arr」があるとします。

arr = [
  { "key": 1 },
  { "key": 2 },
  { "key": 3 }
]

この「arr」に対して非破壊的処理であるmapメソッドを使ったとします。

arr.map( elem => {
  arr.push(elem.key)
} )

すると元の「arr」の中身自体が書き変わります。

console.log(arr)

//出力
arr = [
  { "key": 1 },
  { "key": 2 },
  { "key": 3 },
  1,
  2,
  3
]


破壊的処理 → 非破壊的処理への変換2

spliceによる破壊的処理

配列の既存の要素を取り除いたり、置き換えたり、新しい要素を追加することができる破壊的な処理、spliceメソッドを使った処理の例です。

[1,2,3,4]という配列が入った変数「a」にspliceメソッドを使って、真ん中に「”aaa”」という文字列を追加する処理は以下のようになります。

a = [1,2,3,4]

a.splice(2,0,"aaa")
console.log(a)

//出力
[1, 2, "aaa", 3, 4]

元の配列「a」自体が置き換わっていることがわかります。

合わせて読みたい

spliceメソッドの詳しい使い方については下記をご参考ください。

【JavaScript】spliceとは何か?配列の指定した場所に要素を追加する方法


スプレッド構文とsliceで非破壊的処理に変換する

上記の処理を非破壊で行う場合は、スプレッド構文とsliceメソッドを使うことで非破壊的処理に変換することができます。

a = [1,2,3,4]

b = [
  ...a.slice(0,2),
  ...["aaa"],
  ...a.slice(2)
]

console.log(a)

//出力
[1, 2, 3, 4]

処理結果後ろも元の変数「a」の値は変化していないことがわかります。

処理結果を代入した「b」は以下のようになっています。

console.log(b)

//出力
[1, 2, "aaa", 3, 4]
合わせて読みたい

sliceメソッドとスプレッド構文の詳しい使い方については下記をご参考ください。

【JavaScript】sliceメソッドとは何か?配列の指定した要素を削除・追加する方法を実例で解説

タイトルとURLをコピーしました