はじめに
フロントエンドのプログラムを理解するにあたり、JavaScriptの基礎をおさらいしたく、改めて基礎を備忘録的にまとめてみた。以下のサイトを参照。
基礎文法
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide
スコープと変数宣言
https://qiita.com/varuvarad/items/cc0bff75f0a73aa78a5c
プロトタイプとクラス
https://developer.mozilla.org/ja/docs/Learn/JavaScript/Objects/Object_prototypes
変数
例えば、userAgeという変数を定義する場合、以下のように書ける。JavaScriptは動的型付け言語であり、変数の型を宣言する必要がない。
let userAge = 20;
変数の宣言にはletのほかにvarがあるが、varは関数スコープであり再宣言も可能なため、現在は基本的にlet(ブロックスコープ)を用いることが推奨されている。
定数
constを使って定数を定義できる。再代入は不可。
const userAge = 20;
ただし、constで宣言したオブジェクトや配列は、その「参照」が固定されるだけで、中身の要素は変更可能である点に注意。
const user = { name: "太郎" };
user.name = "次郎"; // これは可能
配列
1, 2, 3を要素に持つ配列は以下のように定義する。Goの配列と異なり、サイズの指定は不要であり、可変長で扱える(Goでいうスライスに近い)。
const a = [1, 2, 3];
要素の追加にはpush、末尾の削除にはpopを使う。
const s = [10, 20];
s.push(30); // [10, 20, 30]
オブジェクト
JavaScriptではkey-valueの集合をオブジェクトで表現する(Goのマップに相当)。例えば、keyが国名でvalueが首都の場合のオブジェクトは以下のように定義される。
const capitals = {
"日本": "東京",
"アメリカ": "ワシントンD.C.",
"フランス": "パリ",
};
要素へのアクセスはcapitals["日本"]またはcapitals.日本のように書ける。なお、より厳密にkey-valueの集合を扱いたい場合はMapオブジェクトを使うこともできる。
const capitals = new Map();
capitals.set("日本", "東京");
capitals.get("日本"); // "東京"
関数
整数のaとbを引数にして、a + bの結果を返す関数は以下のように定義できる。
function add(a, b) {
return a + b;
}
ES6以降は、アロー関数を使って簡潔に書くこともできる。
const add = (a, b) => a + b;
クラスとメソッド
名前と年齢の属性を持つUserというクラスと、そのインスタンスのintroduceメソッドを使った例を以下に示す(Goの構造体とメソッドに相当)。
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
introduce() {
console.log(`こんにちは、私は${this.name}です!`);
}
}
const user = new User("太郎", 20);
user.introduce();
constructorはインスタンス生成時に自動的に呼ばれる初期化メソッドで、thisは生成されるインスタンス自身を指す。
条件分岐
例えば、年齢が18歳以上かどうかで普通自動車免許が取得されるかどうかを判断する条件分岐は、以下のように書ける。
const age = 30;
if (age >= 18) {
console.log("普通自動車免許を取得可能です");
} else {
console.log("普通自動車免許を取得できません");
}
繰り返し
例えば、カゴに1つずつ最大5個までリンゴを入れるような繰り返し処理は、以下のように書ける。count++はcountの値を1つ増やすインクリメントをあらわす。
for (let count = 1; count <= 5; count++) {
console.log("カゴにリンゴを1つ入れました!");
}
count = 3 のときのみ、カゴにリンゴを入れないようにするには、以下のように書ける。continueを使い、count = 3 においてもforの処理を続ける。
for (let count = 1; count <= 5; count++) {
if (count === 3) {
console.log("カゴにリンゴを入れませんでした");
continue;
}
console.log("カゴにリンゴを1つ入れました!");
}
count = 3 になったら、カゴにリンゴを入れず、処理を中止するには、以下のように書ける。breakを使い、count = 3 になったらforの処理を終了する。
for (let count = 1; count <= 5; count++) {
if (count === 3) {
console.log("カゴにリンゴを入れませんでした");
break;
}
console.log("カゴにリンゴを1つ入れました!");
}
配列に対する繰り返し処理は、for...ofを使って書ける(Goのrangeに近い)。
const numbers = [10, 20, 30];
for (const n of numbers) {
console.log(n);
}
インデックスも合わせて取得したい場合は、entriesを使う。
const numbers = [10, 20, 30];
for (const [i, n] of numbers.entries()) {
console.log(i, n);
}
また、配列のメソッドであるforEachやmapを使って関数型の書き方をすることもJavaScriptでは一般的である。
const numbers = [10, 20, 30];
numbers.forEach((n, i) => console.log(i, n));
プリミティブと参照
JavaScriptには明示的なポインタの概念は存在しないが、値の扱いはプリミティブ型(数値、文字列、真偽値など)と参照型(オブジェクト、配列、関数など)で異なる。プリミティブ型は値渡しでコピーされる。
let x = 10;
let y = x;
y = 20;
console.log(x); // 10 (xは変わらない)
一方、オブジェクトは参照渡しとなるため、関数に渡したオブジェクトを変更すると元のオブジェクトも変わる。
function process(data) {
data.numbers[0] = 100;
}
const data = { numbers: [1, 2, 3] };
process(data);
console.log(data.numbers[0]); // 100
これはGoでいうところのポインタ渡しの挙動と似ており、大きなデータを関数に渡す際の値渡しコピーを避けたい場合に有用である。ただし、JavaScriptでは明示的に使い分けるのではなく、型ごとに挙動が決まっている点がGoとの違いである。
モジュール(importとexport)
JavaScript(ES Modules)でも、Goのpackageとimportに相当する仕組みがある。コーヒーを注文するだけの小規模プロジェクトjs-exampleを考える。ディレクトリ構成は以下。
js-example/
├── package.json
├── main.js
└── order/
└── product.js
order/product.jsは以下の通り。exportを付けた関数や変数だけが、他ファイルから参照可能となる(Goにおける大文字始まりのルールに相当)。
export function buy(name) {
console.log(name + "の注文が確定しました。");
}
main.jsは以下の通り。import文で他ファイルの関数を取り込む。
import { buy } from "./order/product.js";
const coffee = { name: "コーヒー", price: 450, size: "M" };
buy(coffee.name);
console.log("商品名:", coffee.name);
実行手順としては、package.jsonに"type": "module"を記載した上で、node main.jsで実行する。実行結果は以下。
コーヒーの注文が確定しました。
商品名: コーヒー
なお、export defaultを使うと「そのファイルのデフォルトのエクスポート」を1つだけ指定でき、import側では任意の名前で受け取ることができる。
// product.js
export default function buy(name) { ... }
// main.js
import buy from "./order/product.js";
非同期処理
JavaScriptはシングルスレッドで動作するため、ネットワーク通信などの時間のかかる処理は非同期で扱われる。Promiseを使った非同期処理は以下のように書ける。
fetch("https://example.com/api")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error));
async/awaitを使うことで、非同期処理を同期処理のように直感的に書ける。
async function getData() {
try {
const response = await fetch("https://example.com/api");
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
まとめ
- JavaScriptは動的型付け・インタプリタ型の言語で、
let(再代入可)とconst(再代入不可)で変数を宣言する。 - データ構造は配列(可変長)とオブジェクト(key-value)が基本で、より厳密なkey-value操作には
Mapも使える。 - 関数はアロー関数で簡潔に書け、クラスとメソッドはGoの構造体とメソッドに相当する役割を担う。
- プリミティブ型は値渡し、オブジェクト型は参照渡しとなるため、Goのポインタ的な挙動はオブジェクトで自然に再現される。
- ES Modulesによりファイル単位でコードを分割し、
exportしたものだけが他ファイルから参照可能となる。 -
Promiseやasync/awaitを用いた非同期処理が、フロントエンドにおける通信や時間のかかる処理の基本となる。