目的・意図
Reactを学ぶためにはJavaScriptのアロー関数と構造分解(分割代入)の理解が必須であることに気づいたので深掘りする。
JavaScriptのアロー関数とは?
ES2015のアロー関数の登場により、「簡潔に」「短く」関数を書けるようになった!
const 関数の変数名 = (引数) => 戻り値;
同じアロー関数でも、丸括弧や波括弧、returnの省略できる・できないのパターンがある。
(ここが理解できてなくて、文を見るたび毎回混乱してた・・・)
const square = x => x * x;
const add = (a, b) => a + b;
const createPerson = (name) => ({ name });
🤔アロー関数をなぜ使うの?
- アロー関数を使うと短く簡潔になるため。特にコールバック関数や高階関数を使用するときに可読性がアップ!
- 親のスコープの
this
を保持するため。特にReactコンポーネント内のメソッド定義においてthis
のバインディングを意識する必要がなくなる
- コールバック関数とは?
- 他の関数に引数として渡され、特定のタイミングで呼び出される関数のこと
- 高階関数とは?
- 他の関数を引数として受け取ったり、関数を戻り値として返したりする関数のこと
- 配列の
map
メソッドは、コールバック関数を引数として受け取り、各要素に対してその関数を適用する
- 配列の
- 高階関数は、元の関数の中身を上書きせずに、新しい値やオブジェクト(または配列)として処理した結果を返すことが多い
- これにより、元のデータを保持しつつ、新しいデータを生成できる
- 他の関数を引数として受け取ったり、関数を戻り値として返したりする関数のこと
-
this
を保持とは?-
this
は関数が呼び出された時の文脈を指し、その関数がどのオブジェクトに属しているかを示す
-
function Person() {
this.age = 0;
setInterval(function() {
// ここでの`this`はグローバルオブジェクトである`window`を指す
// そのため`this.age`が`undefined`となり、エラーになる
this.age++;
console.log(this.age);
}, 1000);
}
const person = new Person(); // `this`が期待通りに動作しない
function Person() {
this.age = 0;
const self = this; // `this`を保持ための変数が必要になる
setInterval(function() {
self.age++; // `self`を使って`this`を参照
console.log(self.age);
}, 1000);
}
const person = new Person(); // ここでは`self`を使って期待通りに動作する
function Person() {
this.age = 0;
setInterval(() => {
this.age++; // ここでの`this`はPersonを指す
console.log(this.age);
}, 1000);
}
const person = new Person(); // `this`が期待通りに動作する
🤔アロー関数ってどうやって書くんだっけ?
なかなかアロー関数の書き方になれなかった私。
通常の関数の書き方から、アロー関数に変化させることで覚えられた。
function 関数名(引数) {
return 戻り値;
};
- まずアロー関数になると
function
が不要になる - そして関数名だった部分を
const
変数名として定義する- そうすることで、ただのスコープ内における関数から、代入ができる変数に昇格!
- あとは前述した省略のパターンを覚えておけばOK
- そうすることで、ただのスコープ内における関数から、代入ができる変数に昇格!
const 関数の変数名 = (引数) => {
return 戻り値;
};
※厳密には(引数) => {return 戻り値;};
の部分がアロー関数であるが、constとして変数に格納して使用することが多い。
JavaScriptの構造分解(分割代入)とは?
ES2015の分割代入により、オブジェクトや配列から値を簡単に取り出せるようになった!
const { プロパティ名1, プロパティ名2 } = オブジェクト名;
const [ 変数1, 変数2 ] = 配列名;
const myProfile = {
name: 'はな',
age: '3',
};
// 従来であればドット繋ぎで呼び出す
const messages = `私の名前は${myProfile.name}です。${myProfile.age}です。`
// 分割代入する場合、
// 左側に取り出した後につけたい変数名、
// 右側にどこから取り出すか(オブジェクト名あるいは配列名)を定義
const { name, age } = myProfile;
// 分割代入すると定義した変数で直接呼び出せるようになる
const bunkatuMessages = `私の名前は${name}です。${age}です。`
const ['メグ', 'ジョー', 'ベス', 'エイミー'] = mySister;
// 従来であればインデックスを使って要素を取り出す
const Messages = `私の姉妹の名前は${mySister[0]}, ${mySister[1]}, ${mySister[2]}, ${mySister[3]}です`;
// 配列を分割代入する場合、任意の変数名をつける(順番に気を付けて取り出す形)
const [meg, jo, beth, amy] = mySister;
const bunkatuMessages = `私の姉妹の名前は${meg}, ${jo}, ${beth}, ${amy}です`
😊構造分解(分割代入)の値を変更したときの挙動
- 構造分解(分割代入)は左辺で任意の名前をつけた変数に対して右辺のオブジェクトや配列を分解して代入を行っている
- そのため別個の変数として新しく定義しているので、右辺がプリミティブ値だった場合には、元のオブジェクトや配列を変更した場合でも、影響を受けない
- ただし、右辺がオブジェクトだった場合には、新しい変数が作成されつつも(複数の変数が同じオブジェクトを指し示すことができるため)一方の変数を通じてオブジェクトを変更すると、他方の変数にもその変更が反映されてしまうので注意する
コンピューター科学において、オブジェクトは識別子によって参照可能なメモリー内の値です
→つまり、オブジェクト自体が変数に直接格納されるのではなく、そのオブジェクトが格納されているメモリのアドレス(参照)が変数に格納されている
const primitiveObj = { num: 50, str: 'hello', bool: true };
const { num, str, bool } = primitiveObj;
// 元のオブジェクトのnumと構造分解したnumは同じ「50」の値をもつ
console.log('数値:', num === primitiveObj.num); // true
// 元のオブジェクトのnumを「100」に変更
primitiveObj.num = 100;
// 構造分解したnumは影響を受けず「false」となる
console.log('数値は同じですか?', num === primitiveObj.num); // false
console.log('元の数値num', primitiveObj.num); // 100
console.log('分割代入の数値num', num); // 50
const referenceObj = { obj: { x: 1 }, arr: [1, 2, 3] };
const { obj, arr } = referenceObj;
// 元のオブジェクトのobjと構造分解したobjは同じ「obj: { x: 1 }」の値をもつ
console.log('オブジェクト:', obj === referenceObj.obj); // true
// 元のオブジェクトobjを「{ x: 2 }」に変更
referenceObj.obj.x = 2;
// 構造分解したobjも影響を受けており「true」となる
console.log('オブジェクトは同じですか?', obj === referenceObj.obj); // true
console.log('元のオブジェクトobj:', referenceObj.obj.x); // 2
console.log('構造分解のobj', obj.x); // 2
🤔(心の声)なんで分割代入っていうの?名前から直感的に意味が理解できない
現在は、「分割代入」ではなく「構造分解」に改名されていた。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring
@juner ありがとうございます🙇♀️
🐻:なんで分割代入っていうんだろ?
- 分割した変数に対してオブジェクトあるいは配列の値を代入する
- その変数を使って呼び出すことで、従来は
オブジェクト名.値
、配列名[0]
のように呼び出していたのが値
あるいは配列用に新しくつけた変数名
で呼び出せるようになった
つまり、
- 複数の中身があるものに対して、複数の変数名をつけてあげる
- この操作を一つの式で行うので「分割で代入すること」が起こっているのか
🐻:あなたが頭の柔軟な小学生なら、分割代入ではなくてどんな名前をつける?
🐰:「簡単呼び出し!」
🐻:そうそう!!
「代入」よりも「取り出し」への利点に目がいっちゃうので直感的じゃないって思ったんだ!
つまり、分割代入という名前は、オブジェクトや配列の中身を分けて新しい変数に代入するというプロセスを表しているが、実際には値を取り出すことに重点が置かれているのか!
🐰:値を取り出すことだけに重点が置かれているのか?
🐻:分割代入による例を見てみよう!
const user = {
name: 'Alice',
age: 30,
email: 'alice@example.com'
};
// 分割代入を使うことで、`name`と`age`を使っていることがわかる
const { name, age } = user;
console.log(name); // 'Alice'
console.log(age); // 30
const userProfile = {
user: {
name: 'Alice',
age: 30,
address: {
city: 'Tokyo',
postalCode: '100-0001'
}
},
preferences: {
theme: 'light',
notifications: true
}
};
// ネストされたオブジェクトを分割代入
const { user: { name, address: { city } } } = userProfile;
console.log(name); // 'Alice'
// ネストしたオブジェクトも`city`という変数一つで呼び出せる
console.log(city); // 'Tokyo'
const user = {
name: 'Bob'
// ageプロパティが存在しない
};
// 分割代入でデフォルト値を設定
const { name, age = 25 } = user;
console.log(name); // 'Bob'
console.log(age); // 25 (デフォルト値)
🐰:以下のパターンがありそうだね
- オブジェクトや配列からどの値を使っていて使っていないのか明示的
- ネストを含めて変数一つで呼び出せるので取り出しが簡単・簡潔になる
- デフォルト値を設定できるのでエラーを起こすリスクを減らせる
🐻:公開されているAPIを利用しに行ったとき、1つのエンドポイントに複数の値が入っていて、その中から自分のシステムで使う値を明示的に示せたり、ネストされているときも簡潔にできたり、反対にAPIレスポンスには含まれてない値をデフォルト値で設定したりで使えそうって思ったわ
🐻:出た結論
分割代入とは、「値を簡単に取り出す」「明示的になる」「堅牢になる」メリットがあり、その手段として「分割で変数に代入している」
だから直感的に意味不明って思ったんだとスッキリした。
⚠️オブジェクトの省略記法と似てるけど別物なので間違えるな!
JavaScriptでは、プロパティ名と変数名が一致していたら、プロパティ名を省略できる。
(これをプロパティのショートハンドという)
const name = 'はな';
const age = 3;
// 通常の書き方
const myProfile = {
name: name,
age: age
};
// 省略記法
const myProfile = {
name, // 右側のnameは変数名を参照
age // 右側のageも同様
};
これが1行で書かれていると、一見「え?分割代入!?」と思ってしまうがこれは省略記法の話なので違う。
左側に書いているか、右側に書いているかで意味が別物なので気をつけよう。
const myProfile = { name, age };
🤔プロパティ名とその値が自然に一致することってあるのか?
この話を聞いた時に、name
はname
、age
はage
でなぜ一致するんだ?と思った。
よくよく調べたら、省略記法を使うには、まずその変数が事前に宣言している必要があった。
const name = 'はな'; // 変数を宣言
const myProfile = {
name // 省略記法を使用
};
const myProfile = {
name // これはエラー
};
なので初めは、
「プロパティ名とその値が一致していたら、プロパティ名を省略できる。」
と思っていたが、正しくは、
「プロパティ名と事前定義した変数名が一致していたら、プロパティ名を省略できる。」
ということだった。
あとは、当たり前かもしれないけど、配列にはプロパティという概念はないため、
配列に対してプロパティのショートハンドは起こり得ない。
const mySisters = ['メグ', 'ジョー', 'ベス', 'エイミー'];
// 配列ではプロパティのショートハンドは適用されない