no-image

現代JavaScript基礎入門

JavaScriptは歴史ある言語です。時代が流れ、ウェブ技術の活躍の場が広がるにつれ、JavaScriptは何度も拡張されてきました。その結果、JavaScriptはかつての貧弱さからは想像できないほどの表現力を手に入れました。

現代において、JavaScriptの技術情報は、たいへん価値あるものとなっています。技術情報サイトや質問サイト、ブログなど、様々な場でJavaScriptの情報が提供されています。しかし古い情報がそのまま放置されていたり、情報が断片的であったりして、最新の情報がまとまって提供されているとは言いがたい状況です。

そこでこの記事では、ある程度新しい仕様に則ったJavaScriptについて、簡単にまとめたいと思います。

この記事についての注意点

この記事では、ある程度モダンなJavaScriptについて取り扱います。したがって、比較的新しい仕様に含まれる機能についての情報も含みます。

JavaScriptの仕様は、どこまで実装されているかはブラウザによってまちまちです。この記事に書いてあることでも、実際のブラウザ上では実行できないかもしれません。また、古いブラウザ(IE11など)においても、この記事の情報は役に立たない可能性があります。

自身が開発ターゲットとしているブラウザがどの程度の機能を有しているのか、あらかじめ調べた上でご活用ください。

JavaScriptについて

JavaScriptはプロトタイプベースオブジェクト指向言語で、動的型付け言語です。実行環境は主にウェブブラウザ上ですが、近年ではサーバサイドの実行環境も整ってきています。

仕様はECMAScriptとして標準化されています。多くの実行環境はこのECMAScriptに従っています。

コンソールへの出力

console.logを使用することでコンソールへ値を出力することが可能です。

console.log('hello world');

ブラウザ上では開発者用ツール、ターミナル上ではターミナルに出力されます。

変数と型

変数宣言

JavaScriptでは変数はletで宣言します。宣言した変数はブロックスコープとなります。

let x = 100;
let s = 'hello world';

constを利用して変数を宣言することで、再代入不可能な変数を宣言することができます。letと同じく、ブロックスコープになります。

const a = 100;
a = 200; // エラー

constという名前ですが、挙動としては他の言語のfinalに近いものとなっています。

また、かつて使われていた変数宣言キーワードにvarがあります。varで宣言した変数は関数スコープになります。letとconstが使える環境でvarを使う必要性はありませんが、現在でも使用することはできます。

var x = 100;

これら3種の変数宣言キーワードの詳しい違いについては、JavaScriptにおけるvar/let/constの使い分けをご覧ください。

プリミティブ型とオブジェクト型

JavaScriptにおいて、型はプリミティブ型とオブジェクト型のふたつに大別されます。プリミティブ型は6つあります。文字列、数値、真偽値、シンボル、null、undefined、の6つです。

'文字列';
3.14;
true;
Symbol();
null;
undefined;

プリミティブ型はメソッドやプロパティを持ちません。

一方オブジェクト型はメソッドやプロパティを持ちます。

const o = new Object();
o.freeze(); // メソッドなどを持つ

プリミティブ型のオートボクシング

ではプリミティブ型はメソッドを利用できないか?と言うと、そうではありません。プリミティブ型に対してメソッドやプロパティを呼び出そうとすると、オートボクシングという機能が働き、自動的にオブジェクト型へと変換されます。これによりプリミティブ型でもオブジェクト型のように扱うことが可能です。

const str = '   文字列   '.trim(); // '文字列'

つまり、現実的には、プリミティブ型かオブジェクト型かは、あまり気にする必要はありません。

文字列

文字列はプリミティブ型のひとつです。シングルクオートあるいはダブルクオートで囲むことにより文字列となります。

const str1 = '文字列';
const str2 = "これも文字列";

また、バッククオート${}を使うことで、文字列内に式を埋め込むことができます。${}の中に式を書きます。

const str = `1 + 2 = ${1 + 2}`;

数値

数値もプリミティブ型のひとつです。整数浮動小数点の型の区別はありません。

const num1 = 100;
const num2 = 3.14;

数値は、整数か実数かに関わらず、倍精度浮動小数点数(他の言語でいうdouble型)になります。

オブジェクト

JavaScriptにおいて、すべてのオブジェクトはオブジェクト型となります。オブジェクト型の実体は、キーと値のペアから成る、いわゆる連想配列です。

const obj1 = new Object();
obj1['key'] = 'value';

const obj2 = { key: 'value' }; // オブジェクトリテラル

リテラルとして記述することもできます。

演算子

算術演算子

数値に対して、以下の算術演算子が使用できます。

1 + 2;
1 - 2;
1 * 2;
1 / 2;
1 % 2; // あまり
1 ** 2; // 1の2乗
1++; // インクリメント
++1;
1--; // デクリメント
--1;
-1; // 負の数

文字列演算子

文字列に対し、文字列結合演算子+が使用できます。

'str1' + 'str2';

等価演算子

等価演算子として==が、不等価演算子として!=が使用できます。

1 == 2; // false
1 != 2; // true

等価演算子==および不等価演算子!=は左右の値(オペランド)が異なる型の場合、変換してから比較を行います。たとえば、’1’と1を比較すると、両方とも同じ型に変換され、等しいという結果になります。

型を変換せずに比較したい場合は、厳密な等価演算子===および厳密な不等価演算子!==を使用してください。

'1' == 1; // true
'1' === 1; // false
'1' !== 1; // true

オブジェクト型を比較する場合、オブジェクトの中身ではなく、参照が等しいかどうかを比較します。

const obj1 = {};
const obj2 = {};

obj1 == obj2; // false

比較演算子

比較演算子はオペランドの値を比較します。文字列の場合は、文字列のUnicodeの値を使って辞書順で比較されます。

1 < 2;
1 <= 2;
1 > 2;
1 >= 2;

条件演算子

三項演算子として、条件演算子?が使用可能です。

const condition = 1 == 2 ? '1と2は等しい' : '1と2は等しくない';

条件演算子はcond ? expr1 : expr2の形式で記述し、condがtrueの場合expr1の値を返し、そうでない場合はexpr2の値を返します。

論理演算子

論理演算子はAND(&&)、OR(||)、NOT(!)の3種類です。

true || false; // true
true && false; // false
!true; // false

制御文

一般的なプログラミング言語と同様、JavaScriptにも様々な制御文が存在します。

if…else if…else

ifは指定された条件がtrueならば文を実行します。

const a = 1;
const b = 2;

if(a < b) {
  console.log('a < b');
} else if(a == b){
  console.log('a == b');
} else {
  console.log('a > b');
}

else ifやelseは省略することも可能です。また、複数のelse ifがあってもかまいません。

while

whileは与えられた条件がtrueである間、文を実行します。いわゆるループを作ります。

let n = 0;
while(n < 3) {
  n++;
  console.log(n); // 1 2
}

for

forはwhileと同様ループを作りますが、初期化、条件の評価、値の更新を同時に記述することができます。

for(let i = 0; i < 3; i++) {
  console.log(i); // 0 1 2
}

また、それぞれの式は省略可能です。「;」は省略不可能です。

for(;;) {
  console.log('無限ループ');
}

全ての式を省略した場合は、while(ture)と同等の無限ループになります。

for of

for ofはiterableオブジェクトに対して反復します。iterableオブジェクトの代表的な例としては、後述するArray(配列)などがあります。

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

関数

functionキーワードを使用することで関数を定義することができます。

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

引数にデフォルトの値を指定することもできます。

function greet(name = 'John Appleseed') {
  console.log(`私の名前は${name}です。`);
}

また、JavaScriptにおいては、関数もオブジェクトであるため、変数に代入するといったことが可能です。

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

const myFunc = add; // 変数に関数を代入
console.log(myFunc(1, 2)); // 3

同様にして、関数を引数に取る関数、高階関数を作ることも可能です。

function twice(func) {
  func();
  func();
}

function hello() {
  console.log('hello');
}

twice(hello); // hello hello

また、関数をより簡易に記述する方法として、アロー関数が用意されています。

const add = (a, b) => a +b;
const twice = (func) => {
  func();
  func();
}

アロー関数では、引数がひとつの場合は()を省略することができます。ただし引数がゼロ個のときは省略することができません。

ジェネレータ関数

function*キーワードを使うことで、ジェネレータ関数を定義することができます。ジェネレータ関数は途中で抜け出したり復帰したりすることが可能な関数です。

ジェネレータ関数内では、returnのかわりにyieldを使用します。yieldによって一度関数から抜け、次回は次のyieldまで実行する、という仕組みになっています。

ジェネレータ関数は、呼び出すとジェネレータを返します。これはiteratorオブジェクトと呼ばれるもので、next()メソッドを呼び出すことで次の値を得ることができます。

function* count() {
  yield 1;
  yield 2;
  yield 3;
}

const generator = count();
console.log(generator.next().value); // 1
console.log(generator.next().value); // 2
console.log(generator.next().value); // 3

また、for ofを利用することでより簡単に反復を書くとこができます。

function* count() {
  yield 1;
  yield 2;
  yield 3;
}

const generator = count();
for(const val of generator) {
  console.log(val); // 1 2 3
}

ジェネレータ関数については、詳しくはJavaScriptのIteratorとGeneratorを使って反復処理を書くをご覧ください。

async関数とawait

async関数は非同期処理を記述する関数です。async関数は通常の値のかわりに、Promiseオブジェクトを返します。Promiseオブジェクトは将来的に処理が完了することを表すオブジェクトで、主に非同期処理に用いられます。

例えばネットワークからリソースを取得するfetch()関数はPromiseオブジェクトを返します。fetch()の結果を返す関数はasync関数として以下のように書くことができます。

async function getResponse(url) {
  return fetch(url);
}

async関数として関数を定義するメリットは、awaitが使えることです。Promiseの結果の展開には本来は少し手間がかかりますが、awaitを使うことで、まるで同期処理のように結果を展開することができます。

async function getResponse(url) {
  return await fetch(url);
}

const response = getResponse('file.txt');

awaitの使い方は、async関数の中で、Promiseオブジェクトの前にawaitと書きます。これだけでPromiseの実行結果が展開されます。

Promiseとasync関数、awaitについてのより詳しい情報は、Promiseとasync/awaitでJavaScriptの非同期処理をシンプルに記述するをご覧ください。

クラス

JavaScriptはプロトタイプベースであり、クラスベースの言語ではないため、厳密にはクラスは存在しません。しかし一般にクラスと呼ばれるものは実現できます。また、クラスベース言語のようにクラスを定義するclassキーワードもサポートされています。

ここではclassキーワードを使ったクラス定義についてのみ説明します。

クラスの定義

クラスを定義するにはclassキーワードを使用します。定義したクラスはnewキーワードでインスタンスを生成できます。

class Person {
  // コンストラクタ
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const p = new Person('John Appleseed', 18);
console.log(p.name);
console.log(p.age);

生成したインスタンスに対しての操作を行うには、thisを介して行います。上の例のコンストラクタ内を参考にしてください。この例では、Personクラスにnameプロパティとageプロパティを定義しています。

クラス宣言は巻き上げされないことに注意してください。つまり簡単に言うと、クラス宣言の前にクラスを使用することはできないということです。

get/setキーワードを使うことで、getter/setterが定義できます。

class Person {
  // コンストラクタ
  constructor(name, age) {
    this._name = name;
    this._age = age;
  }

  // getter/setter
  get name() { return this._name;  }
  get age() { return this._age; }
  set age(value) { this._age = value; }
}

const p = new Person('John Appleseed', 18);
p.age = 20;
console.log(p.name); // John Appleseed
console.log(p.age); // 20

メソッドも定義できます。

class Person {
  // コンストラクタ
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  greet() {
    console.log(`こんにちは、 私は${this.name}です。`)
  }
}

const p = new Person('John Appleseed', 18);
p.greet();

staticメソッドを定義することもできます。

class Person {
  // コンストラクタ
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  static printPerson(p) {
    console.log(`${p.name}, ${p.age}`);
  }
}

const p = new Person('John Appleseed', 18);
Person.printPerson(p);

クラスの継承

extendsキーワードを使うことでクラスを継承することができます。親クラスのコンストラクタを呼び出すにはsuper()を呼びます。

class Animal {
  constructor(name) {
    this.name = name;
  }
}

// extendsで継承する
class Dog extends Animal {
  constructor(name) {
    super(name);
  }
  
  bark() {
    console.log(`${this.name}は吠えた。`)
  }
}

const pochi = new Dog('ポチ');
pochi.bark();

様々なビルトインオブジェクト

JavaScriptには多くのオブジェクトが標準で用意されています。ここではそのうちの一部を紹介します。

RegExp

RegExpオブジェクトは正規表現を表すオブジェクトです。このオブジェクトを作成するには、リテラルを利用する方法とnewを利用する方法があります。

const regExp1 = /[a-z]/g;
const regExp2 = new RegExp('[a-z]', 'g');

//で囲むとRegExpリテラルになります。

Array

Arrayオブジェクトはいわゆる配列です。Arrayは利用頻度が非常に高いため、リテラル記法も用意されています。

const array1 = new Array();
const array2 = []; // リテラル
const array3 = [1, 2, 3]; // リテラル

[]を使うことでArrayオブジェクトの要素にアクセスできます。Arrayの添え字は0から始まります。

const array = [1, 2, 3];
console.log(array[0]); // 1

Arrayオブジェクトを操作するために、多くのプロパティやメソッドが用意されています。

const array = [1, 2, 3];
console.log(array.length); // 3

array.push(4);
console.log(array); // [1, 2, 3, 4]

Arrayオブジェクトを走査するには、様々な手法があります。古典的なforループによる走査から、高階関数forEachを利用した方法まで、様々です。

const array = [1, 2, 3];

// forによる走査
for(let i = 0; i < array.length; i++) {
  console.log(array[i]); // 1 2 3
}

// for ofを利用した走査
for(const e of array) {
  console.log(e); // 1 2 3
}

// forEachを利用した走査
array.forEach((e) => console.log(e));

Arrayオブジェクトは他にも様々なメソッドやプロパティを持ちます。詳しくはMDNのArrayのページをご覧ください。

Map/WeakMap

JavaScriptではオブジェクトはすべてキー/バリューの連想配列として機能しますが、それとは別にMapオブジェクトも用意されています。Mapオブジェクトの方がコレクションとしてより洗練された構造を持っているので、連想配列が欲しい場合はこちらを使うべきでしょう。

Mapオブジェクトはnewすることで作ることができます。リテラルはありません。

const map = new Map();

map.set('key', 'value');
console.log(map.get('key')); // value

このとき、キーは任意のプリミティブ値またはオブジェクトです。

Mapオブジェクトのプロパティやメソッドについては、詳しくはMDNのMapのページをご覧ください。

また、キーに対して弱い参照を持つWeakMapも利用することが可能です。

Set/WeakSet

Setオブジェクトは重複しない値を持つコレクションです。

const set = new Set();

set.add(1);
set.add(1);
set.add(2);

console.log(set.size); // 2

Setオブジェクトのプロパティやメソッドに関しては、詳しくはMDNのSetのページをご覧ください。

また、弱い参照を持つWeakSetも利用することが可能です。

スプレッド演算子と分割代入

JavaScriptでは高度な配列・オブジェクト操作を可能にするため、スプレッド演算子や分割代入といった仕組みが用意されています。

スプレッド演算子

以下のようなコードがあるとします。

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

const args = [1, 2, 3];
console.log(add(args[0], args[1], args[2])); // 6

このとき、スプレッド演算子(…)を使うことで、以下のように書くことができます。

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

const args = [1, 2, 3];
console.log(add(..args)); // 6

これがスプレッド演算子です。スプレッド演算子を使用することで、配列の中身を展開することができます。

スプレッド演算子は配列のリテラルに対しても使用することができます。

const a1 = [1, 2, 3];
const a2 = [4, ...a1, 5, 6];

console.log(a2); // [4, 1, 2, 3, 5, 6]

分割代入

分割代入は配列やオブジェクトの値を分解して、別の変数に代入することができる機能です。

const a = [1, 2];
const [x, y] = a;

console.log(x); //1
console.log(y); // 2

オブジェクトに対して利用することも可能です。

const obj = {a: 'val1', b:'val2'};
const {a, b} = obj;

console.log(a); // val1
console.log(b); // val2

分割代入を利用した少し変わったトリックとして、変数のスワップがあります。

let a = 1;
let b = 2;

[a, b] = [b, a]

console.log(a); // 2
console.log(b); // 1

strictモード

スクリプトの先頭、または関数の先頭に‘use strict’;または“use strict”;と記述することで、JavaScriptはstrictモードで実行されます。strictモードでは、厳格なチェックにより、些細なミスを防ぐことができます。

例えば未定義の変数への代入は、非strictモードではグローバル変数を作成しますが、strictモードではエラーとなります。

'use strict';

undefinedVariable = 100; // strictモードではエラー

strictモードでは他にも様々な制限が課されます。詳しくはMDNのStrictモードについてのページをご覧ください。