iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

JavaScriptの基礎と向き合う

よろしくお願いします!エンジニアのideです。 こちらはアドベントカレンダー23日目の記事になります!

今年は体のあちこちに痛みを感じることが多かったので、来年こそは健康に過ごせる一年になるといいなと願っています。

さて、今回は今年やってきたことを振り返るのもいい機会だなと思い、特に触れる機会が多かったJavaScriptについてまとめてみることにしました。
結果的に基本的な内容が中心となりましたが、改めて理解を深める良いきっかけになったので、これはこれでよしとしています!

それではよろしくお願いいたします!

[目次]

テンプレート文字列

` を使って文字列を囲むことで、${} の中に変数や式を埋め込むことができます。

const name = "hoge";
const age = 38;
console.log(`${name}さんの年齢は${age}歳ですね。`); 
// hogeさんの年齢は38歳ですね。

スプレッド構文

スプレッド構文は、配列やオブジェクトを展開するために使用します。
配列の場合、同じ値が含まれていてもそのまま追加されます。
オブジェクトの場合は、同じキーが存在する場合、後に書かれた値で上書きされます。

// 配列
const arr1 = [1, 2, 3];
const arr2 = [3, 4, 5];
const arr = [...arr1, ...arr2];
console.log(arr); 
// [1, 2, 3, 3, 4, 5]

// オブジェクト
const obj1 = { name: "tomato", price: 100 };
const obj2 = { name: "orange" };
const obj = {...obj1, ...obj2};
console.log(obj); 
// {name: 'orange', price: 100}

分割代入

分割代入は、配列やオブジェクトの要素を簡単に変数に代入できる構文です。
配列の場合、要素を順番に変数に割り当てる.
オブジェクトの場合、プロパティを変数に割り当てる.

// 配列
const fruits = ["apple", "banana", "cherry"];

const [first, second, third] = fruits;

console.log(first);  // "apple"
console.log(second); // "banana"
console.log(third);  // "cherry"

// オブジェクト
const item = { name: "tomato", price: 100 };
const { name, price } = item;

console.log(name); // tomato
console.log(price); // 100

別名の変数に代入することも可能です。

const item = { name: "tomato", price: 200 };
const { name: itemName, price: salePrice } = item;

console.log(itemName);
// tomato
console.log(salePrice);
// 200

プロパティ名の短縮記法

キー名と変数名が同じ場合、簡潔に書くことができます

const name = "tomato";
const price = 100;
const item = { name, price };

console.log(item); // { name: "tomato", price: 100 }

オプショナルチェーン

オブジェクトや配列のプロパティやメソッドにアクセスする際、そのプロパティが存在しない場合でもエラーを発生させず、undefined を返します。

// 配列
const fruits = ["apple", "ringo"];
console.log(fruits?.[0]); // "apple"
console.log(fruits?.[2]); // undefined

// オブジェクト
const sale = { item: { name: "tomato" } };
console.log(sale.item?.name); // tomato
console.log(sale.cost?.name); // undefined

nullish(??)とfalsy(||)チェック

  • nullish(??)チェック

null または undefined の場合にのみ、右辺の値を返します。

const value1 = null ?? "hoge";
console.log(value1); // "hoge"

const value2 = undefined ?? "hoge";
console.log(value2); // "hoge"

const value3 = 0 ?? "hoge";
console.log(value3); // 0

const value4 = "" ?? "hoge";
console.log(value4); // ""
  • falsy(||)チェック

    以下のfalsyな値の場合に右辺の値を返します。

    • false
    • 0
    • "" (空文字列)
    • null
    • undefined
    • NaN
const value1 = null || "hoge";
console.log(value1); // "hoge"

const value2 = undefined || "hoge";
console.log(value2); // "hoge"

const value3 = 0 || "hoge";
console.log(value3); // hoge

const value4 = "" || "hoge";
console.log(value4); // hoge

空の配列やオブジェクトはtruthyになります。

console.log(Boolean([])); // true
console.log(Boolean({})); // true

ディープコピー

ディープコピーは、オブジェクトや配列を完全にコピーすることができます。コピー元とコピー先が完全に独立しているので、コピー先の変更を行なってもコピー元に反映されないです。

ディープコピーの方法は、オブジェクトの場合、JSON.parseとJSON.stringifyを使うことで簡単に行うことができます。

const originData = { name: "tomato", price: { salePrice: 100 } };
const deepCopyData = JSON.parse(JSON.stringify(originData));

originData.price.salePrice = 200;
console.log(originData);
// { name: "tomato", price: { salePrice: 200 } };
console.log(deepCopyData);
// { name: "tomato", price: { salePrice: 100 } };

ディープコピーの他に、コピー元のデータを一部参照するシャローコピーというものが、あります。
詳しくはコピーマスターの中村さんの記事が分かりやすいので、ぜひご参考に!

tech.iimon.co.jp

アロー関数

通常のfunctionを使っての関数よりも短く記載することができますが thisの仕様で異なる部分があります。

1.基本構文

// function
function add(a, b) {
  return a + b;
}

// アロー関数
const add = (a, b) => a + b;

2.引数がない場合、括弧 () を省略せずに記述

const hello = () => 'こんにちは!';

console.log(hello()); 
// こんにちは!

3.引数が1個の場合、括弧 () は省略できる

const tax = x => x * 1.1;

console.log(parseInt(tax(100)));
// 出力: 110

4.function関数とアロー関数の this の違い

  • アロー関数は this を持たない this は定義された親スコープの this を引き継ぐ。 function関数では this が呼び出し元によって変わります。
// function
const obj = {
  value: 42,
  method: function () {
    console.log(this.value); // オブジェクト自身を参照する
  },
};

obj.method(); // 出力: 42


// アロー関数
const obj = {
  value: 42,
  method: () => {
    console.log(this.value); // 親スコープの this を参照する
  },
};

obj.method(); // 出力: undefined
  • コールバック関数内

コールバック関数で this を使う場合、アロー関数の特性が便利

// function
function Timer() {
  this.seconds = 0;
  setInterval(function () {
    this.seconds++; // エラー: this は undefined
    console.log(this.seconds);
  }, 1000);
}

new Timer();

// アロー関数
function Timer() {
  this.seconds = 0;
  setInterval(() => {
    this.seconds++; // Timer インスタンスの this を参照
    console.log(this.seconds);
  }, 1000);
}

new Timer(); // 出力: 1, 2, 3, ...

Promise

Promise は非同期処理の結果を表現するオブジェクトで、以下の3つの状態をもちます。

  • Pending

    非同期処理が開始され、まだ結果が確定していない状態(進行中の状態)。 サーバーへのリクエストが送信され、レスポンスを待っている状態など

  • Fulfilled(成功)

    非同期処理が成功し、結果が得られた状態。
    resolve」が呼び出されると、この状態に遷移します。

  • Rejected(失敗)

    非同期処理が失敗し、エラーなどの理由で結果が得られなかった状態。 「reject」が呼び出されると、この状態に遷移します。

Promise に基づく非同期処理の結果を扱うメソッドは以下の3つがあります。

  • then

    Promiseが「Fulfilled」状態になったときに呼び出されます。 成功時の結果(resolve に渡された値)を受け取るコールバック関数。

  • catch

    Promise が「Rejected」状態になったときに呼び出されます。 エラー(reject に渡された値)を受け取るコールバック関数。

  • finally

    then や catch の後に呼び出され、成功・失敗に関係なく必ず実行される処理

処理の例です

const promise = new Promise((resolve, reject) => {
  const isSuccess = true;

  setTimeout(() => {
    if (isSuccess) {
      resolve('成功!');
    } else {
      reject('失敗!');
    }
  }, 1000);
});

promise
  .then(result => {
    console.log('[成功]状態: Fulfilled', result);
    // [成功]状態: Fulfilled 成功!
  })
  .catch(error => {
    console.error('[失敗]状態: Rejected', error);
    // [失敗]状態: Rejected 失敗!
  })
  .finally(() => {
    console.log('[終了]');
  })

async/await

async/awaitはPromise を扱いやすくするためのものです。 awaitはPromiseがresolveされるまで待機します。 resolveが返されると、その値を変数に代入し もしrejectされる場合、catchにエラーがスローされます。

const fetchData = async () => {
  try {
    const response = await fetch('https://example.com/user/1');
    if (!response.ok) {
      throw new Error('ネットワークエラー');
    }
    const data = await response.json();
    console.log('[成功]データ取得:', data);
  } catch (error) {
    console.error('[失敗]エラー:', error.message);
  } finally {
    console.log('[終了]データ取得処理完了');
  }
};

fetchData();

await は Promise を返す非同期関数や処理に必要です。
Promise以外の値を返す関数には awaitは使用しなくて大丈夫です。 以下のようなパターンの場合に必要になります。

const fetchData = async () => {
  return new Promise(resolve => {
    setTimeout(() => resolve('データ取得完了'), 1000);
  });
};

const res = await fetchData();

forEachの中でasync/awaitが効かない

forEachが非同期処理をサポートしないため、await を使っても、forEachの実行は非同期処理が完了するのを待ってくれないです。

解決方法として、Promise.allを使用することになります。

Promise.all を使用すると、非同期処理をまとめて実行し、すべての非同期処理が完了まで待つことができます。

  • forEachでawaitを使う場合
const fetchData = async (id) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`完了: ${id}`), 1000);
  });
};

const getItems = async (items) => {
  items.forEach(async (item) => {
    const result = await fetchData(item); // これが効かない
    console.log(result);
  });
  console.log('すべての処理が完了');
};

getItems([1, 2, 3]);

//   最初に「すべての処理が完了しました 」のログが出力されます
  • Promise.allでawaitを使う場合
const fetchData = async (id) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`完了: ${id}`), 1000);
  });
};

const getItems = async (items) => {
  const promises = items.map(async (item) => {
    const result = await fetchData(item);
    console.log(result);
    return result;
  });

  // Promise.all ですべての非同期処理が完了するまで待機
  await Promise.all(promises);
  console.log('すべての処理が完了');
};

getItems([1, 2, 3]);

// 完了: 1
// 完了: 2
// 完了: 3
// すべての処理が完了

おわりに

改めて振り返ってみるのもいいものですね! 来年はもっと勉強していきたいと思いました!

さて明日のアドベントカレンダー担当は、おっくんです!

どんな記事を投稿するのか楽しみです!!

最後になりますが、現在弊社ではエンジニアを募集しています!

この記事を読んで少しでも興味を持ってくださった方は、ぜひカジュアル面談でお話ししましょう!

 iimon採用サイト / Wantedly / Green

最後まで読んでいただきありがとうございました!!!

参考

Null 合体演算子 (??) - JavaScript | MDN

アロー関数式 - JavaScript | MDN

Promise - JavaScript | MDN

async function - JavaScript | MDN