3
3

More than 1 year has passed since last update.

【解説編】モダンJavaScript構文ビンゴ 〜あなたはモダンなJavaScriptをどこまで理解できていますか?〜

Last updated at Posted at 2022-07-05

モダンJavaScript構文ビンゴ

IE11お疲れ様でした記念!

皆様、モダンJavaScriptの時代がようやくやってきました。

というわけでまずは、こちらの「モダンJavaScript構文ビンゴ」を無心でやってくださいませ。
パッとみて「理解できる」と思ったらチェックできます。

どうでしたか?

割とできました?
それとも全然チェックできませんでした?

個人的には、これ1つでもチェックできる人と一緒に仕事したいです。

というわけで、回答編というか解説編です。

[...items]

...items

これは「スプレッド構文」と言われるもので、itemsの中身が[1, 2, 3]だった場合に、...items1, 2, 3に置き換えられます。
これはどういうことかというと、つまり、

const newItems = [...items];

が、

const newItems = [1, 2, 3];

として評価されるということです。

これはとても便利で、例えば次のような関数があったとき、スプレッド構文で引数を指定できます。

function myFunction(x, y, z) {
    return x + y + z;
}

const items = [1, 2, 3];
const result = myFunction(...items); // 6

以前はapplyメソッドを使っていたので、だいぶすっきりします。

applyを使った以前の書き方
const result = myFunction.apply(null, items);

スプレッド構文を使って配列の結合や要素の追加ができます。

const items = [1, 2, 3];
const newItems = [0, ...items, 4, 5]; // [0, 1, 2, 3, 4, 5]
const newItems2 = [...newItems, 0, ...items]; // [0, 1, 2, 3, 4, 5, 0, 1, 2, 3]

尚、この時に展開される要素の値は元の要素の複製です。しかし、要素が構造を持っている場合、子要素までは複製されないので注意が必要です。
言い換えると、変数自体は複製されますが、変数の参照先までは複製されません。

つまり、こういうことです。

const items = [ 5, { num1: 1 } ];
const newItems = [...items];

newItems[0] = 6;
console.log(items[0]); // 5

newItems[1].num1 = 2;
console.log(items[1].num1); // 2

{...org, c:3}

せっかくなので続けてこちらを。これもスプレッド構文の一つで、こちらはオブジェクト用のスプレッド構文です。

似たような説明を書くのが面倒なので、MDNより引用します。

let obj1 = { foo: 'bar', x: 42 };
let obj2 = { foo: 'baz', y: 13 };

let clonedObj = { ...obj1 };
// Object { foo: "bar", x: 42 }

let mergedObj = { ...obj1, ...obj2 };
// Object { foo: "baz", x: 42, y: 13 }

同じプロパティ名は上書きしてくれます。

尚、Object.assign()と似たような動きに見えますが、Objet.assignはコピー先のオブジェクトのセッターを起動するのに対して、スプレッド構文ではセッターを起動しない事に注意が必要です。

また、複製されるのは配列の時と同じく変数自体であり、その参照先は複製されません。

const obj1 = {
    child1: {
        childnum : 5  
    },
    num1: 5
};

const obj2 = { ...obj1 };

obj2.num1 = 6;
console.log(obj1.num1); // 5

obj2.child1.childnum = 6;
console.log(obj1.child1.childnum); // 6

console.log(obj1.child1 === obj2.child1); // true

[a,b] = arr

続けて、分割代入です。こちらは最近、react等でも頻繁に用いられる為、慣れている人もいるかもしれません。

この書き方をすると、配列の一部を個別の変数に割り当てて取り出す事ができます。

MDNのサンプルが分かりやすいのでそのまま引用します。

const foo = ['one', 'two', 'three'];

const [red, yellow, green] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // "three"

もし、受け取る側の変数リストがコピー元より多い場合はどうなるでしょうか。
これは単にundefinedになります。

const foo = ['one', 'two'];

const [red, yellow, green, blue] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // undefined
console.log(blue);  //undefined

undefinedではなく、規定値を指定することもできます。

let a, b;

[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7

逆に受け取る側の方が少ない場合、単に捨てられます。
しかし、「残り全部を配列で受け取りたい」場合もあるでしょう。
その時は、スプレッド構文を使うことができます。

const [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]

途中をカンマで飛ばして受け取ることもできます。

const items = [1, 2, 3];

const [a,,b] = items;
console.log(a); // 1
console.log(b); // 3

関数の戻り値が配列の場合に、それを個別の変数で受け取ることができて便利です。

function useState( value = null )
{
  return [ 
    () => value, 
    v => { value = v } 
  ]
}

const [getA, setA] = useState(10);
console.log(getA()); // 10
setA(20);
console.log(getA()); // 20

const [getB, setB] = useState(1);
console.log(getB()); // 1
setB(2);
console.log(getB()); // 2

また、配列と同様にオブジェクトも分割代入できます。

const user = {
    id: 42,
    isVerified: true
};

const {id, isVerified} = user;

console.log(id); // 42
console.log(isVerified); // true

宣言と同時に代入するのではなく、後から代入する場合は、中括弧をブロックだと認識されないよう、丸括弧で囲う必要があるので注意してください。

let a, b;

({a, b} = {a: 1, b: 2});

別名のプロパティへの代入も可能です。

const o = {p: 42, q: true};
const {p: foo, q: bar} = o;

console.log(foo); // 42
console.log(bar); // true

便利ですね。(この辺はどれもMDNからの引用です)

r=>r.json()

すいません、これはちょっと書き方が悪かったかもしれません。
もう少しシンプルな例を挙げると、次のようなものです。

r => r + 1

そう、ラムダ式と言われるものです。
これは、次のものとほぼ同じです。

function(r){
    return r + 1;
}

ですので、次のように変数や定数に代入しておくと、関数として呼び出す事ができます。

const myFunc = (r => r + 1);

console.log(myFunc(10)); // 11

Arrayにはmapやreduceなどの便利な高階関数がありますが、これらは引数に関数を取ります。
そのような場合に、いちいち次のように書かず

const items = [{num1 : 1}, {num1: 2}, {num1: 3}];
const result = items.map(function(item){ return item.num1 });
console.log(result); // [1, 2, 3]

次のように書けます。

const items = [{num1 : 1}, {num1: 2}, {num1: 3}];
const result = items.map(items => item.num1);
console.log(result); // [1, 2, 3]

returnを省略できますし、冗長な「function」定義も不要ですので、見た目に「何をさせたいか」をすっきりと記述できます。

functionと何が違うのかですが、thisの扱いが異なります。
functionでは、thisは「その関数が所属するオブジェクト」になります。
しかしラムダ式では、「そのラムダ式が定義された時点でのthis」を参照します。

この話は少し複雑な話になりますので、興味のある方は調べてみて下さい。

よくわからない人は、「ラムダ式を使う場合、thisは常にその外側のthisを指しているので安心して使えるけど、匿名functionの場合、外側のthisは呼び出されるタイミングによっては違うthisかもしれない」ぐらいに覚えておくと良いでしょう。決してラムダ式のthisの方が優れているというわけではなく、functionのthisの解決方法が必要な場面もあります。

obj?.getX()

?.はオプショナルチェーン演算子といって、その直前のオブジェクトの参照がnull又はundefinedの場合に、エラーにならずにそこで短絡され、undefinedが返ります。

なので、これまで次のように書いていたのを、

let result;
if ( obj ) {
    result = obj.getX();
}

次のように書けます。

const result = obj?.getX();

これがもし深いところにあるオブジェクトだと、この素晴らしさに感動することでしょう。

const result = obj.first?.second?.third;

ところで、「関数が存在する時には呼び出す」という時には、次のように書けます。
これはいきなり見るとぎょっとしますが、便利なものです。

const result = obj.methodA?.();

methodAが存在しない場合、resultにはundefinedが入り、例外は発生しません。

a ?? 0

この勢いでこちらも行ってみましょう。
これは「null合体演算子」と呼ばれるもので、左辺がnull又はundefinedの時、右辺を返します。
nullでもundefinedでもなければ、左辺そのものを返します。

つまり、nullishの時の規定値を指定できるわけです。

次のように書けばいいじゃないかと思われるかもしれません。

const result = a || 0;

しかし、もし aが''で、それは有効値として扱いたい場合であっても、||の左辺は論理値として評価され、''はfalseとして評価される為、0が返ってしまいます。

const result = a ?? 0;

上記の書き方ならば、aがnull又はundefinedの時だけ、右辺が返ります。
より安全なコードと言えるでしょう。

また、null合体演算子は前述のオプショナルチェーン演算子と組み合わせることで、よりシンプルな記述が可能となります。

const result = items.map(item => item.numA); /// itemsがnullの時例外が発生

上記の処理において、itemsがnullだった場合、例外が発生してしまいます。

itemsがnullの時はresultに空の配列が返るように書きたい時、null合体演算子とオプショナルチェーン演算子を組み合わせて次のように書く事ができます。

const result = items?.map(item => item.numA) ?? [];

上記を見て、「なんだか呪文のようだ」と思われるでしょうか。
しかし、null合体演算子もオプショナルチェーン演算子も使わずに上記を書こうとしたら、どれだけ長ったらしいコードになるか想像してみると、「これで良い」と思えるようになるかもしれません。

もちろん、上記の書き方に慣れていない人の為に敢えて冗長な書き方をするという選択肢もあると思います。

`${year}年`

この書き方は、テンプレートリテラルと呼ばれる書き方です。シングルクォートではなくバッククォートで囲うことにご注意ください。

文字列の中に変数を${変数名}の形式で埋め込むことができます。

これまで次のように書いていた場合、

const result = d.getFullYear() + '' + d.getMonth() + '' + d.getDate() + '';

次のように書けます。

const d = new Date();
const result = `${d.getFullYear()}${d.getMonth()}${d.getDate()}日`;
console.log(result);  // "2022年6月5日"

この例だと良さが分からないかもしれませんが、改行含みのHTMLテキストなどを定義する時等、便利に記述できます。

async/await

fetch関数のようなPromiseを返す非同期関数の呼び出しの際に、then()を使わずにawaitを使って「同期的に見える」書き方ができます。

then()を使った従来の記述
fetch(url)
	.then( response => response.json() )
	.then( data => {
		// dataの処理
		console.log(data.Message);
	});
awaitを使った同期的に見える記述
const response = await fetch(url);
const data = await response.json();
// dataの処理
console.log(data.Mesage);

詳しくは以前書いた記事などを参考にどうぞ。

yield i

最後に控えるは、ジェネレータ関数です。

以前書いた記事から抜粋します。

ジェネレータとは、イテレータを生成する為の仕組みです。
イテレータとは、配列のような繰り返し構造を持つデータにアクセスする為の仕組みです。

ジェネレータは、関数名の後ろに*を付け、内部でyieldを実行することで定義することができます。

function* mygenerator(){
  let count = 0;
  count++;
  yield count;
  count++;
  yield count;
  count++;
  yield count;
}

このmygeneratorは、次のように使います。

for (const i of mygenerator()) {
    console.log(i);
}

ログには次のように出力されます。

1
2
3

これは、次の処理と同じ結果です。


for (const i of [1, 2, 3]) {
    console.log(i);
}

ジェネレータがどのように働くか、理解できたでしょうか。

mygeneratorの中でyieldが呼ばれると、mygeneratorは現在のcountを返して一旦処理を停止します。そして、forループから次の値を請求されると、停止した所から処理を再開し、また次のyieldで、値を返します。

mygeneratorの最後まで実行したら、ループ(イテレーション)を終了します。

このような機能を「継続」と呼び、ジェネレータをイテレーション(配列データの繰り返し処理)の為ではなく、コルーチン(処理を一時的に止めたり再開したりする機能)として利用することも可能です。

まとめ

以上、モダンJavaScriptビンゴの回答編でした。

上記の構文は、最新のメジャーなブラウザで利用可能です。
そう、Chromeでも、Edgeでも、Firefoxでも、Operaでも、Safariでも、AndroidでもiOSでも、です。
それらのブラウザであっても、もちろん古いバージョンだと動かなかったりしますが…。

ターゲットブラウザの確認はしっかり行った上で、ぜひ使い倒していきましょう。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3