371
424

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ちょっと高度なJavaScriptの話

Last updated at Posted at 2021-01-15

はじめに

今回は、初心者から中級者になるためのJavaScriptを学んでいきましょう。

JavaScriptを全く勉強したことがないという人には以下の記事がおすすめです。

【JavaScriptの超基本】ファイルのインポートやエクスポートについて簡単に解説

【JavaScriptの超基本】コールバック関数について簡単に解説

【JavaScript】プリミティブ型とオブジェクト型を理解したい

それでは、頑張っていきましょう。

配列やオブジェクトのちょっと便利な構文

それではJavaScriptのちょっと便利に構文について学んでいきましょう。

オブジェクトのkeyとvalueに変数を指定

JavaScriptではオブジェクトのkeyとvalueに変数を指定することができます。以下のコードです。

.js
const keyName = "bar";
const baz = 3
const obj = {foo: 1, [keyName]: 2, baz: baz};
console.log(obj)

{ foo: 1, bar: 2, baz: 3 }

上記の例のように、オブジェクトのkeyに変数を指定するときは[]で囲い、valueに変数を指定するときはそのまま代入します。

keyNameの値であるbarがオブジェクトのkeyに、bazの値である3がオブジェクトのvalueになっていることが分かると思います。

プロパティ名のショートハンド

次は、プロパティ名のショートハンドと呼ばれる書き方です。

変数を{}で囲うことで、変数名をkey・値をvalueに持つオブジェクトを生成することができます。

.js
const organization = 'unreact';
const obj = {organization};
console.log(obj);

{ organization: 'unreact' }

プロパティ名のショートハンドという書き方はES2015から導入された構文で、React開発においても頻繁に使用されます。ぜひともマスターしておいて下さい。

配列の分割代入

他の言語でも頻繁に使われますが、JavaScriptにおいても配列の分割代入は可能です。

配列の分割代入で値を受け取る際は、受け取る側も配列にしておきます。

.js
const [n, m] = [1, 2];
console.log(n, m);

1 2

上記の例では、nに配列の1つ目の要素である1が代入され、mに配列の2つ目の要素である2が代入されます。

JavaScriptの配列には順番が存在することを考えれば、値を受け取る側の順番が、値を与える側の順番に対応するというのは当然の挙動ですよね。

オブジェクトの分割代入

当然ですが、オブジェクトの分割代入も可能です。

.js
const onj = {organization: 'unrect', age: 1};
const {organization, age} = onj;
console.log(organization, age);

unrect 1

オブジェクトの分割代入においては、受け取る側に{}を準備して、その中にオブジェクトのkey名を書きます。

そのkey名に対して、オブジェクトの中で対応するvalueがそれぞれ格納されていきます。

つまり上記の例では、organaizationというオブジェクトのkeyに対応するunreactというvalueが、受け取る側の{}の中のkey名であるorganaizationに代入されることになります。

配列と違いオブジェクトには順番が存在しないため、分割代入においてはkey名で指定することが必要になります。

上記の例では、オブジェクトの分割代入を行った際の変数名が、オブジェクトのkey名になってしまっています。

以下のようにすることで、分割代入で生成する変数名を任意の名前にすることができます。

.js
const onj = {organization: 'unrect', age: 1};
const {organization: group, age} = onj;
console.log(group, age);

unrect 1

上記の例では、organization: groupの部分で、オブジェクトのorganaizationに対応するvalueであるunreactを、groupという変数名で受け取っています。

また以下のようにすれば変数名に初期値を割り当てることができます。

.js
const onj = {age: 1};
const {organization: group = 'unreact', age} = onj;
console.log(group, age);

上記の例では、オブジェクトにはorganaizationが存在しないため何もしなければgroupにはundefinedが格納されます。

それを防ぐために、='unreact'とすることで初期値を設定しています。

オブジェクトの分割代入はReactでpropsを受け取る際などに頻繁に利用されます。ぜひとも覚えておきましょう。

スプレッド構文

それでは次にスプレッド構文をまとめていきます。スプレッド構文とは...をつけることでオブジェクトや配列の中身を展開する構文です。

.js
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5, 6];

console.log(arr2);

const obj1 = {organaization: 'unreact'};
const obj2 = {...obj1, age: 1};

console.log(obj2);

[ 1, 2, 3, 4, 5, 6 ]
{ organaization: 'unreact', age: 1 }

配列のスプレッド構文はES2015から導入され、オブジェクトのスプレッド構文はES2018で導入されました。

使用例としては、オブジェクトや配列をコピーする際などに用いられます。

具体例を解説する前に、値渡しと参照渡し(のようなもの)について学んでおきましょう。

参照渡し(のようなもの)

配列やオブジェクトをそのまま代入すると値渡しではなく参照渡し(のようなもの)が行われます。

どういうことか分からないと思うので、具体例で解説します。以下が参照渡し(のようなもの)が行われる例です。

.js
const arr1 = [1, 2, 3];
const arr2 = arr1;
arr2.push(4);

console.log(arr1);

[ 1, 2, 3, 4 ]

上記の例のように、arr2.push(4)を行ってarr2に対して変更を行うと、arr1にも変更が加えられます。

なぜこのような挙動をするのかというと、オブジェクトや配列は参照渡し(のようなもの)が行われるからです。

正確には、JavaScriptには参照渡しは存在せず、全ての値に値の参照が渡されます。それは、プリミティブでもオブジェクトでも代わりはありません。ただ、変数aにプリミティブ値1を渡し、変数bに変数aを代入した後に、変数bにプリミティブ値10を渡しても、変数aにはプリミティブ値1が入っているという挙動が発生するのですが、これは変数bに格納されている参照がプリミティブ値10に置き換わっただけです。これは値渡しのように見えますが、ただ格納されている参照が移し変わっただけというわけです。

一方、オブジェクトのプロパティアクセスにより変数の再代入を行った場合、代入を行った対象が変数自体ではなく、変数に格納されているオブジェクトの参照に対して行われるため、参照渡しのような挙動が発生します。

値渡し

上記の参照渡しを回避するためには、arr2のために新たに配列のメモリ領域を確保する必要があります。

そのために、下記のようにスプレッド構文を用いることがよくあります。

.js
const arr1 = [1, 2, 3];
const arr2 = [...arr1];
arr2.push(4);

console.log(arr1);

[ 1, 2, 3 ]

このようにすれば、値をコピーして渡すことができます。const arr2 = [...arr1]の部分で新たに配列を生成し、そのメモリ領域を確保した後に、その新たに確保したメモリ領域の参照をarr2に渡しています。

シャローコピーについて

先程配列をスプレッド構文でコピーして値渡しを行う方法について解説しましたが、この方法は少し問題を抱えています。

というのも、スプレッド構文による値渡しは、一段回までの深さしか行えないからです。

つまり、オブジェクトや配列がネストされていた場合は、そのネストされたオブジェクトや配列に対しては参照渡しを行ってしまいます。

以下の具体例で見てみましょう。

.js
const obj1 = {
  organaization: "unreact",
  nest: { group: "react" },
};
const obj2 = {...obj1};
obj2.nest.group = 'React';

console.log(obj1);

{ organaization: 'unreact', nest: { group: 'React' } }

上記の例では、const obj2 = {...obj1}の部分でスプレッド構文による値渡しを行っていますが、ネストされたオブジェクトである{ group: "react" }に関しては参照渡しが行われてしまいます。

そのため、obj2.nest.group = 'React'を行いobj2に対してネストされたオブジェクトの値を変更すると、obj1のネストされたオブジェクトの値まで書き換えられてしまいます。

このシャローコピーを回避するために、いくつかの方法があります。

最も有名なのはJson.parse/stringfyを使う方法でしょうか。

ディープコピーを行う

それでは、このシャローコピーを回避してディープコピーを行う方法について解説していきます。

最も有名なのは、以下のようにJson.parse/stringfyを行う方法です。

.js
const obj1 = {
  organaization: "unreact",
  nest: { group: "react" },
};
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.nest.group = "React";

console.log(obj1);

{ organaization: 'unreact', nest: { group: 'react' } }

JSON.parse(JSON.stringify(obj1))の部分で、オブジェクトを一度JSON文字列に変換した後、再びJavaScriptのオブジェクトに変換しています。

この方法はお手軽で高速にディープコピーを行うことができますが、強引であるがゆえに弊害もあります。

下記の記事に、弊害について分かりやすくまとめてありました。

JavaScriptのDeepCopyでJSON.parse/stringifyを使ってはいけない

かいつまんで説明すると、この方法はオブジェクトのプロパティにDate関数undefinedが存在する場合に上手く動きません。

このような問題があるため、現環境でディープコピーを行う際はLodashというライブラリのcloneDeep()を用いる方法が推奨されています。

Lodashでディープコピー

Lodashを使えば簡単にディープコピーを行うことができます。

.js
import _ from "lodash";

const obj1 = {
  organaization: "unreact",
  nest: { group: "react" },
};
const obj2 = _.cloneDeep(obj1);
obj2.nest.group = "React";

console.log(obj1);

{ organaization: 'unreact', nest: { group: 'react' } }

const obj2 = _.cloneDeep(obj1);の部分でディープコピーを行っています。凄く簡単ですよね。

これからディープコピーを使用する際は積極的に使っていきましょう。

##関数型プログラミングっぽく書こう
JavaScriptはかなり関数型プログラミングのパラダイムに対応しています。

関数型プログラミングとは、先行する式の評価を後続の式に適用し、それをつなげていくことで最終的な評価値を得るプログラミング手法です。

ifやfor文による制御構造による手続き書き連ねていく命令型プログラミングのパラダイムとは異なり、関数型プログラミングはy = f(x)のような数学的な式であるべき状態を宣言します。

React開発ではこの関数型プログラミングのような書き方が非常に好まれるため、積極的に使用していく必要があります。例えば、ReactのJSXの中ではを使用することができず、全てを用いてプログラムを記述する必要があります。

ifやforのようなではなく、値を返すを組み合わせてあるべき状態を宣言していく、そんな宣言的(Declarative)な書き方を学んでいくことで、少しレベルアップできるはずです。

それでは頑張っていきましょう。

ショートサーキット評価

最初にショートサーキット評価、またの名を短絡評価についてまとめていきます。

短絡評価とは&&||などの論理演算子が左から右に評価される性質を利用して、右辺が評価されるかどうかを左辺に委ねる方法です。

&&を使用した場合、左辺がtruthyのとき右辺を返し、||を使用した場合は左辺がfalsyなときに右辺を返します。

以下で具体例を見ておきましょう。

.js
const organaization = "" || "UnReact";
console.log(organaization);

UnReact

上記の例では、 "" || "UnReact"の部分でショートサーキット評価を行っています。

||は左辺がfalsyのときに右辺を返し、左辺がtruthyのときは左辺を返す(短絡する)式です。

空文字はfalsyな値のため、右辺が返ってきてorganaizationに代入されています。

ちなみに、JavaScriptにおいてfalsyな値はfalse0NaNnullundefined''(空文字)のみで、それ以外は全てtruthyな値になります。

以下の例のように、ショートサーキット評価は繋げて書くことができます。

.js
const organaization = 'Uniqlo' && 'Softbank' && 'Toyota' && 'UnReact';
console.log(organaization);

UnReact

&&は値が左辺がtruthyなとき右辺に評価を委ねる記法です。そのため、上記の例では一番右側まで評価されてUnReactが返ってきます。

このショートサーキット評価はif文の代わりに使うことができます。

ifなどのと異なり、値を返すなのでReact開発で頻繁に利用されます。

###Nullish Coalescing
次はNullish Coalecing、通称Null合体演算子をやっていきましょう。

Nulllish Coalescingはショットサーキット評価の||と似ています。||は、左辺がfalsyな値のときに右辺を評価するという記法ですが、Nullish Coalescingは??を用いて左辺がnullまたはundefinedのときに右辺を評価するという記法です。

具体例は以下になります。

.js
const organaization = null ?? "UnReact";
const language = undefined ?? "React";
console.log(organaization);
console.log(language);

UnReact
React

このように??を用いて、左辺がnullまたはundefinedのときに右辺を評価するという記法がNullish Coalescingです。

ちなみに、上記のパターンだとNullish Coalescingの??の部分をショートサーキット評価の||に変えても特段違いはありません。

しかし、ショートサーキット評価の||は0や空文字が左辺に入った際も右辺を評価してしまうため、思わぬバグを生む可能性があります。

そのため、nullまたはundefinedのときに右辺を評価するということを明示的に示したい場合にNullish Coalescingを使用することになります。

ちなみにNullsish CoalescingはES2020で追加されたものなので、Node.js 14.0以降で実行することになります。

Optional Chaining

それでは、Optional Chainingについて解説していきます。

こちらもES2020から追加された記法で、端的に書くならオブジェクトに対するプロパティアクセスを用いた際に、各参照が正しいかどうかを明示的に確認せずアクセスすることができるものです。

もしも途中のプロパティが存在していなかったら、式が短絡してundefinedが返ってきます。

この説明では何がなんだか分からないと思うので、きちんと順を追って解説していきます。

まず、前提としてオブジェクトの存在しない要素に対してプロパティアクセスを行うとundefinedが返ってきます。

.js
const obj = { organization: "UnReact" };
console.log(obj.employee);

undefined

そして、この返ってきたundefinedに対してプロパティアクセスを行うとエラーが発生します。

.js
const obj = { organization: "UnReact" };
console.log(obj.employee.age);

at ModuleJob.run (internal/modules/esm/module_job.js:110:37)
at async Loader.import (internal/modules/esm/loader.js:179:24)

この前提を踏まえた上で、次のようなケースを考えます。

.js
const arr = [
  { organaization: "UnReact", employee: { name: "poocomaru", age: 23 } },
  { organaization: "Softbank", employee: null },
];

arr.map((n) => {
  console.log(n.employee.age);
});

at ModuleJob.run (internal/modules/esm/module_job.js:110:37)
at async Loader.import (internal/modules/esm/loader.js:179:24)

上記のコードでは、会社についての情報を格納したオブジェクトの配列を作成して、それをmapで回しながらプロパティにアクセスしています。

このコードは、2つ目のオブジェクトに対してのn.employee.ageを行う部分でエラーが発生してしまいます。

というのも、n.employeeの実行結果がnullであり、そのnullに対してプロパティアクセスを行ってしまったために発生するエラーです。

Optional Chainingの実装以前はifによる条件分岐でnullやundefinedチェックを行い、エラーを回避していました。

しかし、Optional Chainingを使用すれば、以下のコードのように簡単にエラーを回避することができます。

.js
const arr = [
  { organaization: "UnReact", employee: { name: "poocomaru", age: 23 } },
  { organaization: "Softbank", employee: null },
];

arr.map((n) => {
  console.log(n?.employee?.age);
});

23
undefined

変更が加わったのはn?.employee?.ageの部分です。通常のプロパティアクセス.から、Optional Chainingである?.に変更しています。

この変化により、nullに対してのプロパティアクセスが短絡されてundefinedが返ってくるようになり、nullに対してプロパティアクセスを行ったが故のエラーが発生しなくなります。

終わりに

今回はちょっと高度なJavaScriptの文法について取り扱いました。

少しでも皆様のお役に立てれば幸いです。

ここまで読んで頂きありがとうございました。

371
424
13

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
371
424

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?