まぁ次のコードを見てくださいな。
const hoge = arg => {
let temp = arg + 1;
return temp;
};
console.log(hoge(1));
関数hoge
は入力された値に1を加算して返すだけです。機能としてはそれだけです。ここで注目するのは、加算された結果の格納先です。ここでは変数temp
を新しく宣言して格納しています。アロー関数なんだから、return arg + 1;
にすればいいじゃん、というのはとりあえずここでは置いておいて。
次に、このコードを少し書き直してみます。
const hoge = arg => {
arg = arg + 1; // arg += 1;でも同様
return arg;
};
console.log(hoge(1));
変数を新しく宣言して結果を格納するのではなく、引数として与えられたarg
に直接再代入しちゃえという上記のコード。つまり、計算結果を関数の引数に再代入しているわけです。もちろん、少なくとも狙ったように入力値が1加算されて出力されます。さきほどのコードと同様に動作するわけです。
つまり、引数に本来格納されている値とは別の値を関数の中で再代入しちゃうようなケースですが、とりあえず文法エラーにならず実行が可能で、少なくともこのケースでは想定通りの挙動をしています。なので、少なくとも言語仕様としては問題ないと言えるわけです。
ただ、言語仕様で問題ないからって多用してもいいようなものなの?コレ。
Airbnbのコーディング規約では、関数の引数へ値を再代入することは禁止されています。ESLintにも、再代入を禁止するルールが存在します。
7.13 パラメータを再割り当てしない。eslint: no-param-reassign
const hoge = foo => {
foo += 1;
return foo;
}
hoge(1);
つまり上記のような、foo
に再代入するのはダメということですね。
ESLintなどでは、「no-param-reassign」という名前でルールが存在します。実際、ESLintが体験できるデモサイトでこのルールを有効にした状態で上記のコードを記述すると、再代入している部分でエラーメッセージが表示されます。ただ、このルールはデフォルト設定だとOFFになっています。そこまで優先度は高くないんだろうか。高くないんだろうな。このあたりのさじ加減は、プロジェクトそのものやメンバーの思想に多少左右されるかもしれません。
なお、ESLintでもオブジェクトに対する再代入は「no-param-reassign」のルールをONにしただけではエラーに対象ではありません。オプションで{ "props": true }
とした場合のみ、「プロパティの変更まで監視するよー」とオブジェクトへの変更がエラー扱いになります。
まぁそれを言っちゃうとケースバイケースとしか言えないんだけど、ちょっと思考実験をしてみます。
const hoge = (foo) => {
foo = 'string';
return foo;
}
const fuga = (bar) => {
bar.prop = 'baz';
return bar;
}
let ham = '';
let eggs = {};
console.log(ham);
console.log(eggs);
hoge(ham);
fuga(eggs);
console.log(ham);
console.log(eggs);
こっちはオブジェクトにプロパティを追加しちゃうケース。
""
{}
""
{
"prop": "baz"
}
上記のように、オブジェクトの内容が変更されています。対して、文字列が格納されている変数には影響なし。
const hoge = (foo, bar) => {
foo = 9;
bar.push(4);
console.log(`inside function: ${foo}, ${bar}`);
}
let n = 1;
let a = [0, 1, 2, 3];
console.log(`outside function 1: ${n}, ${a}`);
hoge(n, a);
console.log(`outside function 2: ${n}, ${a}`);
こっちは配列に値を追加しちゃうケース。
outside function 1: 1, 0,1,2,3
inside function: 9, 0,1,2,3,4
outside function 2: 1, 0,1,2,3,4
実行してみると、変数n
とa
に着目したとき関数hoge
を実行する前後でn
は変化しないものの、配列であるa
は内容が変化してしまっています。関数内で引数に対し操作を行ってしまったため、もともと変数に格納されていた値が揮発してしまっています。
個人的には、これらのような再代入を行うコードが危ないと思っています。
自分で書いておいてなんですけど、これは再代入処理が悪いというよりミュータブルとイミュータブルを適切に処理していないために起きている症状なので、再代入の是非を問うケースと考えると少々例えが悪い気もしなくもないな・・・。
前述のようなコードを書くことでオブジェクトなどの内容が書き換わってしまい、場合によってはこのことがバグの原因になる可能性が否めません。なので、オブジェクトに意図せずプロパティを追加しちゃったりするような、本来の想定とは異なる記述をするケースを発見するためにもオプションは有用だと考えます。ただ、単純に引数に再代入するのはダメってのは、チェックする必要があるか?という気がしなくもないんだよなー。
プロパティの誤った追加など、意図しない操作をしてしまう可能性があるのは「オプション」として付属品扱いされている方なので、こちらの方がリスキーだと思うのですがね。
なにはともあれ、オブジェクトに対する意図しない操作によりオブジェクトの内容を書き換えてしまい、バグの発生原因を作ることが危惧されるくらいのコードを書く可能性があるなら、もういっそルールで禁止してしまえということも1つの解決策かもしれません。少なくとも自分はESLintの設定をONにしておこうと思います。もちろん、オプション設定でオブジェクトへの誤った操作を防ぐために、です。props
のチェックを優先してもらいたいのは山々ですが、現状の仕様では仕方ありません。