1.

JavaScript の代入演算子|複合代入・??= と ||= ・分割代入

編集
この記事の要点
  • =なので値を返す。if (x = 1) と書くと代入して真になってしまう
  • 複合代入は += -= *= /= %= **= とビット演算の各種
  • 論理代入 ||= &&= ??= は条件を満たすときだけ代入する
  • ??=null / undefined のときだけ。||=0"" も上書きする
  • オブジェクトの代入は参照のコピー。中身は共有される

基本の代入

let x = 10;
let a, b;

a = b = 5;            // 右から左へ。a も b も 5

// = は値を返すので式として使える
let y;
console.log((y = 3));  // 3

// if の中で使うと事故になる
if (x = 5) {           // 代入した結果 5 が真なので必ず通る
  console.log("常に実行される");
}
if (x === 5) { }       // 比較はこちら

ESLint の no-cond-assign条件式での代入を検出してくれます。意図的に書く場合はかっこで囲む慣習があります。

複合代入

演算子意味例(x = 10 のとき)
+=加算(文字列なら連結)x += 5 → 15
-=減算x -= 5 → 5
*=乗算x *= 2 → 20
/=除算x /= 4 → 2.5
%=剰余x %= 3 → 1
**=べき乗x **= 2 → 100
&= |= ^= <<= >>=ビット演算x <<= 1 → 20
let s = "abc";
s += 123;              // "abc123"   数値も文字列として連結される

let n = 10;
n += "5";              // "105"      ← 意図しない連結。入力値に注意
n = 10;
n += Number("5");      // 15         変換してから足す

インクリメント・デクリメント

let i = 5;

console.log(i++);   // 5   返してから増やす(後置)
console.log(i);     // 6
console.log(++i);   // 7   増やしてから返す(前置)

// 同じ式の中で 2 回使わない
let n = 1;
console.log(n++ + ++n);   // 1 + 3 = 4(読めないので避ける)

// 文字列には使えない
let s = "5";
s++;                // 6(数値に変換される)
let t = "abc";
t++;                // NaN

ループの外で使うときは、戻り値を使わず単独の文として書くのが安全です。

論理代入(ES2021)

let a = null;
a ??= "既定値";        // null / undefined のときだけ代入
console.log(a);       // 既定値

let b = 0;
b ??= 10;             // 0 は null ではないので代入されない
console.log(b);       // 0

let c = 0;
c ||= 10;             // 偽(0 も含む)なら代入
console.log(c);       // 10   ← 0 が消えてしまう

let d = 1;
d &&= 5;             // 真のときだけ代入
console.log(d);       // 5
演算子代入される条件使いどころ
??=左が null / undefined既定値の設定(推奨)
||=左が偽(0 "" false を含む)「空なら埋める」
&&=左が真「値があるときだけ加工する」
// 設定に既定値を当てる
function init(options = {}) {
  options.retry ??= 3;
  options.timeout ??= 5000;
  options.debug ??= false;      // false を渡しても消えない
  return options;
}

// ||= だと false や 0 が上書きされてしまう
options.debug ||= false;        // ← 渡した true が消えることはないが、0 は危険

分割代入

const user = { name: "田中", age: 30 };

const { name, age } = user;                 // 取り出す
const { name: n2 } = user;                  // 別名
const { city = "未設定" } = user;            // 既定値
const { name: n3, ...rest } = user;         // 残りをまとめる

const [first, , third = 0] = [1, 2];        // 配列。2 番目は読み飛ばす
console.log(first, third);                  // 1 0

// 宣言済みの変数に代入するときは全体をかっこで囲む
let p, q;
({ p, q } = { p: 1, q: 2 });                // 波かっこが文の先頭に来ると
                                            // ブロックと誤解されるため

// 入れ替え
let a = 1, b = 2;
[a, b] = [b, a];

// 関数の戻り値をそのまま分解する
const [ok, err] = tryParse(text);

スプレッドによる代入

const base = { a: 1, b: 2 };

const copy = { ...base };                   // 浅いコピー
const merged = { ...base, b: 99, c: 3 };    // 後ろが勝つ
const arr = [...[1, 2], 3];                 // [1, 2, 3]

// 条件付きで足す
const flag = true;
const obj = { a: 1, ...(flag ? { b: 2 } : {}) };

// 元を変えずに更新する(React などの状態更新で必須)
const next = { ...state, count: state.count + 1 };

参照の代入

// プリミティブは値がコピーされる
let a = 1;
let b = a;
b = 2;
console.log(a);         // 1

// オブジェクト・配列は参照がコピーされる
const o1 = { n: 1 };
const o2 = o1;
o2.n = 99;
console.log(o1.n);      // 99   同じ実体

// 複製する
const o3 = { ...o1 };            // 浅い
const o4 = structuredClone(o1);  // 深い

// 関数の引数も同じ
function f(obj) { obj.n = 0; }   // 呼び出し元のオブジェクトが変わる
function g(obj) { obj = {}; }    // 呼び出し元には影響しない(名前の付け替え)

const への代入

const x = 1;
// x = 2;                  // TypeError: Assignment to constant variable.

const arr = [1];
arr.push(2);               // OK(中身の変更は許される)
arr[0] = 9;                // OK
// arr = [];               // TypeError

// 中身も変えたくないなら freeze
const frozen = Object.freeze({ a: 1 });
frozen.a = 2;              // 黙って無視(strict モードでは TypeError)

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 代入演算子
  2. 算術演算子
  3. 比較演算子

最近更新/作成されたページ