サイトアイコンmaita tomoya dev io

JavaScript

フロントエンド基礎

JavaScript

JavaScriptとは

JavaScriptは、Webブラウザで動く唯一のプログラミング言語。HTMLが「構造」、CSSが「見た目」を担当するのに対して、JavaScriptは「動き」を担当する。

ボタンをクリックしたときに何か起こる、フォームの入力値をチェックする、サーバーからデータを取得して表示する――これら全てがJavaScriptの仕事。

現在はNode.jsの登場により、ブラウザだけでなくサーバーサイドでも動作するようになった。つまりJavaScriptを学べば、フロントエンドもバックエンドも書ける。

用途
フロントエンドボタンの動作、アニメーション、SPA
バックエンドAPIサーバー(Node.js + Express等)
モバイルアプリReact Native、Ionic
デスクトップアプリElectron(VS Code等)

JavaとJavaScriptは全く別の言語。 名前が似ているのは歴史的な事情(当時Javaが人気だったため、マーケティング目的で名前を借りた)。

変数

変数はデータを格納する「名前付きの箱」。JavaScriptには3つの変数宣言の方法がある。

var / let / const の違い

特徴varletconst
再宣言可能不可不可
再代入可能可能不可
スコープ関数スコープブロックスコープブロックスコープ
巻き上げあり(undefined)あり(エラー)あり(エラー)
導入時期ES1(最初から)ES2015ES2015
// var: 古い書き方。現代では使わない
var name = '太郎'
var name = '花子' // 再宣言できてしまう(バグの原因になる)

// let: 値を後から変更する場合
let count = 0
count = 1 // 再代入OK
// let count = 2; // エラー: 再宣言は不可

// const: 値を変更しない場合(推奨)
const PI = 3.14
// PI = 3.15;     // エラー: 再代入は不可

スコープ

スコープとは「変数が参照できる範囲」のこと。

// varは関数スコープ(関数の中でのみ有効)
function example() {
  if (true) {
    var x = 10
  }
  console.log(x) // 10(ifブロックの外でも参照可能)
}

// let/constはブロックスコープ({}の中でのみ有効)
function example2() {
  if (true) {
    let y = 10
    const z = 20
  }
  // console.log(y); // エラー: yはブロックの外で参照不可
  // console.log(z); // エラー: zもブロックの外で参照不可
}

使い分けの指針

現代のJavaScriptでは var は使わない。

  • 基本は const を使う(値が変わらないことを明示できる)
  • 値を再代入する必要がある場合だけ let を使う(ループカウンタ等)
  • var は使わない(スコープの挙動が予測しにくく、バグの原因になる)
// 実務での使い方
const userName = '太郎' // 変更しない値はconst
const items = [1, 2, 3] // 配列もconst(中身の変更は可能)
const config = { debug: true } // オブジェクトもconst

let total = 0 // ループで加算するのでlet
for (let i = 0; i < items.length; i++) {
  total += items[i]
}

constで宣言した配列やオブジェクトの中身は変更可能 という点に注意。constは「再代入不可」であって「不変(immutable)」ではない。

const arr = [1, 2, 3]
arr.push(4) // OK: 配列の中身を変更
// arr = [5, 6, 7]; // エラー: 配列自体を別の配列に再代入は不可

const obj = { name: '太郎' }
obj.name = '花子' // OK: プロパティの変更
// obj = {};        // エラー: オブジェクト自体の再代入は不可

データ型

JavaScriptのデータ型は大きく「プリミティブ型」と「オブジェクト型」に分かれる。

プリミティブ型(基本的な値)

説明
string文字列'hello', "world", `hi`
number整数・小数42, 3.14, NaN, Infinity
boolean真偽値true, false
null値が「ない」ことを意図的に示すnull
undefined値が「未定義」undefined
symbol一意の識別子(ES2015)Symbol('description')
BigInt巨大な整数(ES2020)9007199254740991n

オブジェクト型(複合的な値)

説明
Objectキーと値のペア{ name: '太郎', age: 25 }
Array順序付きリスト[1, 2, 3]
Function実行可能な処理function() {}
Date日付と時刻new Date()
RegExp正規表現/pattern/g
Mapキーと値のペアnew Map()
Set重複なしの値の集合new Set()

nullとundefinedの違い

初心者が混乱しやすいポイント。

// undefined: 「まだ値が設定されていない」
let x
console.log(x) // undefined

// null: 「意図的に値がないことを示す」
let user = null // ユーザーはまだログインしていない

例えるなら:

  • undefined = 空の本棚(まだ本を置いていない)
  • null = 「空」と書かれたラベルが貼ってある本棚(意図的に空にしている)

型の確認

typeof 'hello' // 'string'
typeof 42 // 'number'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof null // 'object' ← 注意! JavaScriptの歴史的なバグ
typeof {} // 'object'
typeof [] // 'object' ← 配列かどうかはArray.isArrayで判定
typeof function () {} // 'function'

// 配列かどうかの正確な判定
Array.isArray([1, 2, 3]) // true
Array.isArray('hello') // false

演算子

算術演算子

10 + 3 // 13(加算)
10 - 3 // 7(減算)
10 * 3 // 30(乗算)
10 / 3 // 3.333...(除算)
10 % 3 // 1(剰余 = 余り)
10 ** 3 // 1000(べき乗)

// インクリメント/デクリメント
let count = 0
count++ // 1(1を加算)
count-- // 0(1を減算)

// 複合代入演算子
let total = 100
total += 10 // total = total + 10 → 110
total -= 5 // total = total - 5 → 105
total *= 2 // total = total * 2 → 210

比較演算子(=== vs ==)

JavaScriptで最も重要な注意点の1つ。

// == : 型変換してから比較(緩い比較)→ 使わない
1 == '1' // true(文字列'1'が数値1に変換される)
0 == false // true
null == undefined // true
'' == 0 // true

// === : 型も値も一致する場合のみtrue(厳密比較)→ 常にこちらを使う
1 === '1' // false(型が違う)
0 === false // false
null === undefined // false
'' === 0 // false

実務では常に ===(厳密等価演算子)を使う。 ==は予期しない型変換が起こるため、バグの温床になる。

演算子意味
===厳密等価1 === 1 → true
!==厳密不等価1 !== '1' → true
>より大きい5 > 3 → true
<より小さい3 < 5 → true
>=以上5 >= 5 → true
<=以下3 <= 5 → true

論理演算子

// AND(&&): 両方trueならtrue
true && true // true
true && false // false

// OR(||): どちらかがtrueならtrue
true || false // true
false || false // false

// NOT(!): 反転
!true // false
!false // true
!!0 // false(二重否定で真偽値に変換)
!!'hello' // true

三項演算子

if/elseの短縮形。単純な条件分岐を1行で書ける。

const age = 20
const message = age >= 18 ? '成人' : '未成年'
// 条件 ? trueのときの値 : falseのときの値

Nullish Coalescing(??)

nullまたはundefinedのときだけ右側の値を使う。ES2020で追加。

const input = null
const value = input ?? 'デフォルト値'
console.log(value) // 'デフォルト値'

// || との違い
const count = 0
console.log(count || 10) // 10(0はfalsyなので右側になる)
console.log(count ?? 10) // 0(0はnullでもundefinedでもないので左側が採用)

||0, '', false もfalsyとして扱うため、意図しない動作になることがある。??の方が安全。

Optional Chaining(?.)

プロパティが存在しない場合にエラーにならずundefinedを返す。ES2020で追加。

const user = {
  name: '太郎',
  address: {
    city: '東京',
  },
}

// Optional Chainingなし: エラーになる可能性
// const zip = user.address.zipCode.toString(); // エラー!

// Optional Chainingあり: 安全にアクセス
const zip = user.address?.zipCode?.toString() // undefined(エラーにならない)

// メソッド呼び出しにも使える
const result = user.greet?.() // greetメソッドが存在しなければundefined

条件分岐

if / else

const score = 85

if (score >= 90) {
  console.log('優秀')
} else if (score >= 70) {
  console.log('良好')
} else if (score >= 50) {
  console.log('合格')
} else {
  console.log('不合格')
}

switch

特定の値に対して分岐する場合に使う。breakを忘れると、次のcaseも実行される(フォールスルー)ので注意。

const day = 'Monday'

switch (day) {
  case 'Monday':
  case 'Tuesday':
  case 'Wednesday':
  case 'Thursday':
  case 'Friday':
    console.log('平日')
    break
  case 'Saturday':
  case 'Sunday':
    console.log('休日')
    break
  default:
    console.log('無効な曜日')
}

三項演算子(実用例)

// React等のUIライブラリで頻出
const isLoggedIn = true
const greeting = isLoggedIn ? 'ようこそ!' : 'ログインしてください'

// ネストは避ける(読みにくくなる)
// NG: const result = a ? b ? 'X' : 'Y' : 'Z';
// OK: if/elseを使う

ループ

for文

// 基本的なfor文
for (let i = 0; i < 5; i++) {
  console.log(i) // 0, 1, 2, 3, 4
}

for...of(配列の要素を順番に取り出す)

const fruits = ['りんご', 'バナナ', 'みかん']

for (const fruit of fruits) {
  console.log(fruit)
}
// りんご
// バナナ
// みかん

for...in(オブジェクトのキーを順番に取り出す)

const person = { name: '太郎', age: 25, city: '東京' }

for (const key in person) {
  console.log(`${key}: ${person[key]}`)
}
// name: 太郎
// age: 25
// city: 東京

注意: 配列にfor...inを使うのは避ける。配列にはfor...offorEachを使う。

while

let count = 0
while (count < 5) {
  console.log(count)
  count++
}

forEach(配列メソッド)

const numbers = [1, 2, 3, 4, 5]

numbers.forEach((num, index) => {
  console.log(`${index}: ${num}`)
})

map(配列メソッド - 新しい配列を返す)

const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map((num) => num * 2)
console.log(doubled) // [2, 4, 6, 8, 10]

使い分けの指針

ループ使い所返り値
forインデックスが必要な場合、複雑な条件なし
for...of配列の要素を順番に処理なし
for...inオブジェクトのキーを列挙なし
while条件が変化するまで繰り返すなし
forEach配列の各要素に処理を適用undefined
map配列を変換して新しい配列を作る新しい配列

実務では map, filter, forEach 等の配列メソッドを使う場面が多い。 for文は特殊な場合にのみ使用する。

関数

function宣言

function greet(name) {
  return `こんにちは、${name}さん!`
}

console.log(greet('太郎')) // こんにちは、太郎さん!

アロー関数(ES2015)

現代のJavaScriptで最も使われる関数の書き方。

// 基本形
const greet = (name) => {
  return `こんにちは、${name}さん!`
}

// 1行で返す場合はreturnと{}を省略可能
const greet = (name) => `こんにちは、${name}さん!`

// 引数が1つの場合は()を省略可能
const double = (num) => num * 2

// 引数がない場合
const getTimestamp = () => Date.now()

function宣言 vs アロー関数

特徴function宣言アロー関数
thisの束縛呼び出し元に依存定義時のthisを保持
巻き上げあり(宣言前に呼べる)なし
簡潔さ長め短め
用途メソッド定義、コンストラクタコールバック、一般的な関数
// 巻き上げの違い
greetA('太郎') // OK: function宣言は巻き上げられる
// greetB('太郎'); // エラー: アロー関数は巻き上げられない

function greetA(name) {
  return `こんにちは、${name}`
}

const greetB = (name) => `こんにちは、${name}`

デフォルト引数

const greet = (name = 'ゲスト', greeting = 'こんにちは') => {
  return `${greeting}、${name}さん!`
}

greet() // こんにちは、ゲストさん!
greet('太郎') // こんにちは、太郎さん!
greet('太郎', 'おはよう') // おはよう、太郎さん!

残余引数(Rest Parameters)

const sum = (...numbers) => {
  return numbers.reduce((total, num) => total + num, 0)
}

sum(1, 2, 3) // 6
sum(1, 2, 3, 4, 5) // 15

コールバック関数

関数を別の関数に引数として渡すパターン。JavaScriptの核心的な概念。

// setTimeoutはコールバック関数を受け取る
setTimeout(() => {
  console.log('3秒後に実行')
}, 3000)

// 配列メソッドもコールバック関数を受け取る
const numbers = [1, 2, 3, 4, 5]
const evens = numbers.filter((num) => num % 2 === 0)
console.log(evens) // [2, 4]

// 自分でコールバックを受け取る関数を作る
const executeWithLogging = (callback) => {
  console.log('処理開始')
  const result = callback()
  console.log('処理完了')
  return result
}

executeWithLogging(() => {
  console.log('メインの処理')
})

オブジェクト

オブジェクトは「キーと値のペア」を格納するデータ構造。JavaScriptで最も重要なデータ型の1つ。

オブジェクトリテラル

const user = {
  name: '太郎',
  age: 25,
  email: 'taro@example.com',
  isActive: true,
  // メソッド(オブジェクト内の関数)
  greet() {
    return `こんにちは、${this.name}です`
  },
}

プロパティアクセス

// ドット記法(推奨)
console.log(user.name) // '太郎'

// ブラケット記法(キーが変数や特殊文字を含む場合)
console.log(user['name']) // '太郎'

const key = 'email'
console.log(user[key]) // 'taro@example.com'

// メソッドの呼び出し
console.log(user.greet()) // 'こんにちは、太郎です'

分割代入(Destructuring)

オブジェクトから値を取り出して変数に代入する簡潔な書き方。ES2015で追加。

const user = { name: '太郎', age: 25, city: '東京' }

// 従来の書き方
const name = user.name
const age = user.age

// 分割代入(同じことを簡潔に書ける)
const { name, age, city } = user
console.log(name) // '太郎'
console.log(age) // 25

// 別名を付ける
const { name: userName, age: userAge } = user

// デフォルト値
const { name, country = '日本' } = user
console.log(country) // '日本'(userにcountryがないのでデフォルト値が使われる)

// ネストしたオブジェクトの分割代入
const response = {
  data: {
    user: { name: '太郎', age: 25 },
  },
}
const {
  data: {
    user: { name: deepName },
  },
} = response
console.log(deepName) // '太郎'

スプレッド構文

// オブジェクトのコピー
const original = { a: 1, b: 2 }
const copy = { ...original }

// オブジェクトのマージ
const defaults = { theme: 'light', lang: 'ja', debug: false }
const userConfig = { theme: 'dark', debug: true }
const config = { ...defaults, ...userConfig }
// { theme: 'dark', lang: 'ja', debug: true }
// 後のオブジェクトの値が優先される

// 特定のプロパティを除外してコピー
const { debug, ...configWithoutDebug } = config
// configWithoutDebug = { theme: 'dark', lang: 'ja' }

配列

配列は「順序付きのデータの集まり」を格納するデータ構造。

基本操作

const fruits = ['りんご', 'バナナ', 'みかん']

// アクセス(インデックスは0から始まる)
console.log(fruits[0]) // 'りんご'
console.log(fruits[2]) // 'みかん'

// 長さ
console.log(fruits.length) // 3

配列の主要メソッド一覧

要素の追加・削除

メソッド説明元の配列を変更するか
push()末尾に追加する
pop()末尾を削除して返すする
unshift()先頭に追加する
shift()先頭を削除して返すする
splice()指定位置で追加・削除する
slice()指定範囲を切り出すしない(コピーを返す)
const arr = [1, 2, 3]

// 末尾に追加
arr.push(4) // [1, 2, 3, 4]

// 末尾を削除
arr.pop() // [1, 2, 3]、返り値は4

// 先頭に追加
arr.unshift(0) // [0, 1, 2, 3]

// 先頭を削除
arr.shift() // [1, 2, 3]、返り値は0

// splice: インデックス1から2つ削除
arr.splice(1, 2) // [1]、削除された[2, 3]が返る

// splice: インデックス1に'a', 'b'を挿入
arr.splice(1, 0, 'a', 'b') // [1, 'a', 'b']

// slice: インデックス0から2(2は含まない)を切り出し
const sliced = [1, 2, 3, 4, 5].slice(0, 2) // [1, 2]

検索・判定

メソッド説明返り値
find()条件に合う最初の要素を返す要素 or undefined
findIndex()条件に合う最初のインデックスを返す数値 or -1
includes()特定の値が含まれるかboolean
indexOf()特定の値のインデックスを返す数値 or -1
some()1つでも条件を満たすかboolean
every()全て条件を満たすかboolean
const users = [
  { name: '太郎', age: 25 },
  { name: '花子', age: 30 },
  { name: '次郎', age: 20 },
]

// find: 条件に合う最初の要素
const found = users.find((user) => user.age >= 25)
console.log(found) // { name: '太郎', age: 25 }

// some: 1つでも条件を満たすか
const hasAdult = users.some((user) => user.age >= 20)
console.log(hasAdult) // true

// every: 全て条件を満たすか
const allAdults = users.every((user) => user.age >= 20)
console.log(allAdults) // true

// includes: 値が含まれるか
const colors = ['red', 'blue', 'green']
console.log(colors.includes('blue')) // true

変換・加工

メソッド説明返り値
map()各要素を変換した新しい配列新しい配列
filter()条件に合う要素だけの配列新しい配列
reduce()配列を1つの値にまとめる任意の値
sort()並べ替え元の配列
flat()ネストした配列を平坦化新しい配列
flatMap()mapしてからflatする新しい配列
const numbers = [1, 2, 3, 4, 5]

// map: 各要素を変換
const doubled = numbers.map((num) => num * 2)
console.log(doubled) // [2, 4, 6, 8, 10]

// filter: 条件に合う要素だけ抽出
const evens = numbers.filter((num) => num % 2 === 0)
console.log(evens) // [2, 4]

// reduce: 配列を1つの値にまとめる
const sum = numbers.reduce((total, num) => total + num, 0)
console.log(sum) // 15

// メソッドチェーン(組み合わせて使う)
const result = numbers
  .filter((num) => num % 2 !== 0) // 奇数だけ抽出: [1, 3, 5]
  .map((num) => num * 10) // 10倍: [10, 30, 50]
  .reduce((sum, num) => sum + num, 0) // 合計: 90

// sort: 並べ替え(注意: 元の配列を変更する)
const scores = [80, 95, 70, 60, 90]
scores.sort((a, b) => a - b) // 昇順: [60, 70, 80, 90, 95]
scores.sort((a, b) => b - a) // 降順: [95, 90, 80, 70, 60]

reduceの理解(初心者が最もつまずきやすいメソッド)

reduceは配列を「畳み込む」メソッド。配列の全要素を使って1つの値を作る。

// reduce(コールバック, 初期値)
// コールバック(蓄積値, 現在の要素, インデックス, 元の配列)

// 例: 合計を求める
;[1, 2, 3, 4, 5].reduce((accumulator, current) => {
  console.log(`蓄積値: ${accumulator}, 現在値: ${current}`)
  return accumulator + current
}, 0)
// 蓄積値: 0, 現在値: 1 → return 1
// 蓄積値: 1, 現在値: 2 → return 3
// 蓄積値: 3, 現在値: 3 → return 6
// 蓄積値: 6, 現在値: 4 → return 10
// 蓄積値: 10, 現在値: 5 → return 15
// 結果: 15

// 実用例: オブジェクトの配列から特定のプロパティだけを集計
const cart = [
  { item: 'りんご', price: 150 },
  { item: 'バナナ', price: 100 },
  { item: 'みかん', price: 80 },
]

const totalPrice = cart.reduce((sum, product) => sum + product.price, 0)
console.log(totalPrice) // 330

// 実用例: 出現回数をカウント
const colors = ['red', 'blue', 'red', 'green', 'blue', 'red']
const countMap = colors.reduce((acc, color) => {
  acc[color] = (acc[color] || 0) + 1
  return acc
}, {})
console.log(countMap) // { red: 3, blue: 2, green: 1 }

配列の分割代入

const [first, second, ...rest] = [1, 2, 3, 4, 5]
console.log(first) // 1
console.log(second) // 2
console.log(rest) // [3, 4, 5]

// 値の入れ替え
let a = 1
let b = 2
;[a, b] = [b, a]
console.log(a) // 2
console.log(b) // 1

スプレッド構文(配列)

// 配列のコピー
const original = [1, 2, 3]
const copy = [...original]

// 配列の結合
const arr1 = [1, 2]
const arr2 = [3, 4]
const combined = [...arr1, ...arr2] // [1, 2, 3, 4]

// 配列の中に展開
const withExtra = [0, ...arr1, 5] // [0, 1, 2, 5]

文字列メソッド

const str = '  Hello, World!  '

// 前後の空白を除去
str.trim() // 'Hello, World!'

// 含まれるか
str.includes('World') // true

// 始まるか / 終わるか
str.trim().startsWith('Hello') // true
str.trim().endsWith('!') // true

// 分割
'a,b,c'.split(',') // ['a', 'b', 'c']

// 置換
'Hello World'.replace('World', 'JavaScript') // 'Hello JavaScript'
'aaa'.replaceAll('a', 'b') // 'bbb'(ES2021)

// 大文字 / 小文字
'hello'.toUpperCase() // 'HELLO'
'HELLO'.toLowerCase() // 'hello'

// 部分文字列
'JavaScript'.substring(0, 4) // 'Java'
'JavaScript'.slice(-6) // 'Script'

// パディング
'5'.padStart(3, '0') // '005'
'5'.padEnd(3, '0') // '500'

テンプレートリテラル

バッククォート(`)で囲む文字列。変数の埋め込みと改行がそのまま書ける。

const name = '太郎'
const age = 25

// 従来の文字列連結
const message1 = 'こんにちは、' + name + 'さん。年齢は' + age + '歳です。'

// テンプレートリテラル(推奨)
const message2 = `こんにちは、${name}さん。年齢は${age}歳です。`

// 式も埋め込める
const message3 = `来年は${age + 1}歳になります`

// 複数行
const html = `
  <div class="card">
    <h2>${name}</h2>
    <p>年齢: ${age}</p>
  </div>
`

スコープとクロージャ

スコープの種類

// グローバルスコープ: どこからでもアクセス可能
const globalVar = 'グローバル'

function outer() {
  // 関数スコープ: この関数の中でのみアクセス可能
  const outerVar = '外側の関数'

  if (true) {
    // ブロックスコープ: このブロックの中でのみアクセス可能
    const blockVar = 'ブロック内'
    console.log(globalVar) // OK
    console.log(outerVar) // OK
    console.log(blockVar) // OK
  }

  console.log(globalVar) // OK
  console.log(outerVar) // OK
  // console.log(blockVar); // エラー: ブロックの外
}

レキシカルスコープ

関数は定義された場所のスコープを参照する(呼び出された場所ではない)。

const x = 10

function outer() {
  const x = 20

  function inner() {
    console.log(x) // 20(定義された場所のスコープ = outerのx)
  }

  inner()
}

outer()

クロージャ

関数が、自身が定義されたスコープの変数を「覚えている」仕組み。

function createCounter() {
  let count = 0 // この変数はcreateCounter実行後も保持される

  return {
    increment() {
      count++
      return count
    },
    decrement() {
      count--
      return count
    },
    getCount() {
      return count
    },
  }
}

const counter = createCounter()
console.log(counter.increment()) // 1
console.log(counter.increment()) // 2
console.log(counter.decrement()) // 1
console.log(counter.getCount()) // 1
// countに直接アクセスする方法はない(カプセル化)

クロージャの実用例:

// イベントハンドラでの使用
function setupButton(buttonId) {
  let clickCount = 0

  document.getElementById(buttonId).addEventListener('click', () => {
    clickCount++ // クロージャでclickCountを保持
    console.log(`${clickCount}回クリックされました`)
  })
}

this

thisはJavaScriptで最も混乱しやすい概念の1つ。呼び出し方によってthisが指すものが変わる。

thisの値の決まり方

呼び出し方thisの値
グローバルwindowオブジェクト(ブラウザ)console.log(this)
メソッドとしてそのオブジェクトobj.method()
関数としてundefined(strictモード)func()
アロー関数定義時のthis() => this
new演算子新しいインスタンスnew Constructor()
bind/call/apply指定したオブジェクトfunc.bind(obj)
const user = {
  name: '太郎',

  // メソッド内のthis → userオブジェクト
  greet() {
    console.log(`こんにちは、${this.name}`)
  },

  // アロー関数のthis → 定義時のthis(この場合グローバル)
  greetArrow: () => {
    console.log(`こんにちは、${this.name}`) // thisはuserではない
  },

  // setTimeoutでのよくある問題と解決
  delayedGreet() {
    // NG: function式のthisはsetTimeoutの呼び出し元になる
    // setTimeout(function() {
    //   console.log(this.name); // undefined
    // }, 1000);

    // OK: アロー関数はdelayedGreetのthisを保持
    setTimeout(() => {
      console.log(this.name) // '太郎'
    }, 1000)
  },
}

bind / call / apply

function greet(greeting, punctuation) {
  console.log(`${greeting}、${this.name}${punctuation}`)
}

const user = { name: '太郎' }

// call: thisを指定して即座に実行(引数をカンマ区切り)
greet.call(user, 'こんにちは', '!') // こんにちは、太郎!

// apply: thisを指定して即座に実行(引数を配列で渡す)
greet.apply(user, ['こんにちは', '!']) // こんにちは、太郎!

// bind: thisを束縛した新しい関数を返す(即座には実行しない)
const greetUser = greet.bind(user)
greetUser('こんにちは', '!') // こんにちは、太郎!

実務での指針: アロー関数を使えばthisの問題はほとんど解決できる。メソッド定義には通常の関数を、コールバックにはアロー関数を使うのが安全。

非同期処理

JavaScriptはシングルスレッド(1つの処理を1つずつ実行する)だが、非同期処理により「待ち時間のある処理」を効率的に扱える。

なぜ非同期処理が必要か

サーバーへのデータ取得に3秒かかる場合、その間画面がフリーズしては困る。非同期処理を使えば、データ取得中も画面操作を続けられる。

進化の歴史

コールバック(古い) → Promise(ES2015) → async/await(ES2017、現在の主流)

コールバック(古い方式)

// コールバック地獄(Callback Hell)の例
getUser(userId, (user) => {
  getOrders(user.id, (orders) => {
    getOrderDetails(orders[0].id, (details) => {
      getShippingInfo(details.shippingId, (shipping) => {
        console.log(shipping)
        // ネストが深くなりすぎて読めない...
      })
    })
  })
})

Promise

非同期処理の結果を「約束」するオブジェクト。成功(resolve)か失敗(reject)のどちらかになる。

// Promiseの作成
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true
      if (success) {
        resolve({ data: 'サーバーからのデータ' })
      } else {
        reject(new Error('データ取得に失敗'))
      }
    }, 1000)
  })
}

// Promiseの使用
fetchData()
  .then((result) => {
    console.log(result.data) // 'サーバーからのデータ'
  })
  .catch((error) => {
    console.error(error.message)
  })
  .finally(() => {
    console.log('処理完了')
  })

async/await(現在の主流)

Promiseをより直感的に書けるようにした構文糖(シンタックスシュガー)。

// asyncを付けた関数はPromiseを返す
const fetchUserData = async (userId) => {
  try {
    // awaitでPromiseの解決を待つ
    const response = await fetch(`/api/users/${userId}`)

    // レスポンスが正常でない場合
    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`)
    }

    const user = await response.json()
    return user
  } catch (error) {
    console.error('データ取得エラー:', error.message)
    throw error // エラーを再throw
  }
}

// 使用例
const displayUser = async () => {
  try {
    const user = await fetchUserData(1)
    console.log(user.name)
  } catch (error) {
    console.error('表示エラー:', error)
  }
}

displayUser()

並列実行

// 複数の非同期処理を並列に実行
const fetchAllData = async () => {
  try {
    // Promise.all: 全てが完了するまで待つ
    const [users, posts, comments] = await Promise.all([
      fetch('/api/users').then((r) => r.json()),
      fetch('/api/posts').then((r) => r.json()),
      fetch('/api/comments').then((r) => r.json()),
    ])

    console.log({ users, posts, comments })
  } catch (error) {
    // どれか1つでも失敗したらここに来る
    console.error('エラー:', error)
  }
}

非同期処理の使い分け

メソッド説明使い所
Promise.all()全て成功したら結果を返す全て必要なデータ取得
Promise.allSettled()全て完了(成功・失敗問わず)結果に関わらず全て待つ
Promise.race()最初に完了したものの結果を返すタイムアウト実装
Promise.any()最初に成功したものの結果を返す複数のバックアップURL

エラーハンドリング

try / catch / finally

const parseJSON = (jsonString) => {
  try {
    const data = JSON.parse(jsonString)
    return data
  } catch (error) {
    // JSONのパースに失敗した場合
    console.error('JSONパースエラー:', error.message)
    return null
  } finally {
    // 成功しても失敗しても必ず実行される
    console.log('パース処理完了')
  }
}

parseJSON('{"name": "太郎"}') // { name: '太郎' }
parseJSON('invalid json') // null + エラーログ

カスタムエラー

class ValidationError extends Error {
  constructor(message, field) {
    super(message)
    this.name = 'ValidationError'
    this.field = field
  }
}

const validateAge = (age) => {
  if (typeof age !== 'number') {
    throw new ValidationError('年齢は数値で入力してください', 'age')
  }
  if (age < 0 || age > 150) {
    throw new ValidationError('年齢は0から150の間で入力してください', 'age')
  }
  return true
}

try {
  validateAge('twenty')
} catch (error) {
  if (error instanceof ValidationError) {
    console.error(`${error.field}: ${error.message}`)
  } else {
    throw error // 想定外のエラーは再throw
  }
}

モジュール(import / export)

コードを複数のファイルに分割し、必要な部分だけを読み込む仕組み。

名前付きエクスポート / インポート

// math.js
export const add = (a, b) => a + b
export const subtract = (a, b) => a - b
export const PI = 3.14159

// main.js
import { add, subtract, PI } from './math.js'
console.log(add(2, 3)) // 5

デフォルトエクスポート / インポート

// User.js(1ファイル1つのメインの機能をエクスポート)
export default class User {
  constructor(name) {
    this.name = name
  }
}

// main.js(好きな名前でインポートできる)
import User from './User.js'
const user = new User('太郎')

名前付きとデフォルトの使い分け

種類1ファイルからインポート名使い所
名前付きexport複数可能固定ユーティリティ関数、定数
default export1つだけ自由メインのクラスやコンポーネント
// 混在も可能
// api.js
export const BASE_URL = 'https://api.example.com'
export const fetchData = async (path) => {
  /* ... */
}
export default class ApiClient {
  /* ... */
}

// main.js
import ApiClient, { BASE_URL, fetchData } from './api.js'

クラス(class)

ES2015で追加されたクラス構文。JavaScriptのプロトタイプベースの継承を、他の言語に馴染みのある書き方で扱えるようにした構文糖(シンタックスシュガー)。

基本的なクラス定義

class User {
  // コンストラクタ(インスタンス生成時に呼ばれる)
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  // メソッド
  greet() {
    return `こんにちは、${this.name}です(${this.age}歳)`
  }

  // 静的メソッド(インスタンスではなくクラス自体から呼ぶ)
  static create(name, age) {
    return new User(name, age)
  }
}

const user = new User('太郎', 25)
console.log(user.greet()) // こんにちは、太郎です(25歳)

const user2 = User.create('花子', 30)

継承

class Admin extends User {
  constructor(name, age, role) {
    super(name, age) // 親クラスのコンストラクタを呼ぶ
    this.role = role
  }

  greet() {
    return `${super.greet()} [${this.role}]`
  }
}

const admin = new Admin('管理者', 35, 'admin')
console.log(admin.greet()) // こんにちは、管理者です(35歳) [admin]

実務での使い所: React等のフレームワークではクラスコンポーネントとして使われてきたが、現在は関数コンポーネント+Hooksが主流。ただし、カスタムエラークラスやライブラリのAPI設計ではクラスが今でも活躍している。

ES2015+の重要機能まとめ

ES2015(ES6)以降に追加された、実務で必須の機能一覧。

機能導入時期概要
let / constES2015ブロックスコープの変数宣言
アロー関数ES2015簡潔な関数構文 + thisの束縛
テンプレートリテラルES2015バッククォートによる文字列埋め込み
分割代入ES2015オブジェクト・配列からの値の取り出し
スプレッド構文ES2015展開演算子 ...
PromiseES2015非同期処理の管理
classES2015クラス構文(プロトタイプベースの糖衣構文)
Map / SetES2015新しいコレクション型
SymbolES2015一意の識別子
for...ofES2015イテラブルのループ
import / exportES2015モジュールシステム
async / awaitES2017Promiseの簡潔な書き方
Optional ChainingES2020?. 安全なプロパティアクセス
Nullish CoalescingES2020?? null/undefinedのデフォルト値
replaceAllES2021全置換
Object.hasOwn()ES2022プロパティの存在チェック
Array.at()ES2022負のインデックスでのアクセス

Map / Set

// Map: キーに任意の型が使えるオブジェクト
const userRoles = new Map()
userRoles.set('太郎', 'admin')
userRoles.set('花子', 'editor')
console.log(userRoles.get('太郎')) // 'admin'
console.log(userRoles.has('次郎')) // false
console.log(userRoles.size) // 2

// Set: 重複しない値の集合
const uniqueNumbers = new Set([1, 2, 2, 3, 3, 3])
console.log(uniqueNumbers) // Set {1, 2, 3}

// 配列の重複を除去するパターン(実務で頻出)
const arr = [1, 2, 2, 3, 3, 3]
const unique = [...new Set(arr)]
console.log(unique) // [1, 2, 3]

JSON

JSON(JavaScript Object Notation)はデータ交換のための軽量なフォーマット。APIのレスポンスやデータの保存に広く使われている。

// JavaScriptオブジェクト → JSON文字列
const user = { name: '太郎', age: 25, isActive: true }
const jsonString = JSON.stringify(user)
console.log(jsonString) // '{"name":"太郎","age":25,"isActive":true}'

// 整形して出力(デバッグ時に便利)
console.log(JSON.stringify(user, null, 2))
// {
//   "name": "太郎",
//   "age": 25,
//   "isActive": true
// }

// JSON文字列 → JavaScriptオブジェクト
const parsed = JSON.parse(jsonString)
console.log(parsed.name) // '太郎'

JSONの注意点:

  • キーは必ず二重引用符で囲む(シングルクォートは不可)
  • 末尾のカンマ(trailing comma)は不可
  • コメントは書けない
  • undefined、関数、Symbolは含められない

実務でよく書くパターン集

APIからデータを取得して表示

const fetchAndDisplay = async () => {
  try {
    const response = await fetch('https://api.example.com/users')
    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`)
    }
    const users = await response.json()

    const html = users.map((user) => `<li>${user.name} (${user.email})</li>`).join('')

    document.getElementById('user-list').innerHTML = `<ul>${html}</ul>`
  } catch (error) {
    document.getElementById('user-list').innerHTML = '<p>データの取得に失敗しました</p>'
    console.error(error)
  }
}

フォームの値を取得してバリデーション

const form = document.getElementById('signup-form')

form.addEventListener('submit', (event) => {
  event.preventDefault() // フォームのデフォルト送信を防ぐ

  const formData = new FormData(form)
  const data = {
    name: formData.get('name')?.trim(),
    email: formData.get('email')?.trim(),
    age: Number(formData.get('age')),
  }

  // バリデーション
  const errors = []
  if (!data.name) errors.push('名前は必須です')
  if (!data.email?.includes('@')) errors.push('メールアドレスが無効です')
  if (data.age < 0 || data.age > 150) errors.push('年齢が不正です')

  if (errors.length > 0) {
    alert(errors.join('\n'))
    return
  }

  console.log('送信データ:', data)
})

配列データの加工(実務で最も多いパターン)

const products = [
  { id: 1, name: 'りんご', price: 150, category: '果物', inStock: true },
  { id: 2, name: 'キャベツ', price: 200, category: '野菜', inStock: true },
  { id: 3, name: 'バナナ', price: 100, category: '果物', inStock: false },
  { id: 4, name: 'トマト', price: 180, category: '野菜', inStock: true },
]

// 在庫ありの果物だけ抽出し、名前だけの配列にする
const availableFruits = products
  .filter((p) => p.category === '果物' && p.inStock)
  .map((p) => p.name)
// ['りんご']

// カテゴリごとにグループ化
const grouped = products.reduce((acc, product) => {
  const key = product.category
  if (!acc[key]) acc[key] = []
  acc[key].push(product)
  return acc
}, {})
// { '果物': [...], '野菜': [...] }

// 在庫ありの商品の合計金額
const totalPrice = products.filter((p) => p.inStock).reduce((sum, p) => sum + p.price, 0)
// 530

LocalStorageを使ったデータの永続化

// 保存
const saveData = (key, data) => {
  localStorage.setItem(key, JSON.stringify(data))
}

// 読み込み
const loadData = (key) => {
  const data = localStorage.getItem(key)
  return data ? JSON.parse(data) : null
}

// 使用例
saveData('user-settings', { theme: 'dark', lang: 'ja' })
const settings = loadData('user-settings')
console.log(settings) // { theme: 'dark', lang: 'ja' }

デバウンス(入力中に毎回実行しない)

const debounce = (func, delay) => {
  let timeoutId
  return (...args) => {
    clearTimeout(timeoutId)
    timeoutId = setTimeout(() => func(...args), delay)
  }
}

// 使用例: 検索入力
const searchInput = document.getElementById('search')
const handleSearch = debounce((event) => {
  console.log('検索:', event.target.value)
  // ここでAPIを呼ぶ
}, 300)

searchInput.addEventListener('input', handleSearch)

DOM操作について

この記事のコード例ではdocument.getElementByIdaddEventListenerといったDOM(Document Object Model)操作が登場する。DOMはJavaScriptからHTMLの要素を操作するための仕組みで、フロントエンド開発の核心的な技術の1つ。

DOM操作の詳細はロードマップの別記事で解説しているため、そちらを参照してほしい。

デバッグ方法

console系メソッド

// 基本的なログ
console.log('値:', variable)

// 表形式で表示(配列やオブジェクトの一覧に便利)
const users = [
  { name: '太郎', age: 25 },
  { name: '花子', age: 30 },
]
console.table(users)

// 警告(黄色で表示)
console.warn('注意: この機能は非推奨です')

// エラー(赤色で表示)
console.error('エラーが発生しました')

// グルーピング
console.group('ユーザー処理')
console.log('取得開始')
console.log('取得完了')
console.groupEnd()

// 処理時間の計測
console.time('fetchData')
await fetchData()
console.timeEnd('fetchData') // fetchData: 234ms

// オブジェクトのプロパティを一覧表示
console.dir(document.body)

debugger文

コードの中にdebuggerと書くと、DevToolsが開いている状態でそこで実行が一時停止する。

const calculateTotal = (items) => {
  debugger // ここで止まる(DevToolsで変数の値を確認できる)
  return items.reduce((sum, item) => sum + item.price, 0)
}

ブラウザDevTools

  1. Console: console.logの出力やエラーを確認
  2. Elements: HTMLとCSSをリアルタイムで確認・編集
  3. Sources: ブレークポイントを設定してステップ実行
  4. Network: APIリクエストの内容とレスポンスを確認
  5. Application: LocalStorage、Cookie等のデータを確認

DevToolsの開き方:

  • Windows/Linux: F12 または Ctrl + Shift + I
  • Mac: Cmd + Option + I

参考リンク