[2024年度版]AIを使ってJavascript ロードマップを訳してみた。
JavaScriptの紹介 (Introduction to JavaScript)
JSとは (What is JavaScript)
JavaScript(JSと省略されることが多い)は、HTMLやCSSと並んで、ウェブを支える重要なプログラミング言語のひとつです。
JavaScriptを使うことで、ウェブページにインタラクティブな要素を追加できます。たとえば、スライダー(画像が自動で切り替わる機能)や、アラート(画面に表示される通知)、クリックでの操作、ポップアップ(画面に浮かび上がるウィンドウ)など、いろいろなウェブサイトで見かける動きは、JavaScriptで作られています。
また、JavaScriptはブラウザ内で使われるだけでなく、Node.jsという技術を使ってサーバー側のコードを書くのにも利用されています。他にも、Electronではデスクトップアプリを作るために、React Nativeではモバイルアプリを開発するためにJavaScriptが使われています。つまり、ウェブ以外の場所でも、JavaScriptは幅広く活躍しています。
JSの歴史 (History of JavaScript)
JavaScriptは、もともとNetScape社のブレンダン・アイクによって開発され、1995年にNetScape社がプレスリリースで初めて発表しました。
JavaScriptの名前の歴史はちょっと変わっていて、最初は開発者によって「Mocha(モカ)」と名付けられていました。その後「LiveScript(ライブスクリプト)」に改名され、1996年にはリリースから約1年後に「JavaScript」に再び名前が変更されました。この変更は、当時の人気言語Javaに便乗しようという意図があったためですが、実際にはJavaScriptとJavaはほとんど関係がありません。この時、NetScapeはバージョン2.0をリリースし、正式にJavaScriptをサポートしました。
JSのバージョン (JavaScript Versions)
JavaScriptは、ブレンダン・アイクによって発明され、1997年にECMA(欧州電子標準化機構)の標準として認定され、「ECMAScript」という公式名称が採用されました。
この言語は、いくつかのバージョンを経て進化してきました。具体的には、ES1、ES2、ES3、ES5、そして大きな変革をもたらしたES6が登場しています。これらのアップデートは、JavaScriptの改善や標準化において重要な役割を果たし、ウェブ開発の世界で非常に広く使われる、価値のある言語になりました。
JSはどう実行されるのか (How to run JavaScript)
JavaScriptは、以下の方法でブラウザ内で実行することができます。
外部スクリプトファイルを含める:<script>タグを使って外部のJavaScriptファイルをHTMLに読み込むことができます。
HTMLページ内に直接書く:同じく<script>タグを使って、HTMLページの中にJavaScriptコードを書くこともできます。
ブラウザのコンソールで実行:ブラウザの開発者ツールのコンソールを使って、直接JavaScriptのコードを入力して実行することができます。
REPLを使用する:REPL(Read-Eval-Print Loop)を利用することで、対話的にJavaScriptを実行することもできます。
変数について (All about Variables)
ほとんどの場合、JavaScriptアプリケーションは情報を扱う必要があります。
この情報をJavaScriptのコード内で保存し、表現するために変数を使います。変数は、値を格納するための「コンテナ」と考えることができます。
変数の宣言の種類 (Variable Declarations
JavaScriptで変数を使うためには、まず変数を作る必要があります。つまり、変数を宣言します。
変数を宣言するためには、var、let、またはconstのいずれかのキーワードを使います。
const
定数は、letキーワードで宣言された変数と同様に、ブロックスコープを持っています。
定数の値は再代入(=代入演算子を使うこと)によって変更することはできず、再宣言(=変数の再定義)もできません。ただし、定数がオブジェクトや配列である場合、そのプロパティや要素を更新したり削除したりすることは可能です。
ブロックスコープ:定数が宣言されたブロック(たとえば、if文やループなど)の中でのみ有効であり、そのブロックの外ではアクセスできません。
オブジェクトや配列:定数自体は再代入できませんが、たとえばconst person = { name: "Alice" };というオブジェクトのnameをperson.name = "Bob";のように変更することはできます。
let
let宣言は、ブロックスコープを持つローカル変数を宣言し、必要に応じて初期値を設定します。
初期値の設定:let x = 10;のように初期値を指定することができ、初期値を指定しない場合はundefinedが代入されます。
var
var文は、関数スコープまたはグローバルスコープの変数を宣言し、必要に応じて初期値を設定します。
初期値の設定:var y = 20;のように初期値を指定することができ、初期値を指定しない場合はundefinedが代入されます。
巻き上げ (Hoisting)
JavaScriptの「ホイスティング」とは、インタープリタが関数、変数、またはクラスの宣言を、そのスコープの先頭に移動させるように見えるプロセスを指します。これはコードの実行前に行われます。
ホイスティング:JavaScriptでは、関数や変数の宣言がスコープの最上部に持ち上げられる(ホイストされる)ため、実際に宣言する前にそれらを使用できるように見える現象です。
関数と変数:例えば、関数を宣言する前にその関数を呼び出すことができ、変数も宣言前に参照することができますが、未定義(undefined)として扱われることがあります。
クラス:クラスの場合はホイストされず、宣言する前にインスタンス化しようとするとエラーになります。
var
のホイスティング
-
ホイストされる:
var
で宣言された変数は、スコープの最上部にホイストされます。 -
初期値は
undefined
: 宣言されたが初期化されていない場合、変数はundefined
として扱われます。
console.log(x); // 出力: undefined
var x = 5;
console.log(x); // 出力: 5
let
のホイスティング
-
ホイストされるが、初期化されない:
let
で宣言された変数もホイストされますが、スコープ内での使用は宣言が行われるまで許可されません。これは「一時的死角(Temporal Dead Zone)」と呼ばれます。 -
宣言前のアクセスはエラー: 宣言前に変数にアクセスしようとすると、
ReferenceError
が発生します。
console.log(y); // エラー: Cannot access 'y' before initialization
let y = 10;
console.log(y); // 出力: 10
const
のホイスティング
-
let
と同様:const
もホイストされますが、初期化される前にアクセスするとReferenceError
が発生します。 -
再代入不可:
const
で宣言された変数は再代入できないため、初期化時に必ず値を設定する必要があります。
console.log(z); // エラー: Cannot access 'z' before initialization
const z = 20;
console.log(z); // 出力: 20
関数のホイスティング
console.log(myFunction()); // 出力: "Hello, World!"
function myFunction() {
return "Hello, World!";
}
- 関数宣言
myFunction
はホイスティングされるため、関数が宣言される前に呼び出してもエラーにならず、正常に実行されます。
クラスのホイスティング
const obj = new MyClass(); // エラー: MyClass is not defined
class MyClass {
constructor() {
console.log("MyClass instance created");
}
}
- クラスの宣言はホイストされないため、クラスを宣言する前にインスタンス化しようとするとエラーが発生します。
これらの例から、ホイスティングの挙動を理解するのが重要であることがわかります。特にvar
や関数宣言と、クラスやlet
、const
の挙動の違いを把握しておくと、意図しないバグを避けることができます。
変数の命名規則 (Variable Naming Rules)
変数名は、その変数を正確に識別するものであるべきです。良い変数名を作成することで、JavaScriptのコードは理解しやすく、扱いやすくなります。適切な変数名を付けることは非常に重要です。
変数の有効範囲 (Variable Scopes)
JavaScriptにおけるスコープとは、変数の可視性、つまり変数が宣言された後にどのように使用できるかを指します。変数のスコープは、その変数を宣言する際に使用されたキーワードによって決まります。
スコープには主に3つの種類があります:グローバルスコープ、関数スコープ、ブロックスコープです。ES6(2015年)以前は、JavaScriptにはvar
キーワードを使ったグローバルスコープと関数スコープしかありませんでしたが、ES6で導入されたlet
とconst
により、ブロックスコープもサポートされるようになりました。
Global
グローバルに(任意の関数の外で)宣言された変数はグローバルスコープを持ちます。グローバル変数は、JavaScriptプログラムのどこからでもアクセス可能です。var、let、constで宣言された変数は、ブロックの外で宣言された場合は非常に似た挙動を示します。
Function
関数内で変数が宣言されると、その変数はその関数内でのみアクセス可能であり、その関数の外では使用することができません。
Block
このスコープは、特定のブロック内で宣言された変数への外部からのアクセスを制限します。letとconstキーワードを使用することで、変数はブロックスコープを持つことができます。その特定のブロックの変数にアクセスするためには、その変数のためのオブジェクトを作成する必要があります。一方、varキーワードで宣言された変数にはブロックスコープはありません。
データの種類 (Data Types)
データ型とは、JavaScriptの変数が保持できるデータの種類を指します。JavaScriptには7つのプリミティブ(基本)データ型があり、それらは次の通りです:Number(数値)、BigInt(大きな整数)、String(文字列)、Boolean(真偽値)、Null(無)、Undefined(未定義)、そしてSymbol(シンボル)です。オブジェクトは非プリミティブ型に分類されます。
基本的な種類 (Primitive Types)
string
文字列(String)は、一連の文字を保持するプリミティブ型です。JavaScriptの文字列は、シングルクォーテーション(''
)、ダブルクォーテーション(""
)、またはバックティック(``)で囲んで書かれます(テンプレートリテラル)。どの種類の引用符も文字列を囲むのに使用できますが、開始の引用符と終了の引用符は同じでなければなりません。
let name = 'John';
let greeting = `Hello, ${name}!`; // テンプレートリテラルを使用して変数を埋め込む
undefined
undefined
はJavaScriptにおけるプリミティブデータ型の一つです。
変数が宣言されたが、初期化や値が割り当てられていない場合、その変数はundefined
として保存されます。関数が値を返さない場合はundefined
が返されます。また、評価される変数に値が割り当てられていない場合も、メソッドや文がundefined
を返します。
let a; // aは宣言されているが初期化されていない
console.log(a); // 結果: undefined
-
関数での
undefined
:関数が何も値を返さない場合、その関数はデフォルトでundefined
を返します。function noReturn() { // 何も返していない } console.log(noReturn()); // 結果: undefined
-
評価での
undefined
:変数や式を評価する際に、値が割り当てられていない場合、undefined
が返されます。
number
JavaScriptにおけるNumber
データ型は、37や-9.25のような浮動小数点数を表します。Number
コンストラクタには、数値を扱うための定数やメソッドが用意されており、他の型の値もNumber()
関数を使用して数値に変換できます。
let num1 = 255; // 整数
let num2 = 255.0; // 小数点部分がない浮動小数点数
let num3 = 0xff; // 16進数表記
let num4 = 0b11111111; // 2進数表記
let num5 = 0.255e3; // 指数表記
console.log(num1 === num2); // true
console.log(num1 === num3); // true
console.log(num1 === num4); // true
console.log(num1 === num5); // true
-
255と255.0:JavaScriptでは、整数(
255
)と小数点がある浮動小数点数(255.0
)は同じ数値として扱われます。 -
16進数表記(0xff):
0xff
は16進数で255を表します。 -
2進数表記(0b11111111):
0b11111111
は2進数で255を表します。 -
指数表記(0.255e3):
0.255e3
は255を表す指数表記です(0.255 × 10^3
)。
これらの異なる表記法は、JavaScriptではすべて255として等しい扱いになります。
bigint
BigInt
は、JavaScriptに組み込まれているオブジェクトで、任意の大きさの整数を扱うことができます。
Number
型は±2^53の範囲内でのみ正確に整数を表現できますが、BigInt
はこの制限を超えた非常に大きな整数も扱うことができます。そのため、暗号技術や科学計算など、高精度が求められる大規模な数値を扱うアプリケーションで特に役立ちます。
-
BigInt
とNumber
型の違い:通常のNumber
型では、整数の正確な表現は±2^53(約9000兆)までの範囲に限定されています。それを超える大きな数値は精度が失われる可能性があります。しかし、BigInt
を使うことで、この範囲を超えた数値も正確に扱うことが可能になります。let bigNumber = BigInt("9007199254740991"); // Number型では扱えない大きな整数 console.log(bigNumber); // 結果: 9007199254740991n
-
用途
-
暗号技術:暗号化アルゴリズムでは、非常に大きな数を扱うことが多く、
BigInt
が不可欠です。 -
科学計算:特に物理学や天文学では、極めて大きな数値や精度が求められる場面が多いため、
BigInt
はそのニーズに対応します。
-
暗号技術:暗号化アルゴリズムでは、非常に大きな数を扱うことが多く、
boolean
JavaScriptにおけるboolean
は、true
またはfalse
という2つの値のいずれかを持つシンプルなデータ型です。これらの値は論理的な状態を表すために使用され、プログラムの流れを制御する上で非常に重要です。
boolean
は、条件文(if
、else
、while
など)でよく使用され、特定のコードブロックを実行するかどうかを判断するために使われます。
-
boolean
の役割:プログラムの中で「真か偽か」を判断するための基本的なデータ型です。例えば、何かが「存在するかどうか」「条件を満たしているかどうか」をboolean
でチェックします。let isRaining = true; // 雨が降っているかどうかを表すboolean変数 if (isRaining) { console.log("傘を持っていこう!"); } else { console.log("傘は必要ないよ!"); }
-
条件文での使用:
boolean
はif
文やwhile
文などの条件文において重要な役割を果たします。例えば、if
文では、条件がtrue
のときに指定されたコードブロックが実行されます。let num = 5; if (num > 3) { console.log("numは3より大きい"); } else { console.log("numは3以下"); }
null
null
値は、JavaScriptにおいて「意図的にオブジェクト値が存在しない」ことを示します。これはJavaScriptのプリミティブ値(基本データ型)の一つであり、falsy
(偽とみなされる)値として扱われます。
「意図的な不在」という点では、null
は変数がどのオブジェクトも指していないことを意図的に示すために使われます。この明示的な宣言により、null
は「変数が実行時に空や存在しない状態であるべきだ」という意図を表現します。
-
null
の役割:null
は、変数が「何も指していない」ことを明示的に示します。例えば、オブジェクトの値が存在しない、あるいは必要なくなった場合に、その変数にnull
を代入してリセットすることができます。let person = { name: "Alice" }; person = null; // オブジェクトの参照をリセット
-
null
とundefined
の違い:-
null
は意図的に「何もない」状態を示すために使用されます。 -
undefined
は、変数が初期化されていないか、値が設定されていない場合にJavaScriptが自動的に割り当てる値です。
let emptyValue = null; // 明示的に「空」を表現 let uninitialized; // 値が設定されていないため、undefined
-
-
falsy
値:null
はif
文などの条件式でfalse
とみなされます。これをfalsy
値と呼び、他には0
や""
(空文字列)、undefined
もfalsy
値です。
Symbol
Symbol
は、JavaScriptのユニークで変更不可能なプリミティブデータ型で、ECMAScript 6 (ES6)で導入されました。主にオブジェクトのプロパティキーをユニークにするために使用され、プロパティキーが重複しないようにします。 同じ説明(description
)で複数のSymbol
を作成しても、それぞれは異なる値として扱われます。Symbol
はSymbol()
関数を使って作成でき、その主な用途は、他のプロパティやメソッドに干渉しない隠しプロパティや特別なプロパティをオブジェクトに追加することです。
-
Symbol
の役割:JavaScriptでは、オブジェクトのプロパティキーは通常文字列や数値を使いますが、これだと異なるコード部分でキーが重複してしまうリスクがあります。Symbol
を使うと、必ず一意なプロパティキーを作成できるため、キーの重複を防げます。let sym1 = Symbol("description"); let sym2 = Symbol("description"); console.log(sym1 === sym2); // false それぞれ異なるSymbol
-
ユニーク性:
Symbol
は、たとえ同じ説明文(description
)で作成されたとしても、それぞれ異なる一意の値を持つため、他のプロパティと衝突することはありません。 -
隠しプロパティ:
Symbol
を使ってオブジェクトにプロパティを追加すると、そのプロパティは通常のループやメソッドからは見えないため、他の部分と干渉しない隠しプロパティを作ることができます。let myObject = {}; let hiddenKey = Symbol("hidden"); myObject[hiddenKey] = "This is a hidden value"; console.log(myObject[hiddenKey]); // "This is a hidden value"
-
Symbol
のユースケース:主にライブラリやフレームワークで、他のコードに影響を与えない特殊なプロパティを追加するために使用されます。Symbol
を使うことで、安全にプロパティを管理しつつ、コードの分離性を高めることが可能です。
Object
JavaScriptのオブジェクトは、キーと値のペアを持つデータ構造です。各キーは任意のJavaScriptデータ型の値にマッピングされるため、異なるキーを持つことができます。現実のオブジェクトと比較すると、ペンは色やデザイン、材料などのさまざまなプロパティを持つオブジェクトです。同様に、JavaScriptのオブジェクトもその特徴を定義するプロパティを持つことができます。
オブジェクトの基本構造
JavaScriptのオブジェクトは、次のように作成できます。
let pen = {
color: "blue",
design: "modern",
material: "plastic"
};
ここで、pen
はオブジェクトであり、color
、design
、material
がプロパティです。それぞれのプロパティには値が設定されています。
プロパティへのアクセス
オブジェクトのプロパティには、ドット記法またはブラケット記法を使ってアクセスできます。
- ドット記法:
console.log(pen.color); // "blue"
- ブラケット記法:
console.log(pen["design"]); // "modern"
プロパティの追加・更新
オブジェクトに新しいプロパティを追加したり、既存のプロパティの値を更新することも簡単です。
// プロパティの追加
pen.brand = "Parker";
// プロパティの更新
pen.color = "black";
console.log(pen);
// {
// color: "black",
// design: "modern",
// material: "plastic",
// brand: "Parker"
// }
オブジェクトのメソッド
オブジェクトは、プロパティだけでなく、メソッドも持つことができます。メソッドは、オブジェクトの中で定義された関数です。
let pen = {
color: "blue",
write: function() {
console.log("Writing with a " + this.color + " pen.");
}
};
pen.write(); // "Writing with a blue pen."
組み込みオブジェクト (Built-in Objects)
ビルトインオブジェクト、または「グローバルオブジェクト」は、言語仕様自体に組み込まれているオブジェクトのことを指します。JavaScriptには多くのビルトインオブジェクトがあり、すべてグローバルスコープでアクセス可能です。以下はその一部の例です。
Number
数値を操作するためのビルトインオブジェクトです。数値のプロパティやメソッドを提供します。
let num = Number.MAX_VALUE; // 最大の数値
console.log(num); // 1.7976931348623157e+308
Math
数学的な計算を行うためのビルトインオブジェクトで、さまざまな数学関数や定数が含まれています。
let squareRoot = Math.sqrt(16); // 平方根を計算
console.log(squareRoot); // 4
Date
日付や時間を扱うためのビルトインオブジェクトです。現在の日時や特定の日付を操作できます。
let now = new Date(); // 現在の日付と時間
console.log(now); // 例: 2024-09-25T09:45:00.000Z
String
文字列を操作するためのビルトインオブジェクトです。文字列のメソッドやプロパティが提供されています。
let greeting = "Hello, World!";
let length = greeting.length; // 文字列の長さを取得
console.log(length); // 13
Error
エラーを表現するためのビルトインオブジェクトで、エラーの種類やメッセージを管理できます。
let error = new Error("Something went wrong!");
console.log(error.message); // "Something went wrong!"
Function
関数を作成するためのビルトインオブジェクトです。関数オブジェクトに関連するプロパティやメソッドを提供します。
function greet(name) {
return "Hello, " + name;
}
console.log(Function.prototype.isPrototypeOf(greet)); // true
Boolean
真偽値(true または false)を表すためのビルトインオブジェクトです。
let isJavaScriptFun = Boolean(true);
console.log(isJavaScriptFun); // true
プロトタイプ継承 (Prototypal Inheritance)
プロトタイプ継承(Prototypal Inheritance)は、JavaScriptにおけるオブジェクトの機能で、オブジェクトにメソッドやプロパティを追加するために使われます。この仕組みにより、あるオブジェクトが別のオブジェクトのプロパティやメソッドを継承することができます。従来、オブジェクトのプロトタイプを取得または設定するためには、Object.getPrototypeOf
と Object.setPrototypeOf
を使用します。
プロトタイプ継承の仕組み
JavaScriptのオブジェクトは、他のオブジェクトをプロトタイプとして持つことができます。プロトタイプを介して、親オブジェクトのプロパティやメソッドを子オブジェクトが利用できるようになります。これにより、コードの再利用やメソッドの追加が簡単に行えます。
// 親オブジェクト
const animal = {
type: 'unknown',
speak: function() {
console.log(`The ${this.type} makes a noise.`);
}
};
// 子オブジェクトを作成し、プロトタイプにanimalを指定
const dog = Object.create(animal);
dog.type = 'dog';
dog.speak(); // "The dog makes a noise."
プロトタイプの取得と設定
プロトタイプを取得するには、Object.getPrototypeOf
を使用します。
console.log(Object.getPrototypeOf(dog) === animal); // true
プロトタイプを設定するには、Object.setPrototypeOf
を使用します。
const cat = {};
Object.setPrototypeOf(cat, animal);
cat.type = 'cat';
cat.speak(); // "The cat makes a noise."
オブジェクトプロトタイプ (Object Prototype)
JavaScriptはプロトタイプモデルに基づいたオブジェクト指向言語です。JavaScriptでは、全てのオブジェクトがプロトタイプ(親となるオブジェクト)からプロパティを継承します。プロトタイプとは、あるオブジェクトが別のオブジェクトからプロパティやメソッドを継承するための基となるオブジェクトのことです。
プロトタイプはJavaScriptのオブジェクト指向プログラミング(OOP)の核心部分であり、オブジェクト同士の継承を簡単に実現します。複雑なプログラムを作成する際には、このプロトタイプ機能を使いこなすことが非常に重要です。
プロトタイプ継承の仕組み
オブジェクトを作成すると、そのオブジェクトには「プロトタイプ」という隠れたリンクが存在し、このリンクを介して他のオブジェクトのプロパティやメソッドにアクセスできます。たとえば、Object.create()
メソッドを使えば、特定のオブジェクトをプロトタイプとして持つ新しいオブジェクトを作ることができます。
const parentObject = {
greet: function() {
console.log('Hello from parent');
}
};
const childObject = Object.create(parentObject);
childObject.greet(); // "Hello from parent"
プロトタイプチェーン
オブジェクトは、自身が持つプロパティにアクセスしようとしたとき、そのプロパティが存在しない場合はプロトタイプチェーンをたどり、親のプロトタイプにあるかを確認します。このように、オブジェクトは継承チェーンを通してプロパティやメソッドにアクセスできます。
プロトタイプの利用
プロトタイプは特に複雑なオブジェクト指向プログラムや再利用可能なコードを設計する際に役立ちます。JavaScriptの組み込みオブジェクト(例: Array
, String
, Function
)もプロトタイプを持ち、それぞれに便利なメソッドが定義されています。
このように、プロトタイプを活用することは、JavaScriptで高度なオブジェクト操作や継承を効率的に行うために非常に重要です。
typeof operator
typeof
演算子は、JavaScriptの変数のデータ型を確認するために使われます。この演算子は、与えられたオペランドの値のデータ型を示す文字列を返します。
let number = 42;
let text = "Hello, world!";
let isTrue = true;
console.log(typeof number); // "number"
console.log(typeof text); // "string"
console.log(typeof isTrue); // "boolean"
上記のコード例では、typeof
を使うことで、それぞれの変数 number
, text
, isTrue
のデータ型を確認しています。
型変換 (Type Casting)
型変換(または型キャスト)は、データをあるデータ型から別のデータ型に変換することを意味します。暗黙的な型変換は、コンパイルされる言語のコンパイラや、JavaScriptのようなスクリプト言語のランタイムが自動的にデータ型を変換する際に発生します。また、ソースコード内で明示的に型変換を行うこともできます。
型の変換 vs 型の強制 (Type Conversion vs Coercion)
型強制(Type coercion)は、値が自動的または暗黙的に、あるデータ型から別のデータ型に変換されることを指します(例えば、文字列から数値への変換)。型変換(Type conversion)は型強制に似ていて、どちらも値を異なるデータ型に変換しますが、重要な違いは型強制が暗黙的に行われる点です。一方、型変換は暗黙的に行われることも、明示的に指定されることもあります。
let result = "5" - 2;
console.log(result); // 3 (暗黙的に文字列が数値に変換されて計算される)
この場合、JavaScriptは文字列 "5"
を数値に自動で強制変換し、引き算が実行されています。
暗黙的な型変換 (Implicit Type Casting)
暗黙の型変換は、コンパイラやランタイムが自動的にデータ型を変換する場合に発生します。JavaScriptは「ゆるい型付け」の言語であり、多くの場合、演算子が自動的に値を適切なデータ型に変換します。
let result = "10" * 2;
console.log(result); // 20
この例では、文字列 "10"
が数値として扱われ、掛け算が実行されています。JavaScriptは自動的にデータ型を変換して処理しますが、これは時に意図しない結果を招くこともあります。
明示的な型変換 (Explicit Type Casting)
型キャスト(Type casting)とは、データをあるデータ型から別のデータ型に変換する際に、明示的に変換する型を指定することを意味します。明示的な型キャストは、データを他の変数と互換性を持たせるために行われることが一般的です。型キャストを行う方法の例としては、parseInt()
、parseFloat()
、toString()
などがあります。
let num = "123";
let intNum = parseInt(num); // 文字列を整数に変換
console.log(intNum); // 123
let floatNum = parseFloat("123.45"); // 文字列を浮動小数点数に変換
console.log(floatNum); // 123.45
let str = (456).toString(); // 数値を文字列に変換
console.log(str); // "456"
データ構造 (Data Structures)
データ構造とは、データを効率的にアクセスおよび変更できるように、整理、管理、保存するための形式のことです。JavaScriptには、 プリミティブ(組み込み) と 非プリミティブ(非組み込み) のデータ構造があります。
-
プリミティブデータ構造は、プログラミング言語に最初から含まれており、すぐに利用できるものです。例えば、配列(
array
)やオブジェクト(object
)がこれに該当します。 -
非プリミティブデータ構造は、JavaScriptには最初から含まれていないため、必要な場合は自分で実装する必要があります。例えば、グラフやツリーのようなデータ構造は自分で構築する必要があります。
プリミティブデータ構造 (配列)
let arr = [1, 2, 3, 4]; // 配列を定義
console.log(arr[0]); // 1
非プリミティブデータ構造 (スタックの実装)
class Stack {
constructor() {
this.items = [];
}
push(element) {
this.items.push(element);
}
pop() {
return this.items.pop();
}
}
let stack = new Stack();
stack.push(10);
stack.push(20);
console.log(stack.pop()); // 20
プリミティブデータ構造はすぐに使えますが、非プリミティブデータ構造は自分で作り上げる必要があります。
構造化されたデータ (Structured Data)
構造化データ(Structured data)は、Googleなどの検索エンジンがページの内容を理解したり、ウェブや世界に関する情報を収集したりするために使用されます。
このデータは、情報が適用されるページ上にあるインページマークアップを使用してコード化されます。例えば、商品情報やイベント情報などを検索エンジンが正確に認識し、検索結果に適切に表示するために使われます。
JSON
*JavaScript Object Notation(JSON)は、JavaScriptのオブジェクト構文に基づいた、構造化データを表現するための標準的なテキスト形式です。
ウェブアプリケーションでデータをやり取りする際によく使用されます。例えば、サーバーからクライアントにデータを送信してウェブページに表示する場合や、逆にクライアントからサーバーにデータを送信する際などに利用されます。
{
"name": "John",
"age": 30,
"city": "New York"
}
このように、JSONはキーと値のペアで構造化され、軽量で扱いやすい形式となっています。
キー付きのコレクション (Keyed Collections)
キー付きコレクション(Keyed collections)は、インデックスではなくキーによって順序付けられたデータコレクションのことです。これらは「連想的」な性質を持っており、キーと値のペアでデータを管理します。
JavaScriptでは、MapやSetオブジェクトがキー付きコレクションに該当し、これらは挿入された順序で反復処理(イテレーション)することができます。Mapはキーと値のペアを持ち、Setは重複しない値のコレクションを保持します。
Map
Mapは、オブジェクトと同様にキー付きデータアイテムのコレクションです。しかし、Mapとオブジェクトの主な違いは、Mapでは任意のデータ型をキーとして使用できる点です。オブジェクトでは、キーは基本的に文字列やシンボルである必要がありますが、Mapでは数値やオブジェクト、さらには関数など、あらゆる型のデータをキーにすることができます。
Mapとオブジェクトの違い
キーの型
- オブジェクト: 文字列またはシンボルがキーに使用される。
- Map: 任意の型がキーに使用できる(数値、オブジェクト、関数など)。
let map = new Map();
// 数値をキーに使用
map.set(1, 'one');
// オブジェクトをキーに使用
let objKey = { name: 'object' };
map.set(objKey, 'value associated with object');
console.log(map.get(1)); // 'one'
console.log(map.get(objKey)); // 'value associated with object'
Weak Map
WeakMapは、Mapに似たキーと値のペアのコレクションですが、いくつかの重要な違いがあります。特に、WeakMapのキーは必ずオブジェクトでなければならず、参照されなくなったキー(オブジェクト)はガベージコレクションによって自動的に削除されます。これは、メモリリークを防ぐための強力な機能です。
let weakMap = new WeakMap();
let obj = { name: 'Object' };
// オブジェクトをキーとしてセット
weakMap.set(obj, 'Some value');
console.log(weakMap.get(obj)); // 'Some value'
// objが他で参照されなくなると、WeakMapから自動的に削除される
obj = null;
// ガベージコレクションによってキー/値が削除される
Set
Setオブジェクトは、重複しない一意の値を保持できるデータ構造です。プリミティブ値やオブジェクト参照など、あらゆるタイプの値を格納することができます。Set内の値は、1回しか登場できず、同じ値が2回以上存在することはありません。これは、配列などで重複する要素を管理したくない場合に非常に便利です。
let mySet = new Set();
// 値を追加
mySet.add(1);
mySet.add(5);
mySet.add(1); // 既に存在するため無視される
console.log(mySet.size); // 2
// 値が存在するかチェック
console.log(mySet.has(1)); // true
console.log(mySet.has(3)); // false
// Setの値を反復処理
mySet.forEach(value => {
console.log(value); // 1, 5
});
Setの用途
- 重複を防ぐ: 例えば、配列内の重複を取り除くときに役立ちます。
- 一意の要素の集合: 一意の値の集合を扱いたいとき(例えば、タグやカテゴリのリスト)に適しています。
let numbers = [1, 2, 2, 3, 4, 4, 5];
let uniqueNumbers = new Set(numbers);
console.log([...uniqueNumbers]); // [1, 2, 3, 4, 5]
Weak Set
WeakSetオブジェクトは、Setに似たデータ構造で、オブジェクトのコレクションを管理します。WeakSetに格納されるオブジェクトは、一意で重複することはありませんが、いくつかの重要な違いがあります。
let weakset = new WeakSet();
let obj1 = { name: 'Object 1' };
let obj2 = { name: 'Object 2' };
weakset.add(obj1);
weakset.add(obj2);
weakset.add(obj1); // obj1は既に存在するため無視される
console.log(weakset.has(obj1)); // true
console.log(weakset.has({})); // false - 新しいオブジェクトは別物とみなされる
// obj1への参照を削除
obj1 = null;
// ガベージコレクションによって、obj1はWeakSetから自動的に削除される
console.log(weakset.has(obj1)); // false
インデックス付きコレクション (Indexed Collections)
インデックス付きコレクションとは、数値のインデックス(添字)によってデータを管理するコレクションのことです。JavaScriptでは、配列(Array) がこのインデックス付きコレクションに該当します。
let arr = [10, 20, 30, 40, 50]; // 数値を含む配列
console.log(arr[0]); // 10 - 最初の要素
console.log(arr[2]); // 30 - 3番目の要素
arr[2] = 35; // 3番目の要素を変更
console.log(arr); // [10, 20, 35, 40, 50]
arr.push(60); // 新しい要素を末尾に追加
console.log(arr); // [10, 20, 35, 40, 50, 60]
arr.pop(); // 最後の要素を削除
console.log(arr); // [10, 20, 35, 40, 50]
型付きの配列 (Typed Arrays)
JavaScriptにおけるTypedArray(型付き配列)は、バイナリデータのバッファを効率的に操作するための配列のようなデータ構造です。
通常のJavaScript配列とは異なり、TypedArrayは特定の型に基づいてデータを格納し、サイズが固定されたバッファ上に連続的にメモリが割り当てられます。
TypedArrayという名前のプロパティやオブジェクト自体は存在しませんが、いくつかの異なる型付き配列クラス(Int8Array
, Uint8Array
, Float32Array
など)があります。これらは、特定の数値型に特化した配列を操作するために使用され、バイナリデータを効率的に扱うことができます。
Uint8Array
の使用例
let buffer = new ArrayBuffer(8); // 8バイトのバッファを作成
let view = new Uint8Array(buffer); // バッファに基づくUint8Arrayを作成
view[0] = 255; // 1バイト目に255を設定
view[1] = 128; // 2バイト目に128を設定
console.log(view); // Uint8Array(8) [255, 128, 0, 0, 0, 0, 0, 0]
使用用途
TypedArrayは、特にWebGL(グラフィックス描画)、バイナリデータの読み書き、パフォーマンスを重視するアプリケーションで使用されます。配列のデータ型が明確であるため、通常の配列よりも処理が高速になることが多く、例えば画像処理や音声・動画の操作などの用途にも適しています。
配列 (Arrays)
配列(Array)は、項目のコレクションを格納するオブジェクトであり、変数に割り当てることができます。配列は、数値や文字列、オブジェクトなど、任意のデータ型の値を要素として持つことができ、その要素に対して様々な操作を行うためのメソッドを持っています。
配列の特徴
可変長: 配列は、必要に応じて要素を追加したり削除したりすることができます。
インデックスアクセス: 配列の要素には、0から始まるインデックスを使ってアクセスします。
多様なデータ型: 配列内の要素は異なるデータ型を持つことができ、例えば文字列、数値、オブジェクト、さらには他の配列を含むこともできます。
配列のメソッド
JavaScriptの配列には、多くの便利なメソッドがあります。以下はその一部です。
-
push()
: 配列の末尾に1つ以上の要素を追加します。let fruits = ['apple', 'banana']; fruits.push('orange'); // ['apple', 'banana', 'orange']
-
pop()
: 配列の末尾から要素を削除し、その要素を返します。let lastFruit = fruits.pop(); // 'orange'
-
shift()
: 配列の最初の要素を削除し、その要素を返します。let firstFruit = fruits.shift(); // 'apple'
-
unshift()
: 配列の最初に1つ以上の要素を追加します。fruits.unshift('grape'); // ['grape', 'banana']
-
map()
: 各要素に対して指定した関数を実行し、新しい配列を返します。let numbers = [1, 2, 3]; let doubled = numbers.map(num => num * 2); // [2, 4, 6]
-
filter()
: 条件を満たす要素のみを含む新しい配列を返します。let evens = numbers.filter(num => num % 2 === 0); // [2]
等価比較 (Equality Comparisons)
比較演算子(Comparison Operators)は、論理式において変数や値の等しさや違いを判断するために使用されます。これらの演算子は、条件文(if文など)で値を比較し、その結果に応じてアクションを取るために使われます。
==
JavaScriptにおける比較演算子には、==
(抽象等価演算子)、===
(厳密等価演算子)、および Object.is()
メソッドがあります。これらの違いを理解することは、予期しない結果を避けるために重要です。
==
演算子は、比較する前にオペランドの型を変換します。このため、異なる型の値を比較することができます。
console.log("" == false); // true
空文字列は false
に型変換されるため、結果は true
になります。
===
JavaScriptにおいて、厳密等価演算子(===
)は二つのオペランドの値と型を比較します。つまり、両方の値と型が同一である場合にのみtrue
を返します。
"5" === "5" // true
この場合、値と型の両方が同じであるため、結果はtrue
になります。
"5" === 5 // false
ここでは、見た目は似ている値でも、型が異なる(文字列と数値)ため、結果はfalse
になります。厳密等価演算子は型の強制変換を行わず、値と型の両方が一致している必要があります。
Object.js
Object.is()
メソッドは、二つの値が同じ値であるかどうかを判断します。
console.log(Object.is('1', 1)); // 期待される出力: false
この場合、文字列'1'
と数値1
は異なる型であるため、結果はfalse
になります。
console.log(Object.is(NaN, NaN)); // 期待される出力: true
ここでは、NaN
は自分自身と等しいと評価されるため、結果はtrue
になります。
console.log(Object.is(-0, 0)); // 期待される出力: false
この例では、-0
と0
は異なると見なされるため、結果はfalse
になります。
const obj = {};
console.log(Object.is(obj, {})); // 期待される出力: false
この場合、二つの空のオブジェクトは異なるオブジェクトであるため、結果はfalse
になります。
isLooselyEqual /
isStrictlyEqual /
Same value zero /
Same value /
ループと反復 (Loops and Iterations)
ループは、同じ処理を繰り返すための簡単で効率的な方法を提供します。
ループを、誰かに「Xステップ進む」「Yステップ進む」といった指示を繰り返し出すゲームのコンピュータ版と考えることができます。例えば、「東に5歩進む」というアイデアは次のようにループで表現できます。
for (let step = 0; step < 5; step++) {
// 0から4までのstep値で、合計5回実行される。
console.log('東に1歩進む');
}
このコードでは、for
ループが5回繰り返され、step
が0から4まで増加しながら実行されます。それぞれの繰り返しで「東に1歩進む」と表示されるのです。
for
for
ループは、多くのプログラミング言語、特にJavaScriptにおいて一般的な制御フロー構文です。特定のシーケンスを反復したり、決まった回数だけ処理を繰り返したりするためによく使われます。各イテレーションでコードを実行することができます。
for (let i = 0; i < 5; i++) {
console.log(i); // 0から4までの値が出力される
}
この例では、ループは i
の値が0から始まり、5未満の間繰り返し処理されます。
do ... while
do...while
文は、指定された条件が false
になるまでループを実行する構文です。特徴的なのは、条件の評価が処理の後に行われるため、指定された処理が必ず1回は実行されるという点です。
let count = 0;
do {
console.log(count);
count++;
} while (count < 3);
while
while
文は、指定された条件が true
である限り、繰り返し処理を実行するループ構文です。条件は、処理が実行される前に評価されるため、条件が初めから false
の場合は一度も実行されません。
let count = 0;
while (count < 3) {
console.log(count);
count++;
}
break / continue
break
文は、ループまたは switch
文の実行を途中で中断して抜けるために使用されます。label
を使わない場合、単にループや switch
ブロックを終了させます。
for (let i = 0; i < 5; i++) {
if (i === 3) {
break; // i が 3 のときループを抜ける
}
console.log(i); // 0, 1, 2 が出力される
}
この例では、i
が 3 になった時点で break
が実行され、ループが終了します。
continue
文は、現在のループの1回のイテレーションをスキップし、次のイテレーションを開始するために使用されます。
for (let i = 0; i < 5; i++) {
if (i === 3) {
continue; // i が 3 のとき現在のイテレーションをスキップ
}
console.log(i); // 0, 1, 2, 4 が出力される
}
この例では、i
が 3 になったときに continue
が実行され、その時の console.log(i)
の部分はスキップされますが、ループ自体は続行されます。
for ... of loop
for...of
文は、イテラブルオブジェクトから値を順番に取得し、それぞれの値に対して処理を行うためのループを実行します。for...of
は、次のようなイテラブルオブジェクトに対して使用できます。
- Array(配列)
- String(文字列)
- TypedArray(型付き配列)
- Map
- Set
- NodeList(DOM コレクション)
- arguments オブジェクト(関数内の引数リスト)
- ジェネレータ関数によって生成されたジェネレータ
- ユーザー定義のイテラブルオブジェクト
// 配列に対する for...of ループ
const array = ['apple', 'banana', 'cherry'];
for (const fruit of array) {
console.log(fruit);
}
// 出力: apple, banana, cherry
// 文字列に対する for...of ループ
const str = "hello";
for (const char of str) {
console.log(char);
}
// 出力: h, e, l, l, o
for ... in loop
for...in
文は、オブジェクトのすべての列挙可能なプロパティを反復処理します。
このプロパティは、文字列でキーが指定されたものが対象で、Symbol
でキーが指定されたプロパティは無視されます。また、オブジェクトが継承した列挙可能なプロパティも対象となります。
for…in 文は、継承された列挙可能なプロパティを含む、文字列によってキー付けされるオブジェクトのすべての列挙可能なプロパティ (シンボルによってキー付けされるプロパティは無視されます) を反復処理します。
const person = { name: 'Alice', age: 25 };
for (const key in person) {
console.log(key, person[key]);
}
// 出力:
// name Alice
// age 25
制御フロー (Control Flow)
JavaScriptにおける制御フローは、コンピュータがコードを上から下へ実行する方法を指します。最初の行から始まり、最後の行で終了しますが、ループや条件文など、プログラムの制御フローを変更するステートメントに遭遇すると、そのフローが変わります。
条件文 (Conditional Statements)
コードを書く際には、異なる判断に対して異なるアクションを実行したくなることがよくあります。JavaScriptでは、これを実現するために条件文を使用します。
if ... else
if
文は、指定された条件が真である場合にステートメントを実行します。条件が偽である場合、オプションの else
節内の別のステートメントが実行されます。
let score = 75;
if (score >= 60) {
console.log("合格です。"); // 条件が真の場合に実行
} else {
console.log("不合格です。"); // 条件が偽の場合に実行
}
Switch
switch
文は、式を評価し、その値を一連の case
節と照合します。最初に一致する case
節の後にあるステートメントを実行し、break
ステートメントに出会うまで続けます。switch
文の default
節は、どの case
も式の値に一致しない場合に実行されます。
let fruit = "りんご";
switch (fruit) {
case "バナナ":
console.log("バナナを選びました。");
break;
case "りんご":
console.log("りんごを選びました。"); // 一致した場合に実行
break;
case "オレンジ":
console.log("オレンジを選びました。");
break;
default:
console.log("その他の果物を選びました。");
break;
}
例外処理 (Exceptional Handling)
JavaScriptでは、すべての例外は単なるオブジェクトです。
ほとんどの例外はグローバルな Error
クラスの実装ですが、任意のオブジェクトをスローすることもできます。このことを考慮すると、例外をスローする方法は2つあります。
Error
オブジェクトを直接スローする方法と、カスタムオブジェクトをスローする方法です。
例外をスローする方法
Errorオブジェクトを使用してスローする
throw new Error("これはエラーメッセージです。");
カスタムオブジェクトを使用してスローする
const customError = { message: "カスタムエラーです。" };
throw customError;
このように、JavaScriptでは例外を柔軟にスローでき、エラーの詳細情報を提供することが可能です。例外が発生した場合には、try...catch
文を使用して適切に処理することができます。
Throw Statement
throw
文はユーザー定義の例外をスローします。
現在の関数の実行は停止し(throw
の後の文は実行されません)、制御は呼び出しスタック内の最初の catch
ブロックに渡されます。もし呼び出し元の関数に catch
ブロックが存在しない場合、プログラムは終了します。
function exampleFunction() {
throw new Error("ユーザー定義のエラーが発生しました");
}
try {
exampleFunction(); // 例外をスロー
} catch (e) {
console.log(e.message); // "ユーザー定義のエラーが発生しました"
}
Try, Catch, Finally
これらは、JavaScript コード内でエラーを処理する方法です。
try
コードブロック内には実行するコードがあり、catch
ブロック内ではエラーを処理します。
また、finally
ブロック内には、前のコードブロックの実行結果に関係なく実行されるコードがあります。
function exampleFunction() {
try {
// 実行するコード
console.log("コードを実行中...");
throw new Error("エラーが発生しました"); // エラーをスロー
} catch (error) {
// エラーを処理
console.log("エラーキャッチ:", error.message);
} finally {
// 最後に実行されるコード
console.log("このコードは必ず実行されます。");
}
}
exampleFunction();
Utilizing error objects
ランタイムエラーが発生すると、新しい Error
オブジェクトが作成されてスローされます。この Error
オブジェクトを使用して、エラーの種類を特定し、それに応じて適切に処理することができます。
エラーの種類
JavaScript には、エラーコンストラクター以外にもいくつかのコアエラーコンストラクターがあります。
- AggregateError: 同時にスローされたエラーのコレクション。
- EvalError: JavaScript 式の評価中にエラーが発生した。
- InternalError: JavaScript 内部のエラーで、エンジンのバグを示すことが多い。
- RangeError: 値が特定の操作に対して許可された範囲外である。
- ReferenceError: 変数やオブジェクトが宣言される前に参照された、または存在しない。
- SyntaxError: コードに誤った構文が含まれており、パースできない。
try {
willGiveErrorSometime(); // エラーを発生させる関数
} catch (error) {
if (error instanceof RangeError) {
rangeErrorHandler(error); // RangeErrorの場合の処理
} else if (error instanceof ReferenceError) {
referenceErrorHandler(error); // ReferenceErrorの場合の処理
} else {
errorHandler(error); // その他のエラーの場合の処理
}
}
式と演算子 (Expressions & Operators)
式(Expression)は、値を解決する有効なコードの単位を指します。式には主に二つのタイプがあります。
副作用を持つ式(例えば、値を代入する場合)と、純粋に評価を行う式です。
副作用のある式の例 x = 7
ここでは、=
演算子を使用して変数 x
に値7を代入しています。この式自体は、7という値を評価します。
純粋に評価を行う式の例 3 + 4
この式は、+
演算子を使って3と4を足し合わせ、その結果として7を生成します。
ただし、式が最終的により大きな構造の一部(例えば、const z = 3 + 4
のような変数宣言)にならない場合、その結果はすぐに破棄されます。これは通常、評価が効果を生まないため、プログラマーのミスとされます。
上記の例からも分かるように、すべての複雑な式は、=
や +
のような演算子によって結びつけられています。
条件演算子 (Conditional operators)
条件演算子(別名三項演算子)は、JavaScriptで唯一、三つのオペランドを取る演算子です。この演算子は、条件に基づいて二つの値のいずれかを返すことができます。
let age = 18;
let canVote = (age >= 18) ? "Yes" : "No";
console.log(canVote); // 出力: "Yes"
コンマ演算子 (Comma operators)
カンマ演算子(,)は、そのオペランドを左から右に評価し、最後のオペランドの値を返します。これにより、複数の式が評価される複合式を作成でき、複合式の最終的な値は、そのメンバー式の右端の値になります。この演算子は、主にforループに複数のパラメータを提供する際に使用されます。
let a = 1, b = 2, c = 3;
let result = (a += 1, b += 2, c += 3); // 各オペランドを評価する
console.log(result); // 出力: 6 (cの値)
console.log(a); // 出力: 2
console.log(b); // 出力: 4
console.log(c); // 出力: 6
この例では、a
, b
, c
の値がそれぞれ増加し、最終的に c
の値である 6 が result
に代入されます。カンマ演算子を使用することで、一つの文で複数の操作を実行することができますが、可読性が低下することがあるため注意が必要です。
単項演算子 (Unary Operators)
JavaScriptの単項演算子(Unary Operators)は、単一のオペランドを考慮し、そのオペランドに対してさまざまな操作を実行する特別な演算子です。これらの演算子には、単項プラス、単項マイナス、前置インクリメント、後置インクリメント、前置デクリメント、後置デクリメントが含まれます。
単項プラス(Unary Plus)
値を数値に変換します。もしオペランドがすでに数値であれば、そのままの値が返ります。
let x = '5';
let result = +x; // 5 (文字列を数値に変換)
単項マイナス(Unary Minus)
値を数値に変換し、符号を反転させます。
let x = 5;
let result = -x; // -5
前置インクリメント(Prefix Increment)
オペランドの値を1増加させ、増加させた値を返します。
let x = 5;
let result = ++x; // xは6になる、resultも6
後置インクリメント(Postfix Increment):
オペランドの値を返し、その後に値を1増加させます。
let x = 5;
let result = x++; // resultは5、xは6になる
前置デクリメント(Prefix Decrement):
オペランドの値を1減少させ、減少させた値を返します。
let x = 5;
let result = --x; // xは4になる、resultも4
後置デクリメント(Postfix Decrement):
オペランドの値を返し、その後に値を1減少させます。
let x = 5;
let result = x--; // resultは5、xは4になる
関係演算子 (Relational Operators)
関係演算子(Relational Operators)は、比較演算子とも呼ばれ、2つの値の関係を見つけたり、それらの関係を比較するために使用されます。比較の結果は真(true)または偽(false)になります。
主な関係演算子の種類
大なり(>)
左の値が右の値より大きい場合、trueを返します。
console.log(5 > 3); // true
小なり(<)
左の値が右の値より小さい場合、trueを返します。
console.log(5 < 3); // false
大なりイコール(>=)
左の値が右の値より大きいか、または等しい場合、trueを返します。
console.log(5 >= 5); // true
小なりイコール(<=)
左の値が右の値より小さいか、または等しい場合、trueを返します。
console.log(5 <= 3); // false
等しい(==)
左の値と右の値が等しい場合、trueを返します。ただし、型変換が行われるため、型が異なっていても同じ値であればtrueになります。
console.log(5 == '5'); // true
厳密に等しい(===)
左の値と右の値が等しく、かつ同じ型である場合にtrueを返します。型変換は行われません。
console.log(5 === '5'); // false
代入演算子 (Assignment Operators)
代入演算子は、右オペランドの値を基に、左オペランドに値を割り当てる演算子です。
最も基本的な代入演算子は =
(イコール)で、右オペランドの値を左オペランドに代入します。
つまり、x = f()
という代入式は、関数 f()
の結果を x
に代入することを意味します。
let x;
x = 5; // 右オペランド5を左オペランドxに代入
JavaScriptには他にも様々な代入演算子があり、代入と同時に他の演算も行います。
加算代入演算子(+=
)
左オペランドに右オペランドの値を加算し、その結果を左オペランドに代入します。
let a = 10;
a += 5; // a = a + 5 と同じ意味。結果は a = 15
減算代入演算子(-=
)
左オペランドから右オペランドの値を減算し、その結果を左オペランドに代入します。
let b = 10;
b -= 3; // b = b - 3 と同じ意味。結果は b = 7
乗算代入演算子(*=
)
左オペランドに右オペランドを掛け、その結果を左オペランドに代入します。
let c = 10;
c *= 2; // c = c * 2 と同じ意味。結果は c = 20
除算代入演算子(/=
)
左オペランドを右オペランドで割り、その結果を左オペランドに代入します。
let d = 10;
d /= 2; // d = d / 2 と同じ意味。結果は d = 5
剰余代入演算子(%=
)
左オペランドを右オペランドで割った余りを左オペランドに代入します。
let e = 10;
e %= 3; // e = e % 3 と同じ意味。結果は e = 1
比較演算子 (Comparison Operators)
比較演算子(Comparison operators)は、値を比較して、その結果を true
もしくは false
として返す演算子です。JavaScriptでは、以下のような比較演算子が使用されます。
>
(より大きい)
左オペランドが右オペランドより大きい場合、true
を返します。
5 > 3; // true
<
(より小さい)
左オペランドが右オペランドより小さい場合、true
を返します。
3 < 5; // true
>=
(以上)
左オペランドが右オペランド以上であれば、true
を返します。
5 >= 5; // true
<=
(以下)
左オペランドが右オペランド以下であれば、true
を返します。
3 <= 5; // true
==
(等しい)
値が等しければ true
を返します(型の違いは無視される)。型変換が行われるため、予期せぬ結果になることがあります。
'5' == 5; // true (文字列と数値が型変換されて比較される)
===
(厳密な等しさ)
値と型の両方が等しい場合に true
を返します。型変換は行われません。
'5' === 5; // false (型が違うため)
!=
(等しくない)
値が等しくない場合に true
を返します(型の違いは無視される)。
'5' != 5; // false (型変換が行われるため、等しいとみなされる)
!==
(厳密な等しくなさ)
値または型が異なる場合に true
を返します。型変換は行われません。
'5' !== 5; // true (型が異なるため)
算術演算子 (Arithmetic operators)
算術演算子(Arithmetic operators)は、足し算、引き算、掛け算、割り算、べき乗、剰余などの基本的な数値計算を行うために使用されます。JavaScriptでは、次の算術演算子が使えます。
+
(足し算)
左オペランドと右オペランドの和を計算します。
5 + 3; // 8
-
(引き算)
左オペランドから右オペランドを引きます。
5 - 3; // 2
*
(掛け算)
左オペランドと右オペランドの積を計算します。
5 * 3; // 15
**
(べき乗)
左オペランドを右オペランドの指数で累乗します。
5 ** 2; // 25
/
(割り算)
左オペランドを右オペランドで割ります。
6 / 3; // 2
%
(剰余、余り)
左オペランドを右オペランドで割った余りを返します。
5 % 2; // 1
++
(インクリメント)
オペランドの値を1増やします。前置と後置で挙動が異なります。
let x = 5;
x++; // 5 (先に値が返され、後で x が 6 に増える)
++x; // 6 (先に x が 6 に増えてから値が返される)
--
(デクリメント)
オペランドの値を1減らします。前置と後置で挙動が異なります。
let x = 5;
x--; // 5 (先に値が返され、後で x が 4 に減る)
--x; // 4 (先に x が 4 に減ってから値が返される)
ビット演算子 (Bitwise operators)
ビット演算子(Bitwise operators)は、引数を32ビットのバイナリ表現として扱い、そのバイナリ表現に基づいて演算を行います。例えば、10進数の9は2進数で表すと「1001」となります。ビット演算子はこのような2進数のビットに対して操作を行い、結果は標準的なJavaScriptの数値として返されます。
&
(ビットAND)
両方のビットが1であれば1を返します。つまり、ビットごとにANDを行います。
5 & 3; // 1 (5 = 101, 3 = 011, 101 & 011 = 001)
|
(ビットOR)
いずれかのビットが1であれば1を返します。
5 | 3; // 7 (5 = 101, 3 = 011, 101 | 011 = 111)
^
(ビットXOR)
両方のビットが異なれば1を返し、同じであれば0を返します。
5 ^ 3; // 6 (5 = 101, 3 = 011, 101 ^ 011 = 110)
~
(ビットNOT)
ビットを反転します。各ビットを0から1に、1から0に反転します。
~5; // -6 (5 = 00000000000000000000000000000101, ~5 = 11111111111111111111111111111010 = -6)
<<
(左シフト)
左に指定されたビット数だけビットをシフトします。シフトされた部分はゼロで埋められます。
5 << 1; // 10 (5 = 101, 左に1ビットシフトすると1010 = 10)
>>
(右シフト)
右に指定されたビット数だけビットをシフトします。符号ビットを保持しながらシフトします。
5 >> 1; // 2 (5 = 101, 右に1ビットシフトすると010 = 2)
>>>
(ゼロ埋め右シフト)
符号ビットを無視して、ゼロで埋めながら右にシフトします。
-5 >>> 1; // 2147483645 (負の数をゼロ埋めでシフト)
論理演算子 (Logical Operators)
JavaScriptには4つの論理演算子があり、それぞれ特定の条件に基づいて真偽値を操作します。
||
(論理OR)
どちらか一方が真であれば真を返します。両方が偽の場合のみ偽を返します。左側のオペランドが真の場合、右側は評価されません(短絡評価)。
true || false; // true
false || false; // false
&&
(論理AND)
両方とも真であれば真を返します。いずれか一方が偽の場合、偽を返します。左側のオペランドが偽の場合、右側は評価されません(短絡評価)。
true && true; // true
true && false; // false
!
(論理NOT)
真偽値を反転させます。真は偽に、偽は真になります。
!true; // false
!false; // true
??
(Nullish Coalescing)
左側のオペランドが null
または undefined
の場合に限り、右側のオペランドを返します。それ以外の場合は左側のオペランドを返します。この演算子は、null
や undefined
に対するデフォルト値を設定する際に便利です。
let x = null;
let y = x ?? 'default'; // 'default'
let z = 0;
let w = z ?? 'default'; // 0
ビッグ整数演算子 (BigInt Operators)
多くの演算子は、Number
型データに使える場合と同様に、BigInt
型にも使用できます(例:算術演算、比較演算など)。ただし、いくつかの例外や挙動の違いがあります。
符号なし右シフト演算子 >>>
は、BigInt
に対して使用できません。これは BigInt
が任意の精度を持つ整数型であり、32ビットの固定幅に基づく演算とは異なるためです。
挙動の違い
除算演算子 /
による BigInt
の割り算では、結果がゼロに向かって丸められます。つまり、結果が小数の場合、小数部分は切り捨てられます。Number
型では通常の浮動小数点演算が行われますが、BigInt
では整数値の結果しか返りません。
let x = 10n;
let y = 3n;
console.log(x / y); // 3n (小数点以下切り捨て)
文字列演算子 (String Operators)
比較演算子に加えて、連結演算子 +
は、2つの文字列を結合し、結合された新しい文字列を返します。これは、2つのオペランド文字列の結合を意味します。
また、省略形代入演算子 +=
も文字列の結合に使用できます。これは、左側の変数に右側の文字列を追加して再代入する操作を行います。
let str1 = "Hello, ";
let str2 = "World!";
let result = str1 + str2;
console.log(result); // "Hello, World!"
let greeting = "Hello";
greeting += ", World!";
console.log(greeting); // "Hello, World!"
関数 (Functions)
関数は、コードを再利用するために存在します。
関数は、呼び出されるたびに実行されるコードのブロックです。
通常、各関数は特定のタスクを実行するために記述されます。
例えば、複数の数値の合計を計算するための「加算関数」を作成できます。
コードの任意の場所で数値を加算する必要がある場合、この加算関数を必要な回数だけ呼び出すことができます。
function add(a, b) {
return a + b;
}
// 関数の呼び出し
console.log(add(3, 4)); // 7
console.log(add(10, 20)); // 30
関数の引数 (Function Parameters)
パラメーターとは、関数定義の内部で宣言された変数に与えられる名前のことです。関数が呼び出されたときに、引数として渡された値がパラメーターに割り当てられます。JavaScript では、パラメーターに対して2つの特殊な構文が用意されています。それがデフォルトパラメーターとレストパラメーターです。
基本的な引数 (Default Parameters)
デフォルト関数パラメーターは、引数として値が渡されなかったり、undefined
が渡された場合に、名前付きパラメーターにデフォルト値を初期化することを可能にします。この機能により、関数を呼び出す際に、必ずしもすべての引数を指定する必要がなくなります。
function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(5)); // 5 (bが指定されていないため、デフォルト値1が使用される)
console.log(multiply(5, 2)); // 10 (bには2が渡される)
残余引数 (Rest Parameters)
レストパラメーター構文を使用すると、関数が不定数の引数を配列として受け取ることができ、JavaScriptにおける可変長引数(variadic functions)を表現する方法を提供します。この機能により、関数が任意の数の引数を受け入れ、それらを1つの配列として扱うことができます。
function sum(...numbers) {
return numbers.reduce((acc, current) => acc + current, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(5, 10, 15, 20)); // 50
console.log(sum()); // 0 (引数がない場合)
アロー関数 (Arrow Functions)
アロー関数は、=>
演算子を使用して関数を作成する新しい方法で、より短い構文を提供します。
アロー関数は、従来の関数定義と比較して、いくつかの利点があります。例えば、this
の挙動が異なり、親のスコープから this
を継承します。
const sayHello = () => {
console.log(`Hello from Arrow Function!`);
};
sayHello(); // 出力: Hello from Arrow Function!
即時実行関数 (IIFE)
Immediately-Invoked Function Expression (IIFE) は、定義されると同時に実行される関数のことを指します。通常、IIFEはプライベートスコープを作成し、変数や関数を外部に露出させずにコードを実行するために使用されます。以下に、IIFE の概要と例を示します。
IIFE の特徴
即時実行: 定義されると同時に実行されるため、結果をすぐに得ることができます。
プライベートスコープ: IIFE 内で宣言された変数や関数は、外部からアクセスできず、他のコードとの衝突を避けることができます。
IIFE の構文
IIFE は通常、関数を丸括弧で囲み、その後に別の丸括弧を続けて呼び出します。
(function() {
// ここにコードを書く
})();
IIFE の例
次の例では、IIFE を使用して変数 x
と y
の合計を計算し、その結果をコンソールに出力します。
// 通常の IIFE
(function() {
const x = 1;
const y = 9;
console.log(`Hello, The Answer is ${x + y}`);
})();
// Async IIFE
(async () => {
const x = 1;
const y = 9;
console.log(`Hello, The Answer is ${x + y}`);
})();
引数オブジェクト (Arguments object)
arguments
オブジェクトは、関数内でアクセス可能な配列のようなオブジェクトで、呼び出された関数に渡された引数の値を含んでいます。すべての非アロー関数内で利用でき、関数に渡された引数の数に応じてエントリーが作成され、最初のエントリーのインデックスは 0 から始まります。
特徴
配列のようなオブジェクト: arguments
は配列ではありませんが、配列のようにインデックスを使ってアクセスできます。
すべての引数を含む: 引数がいくつであっても、arguments
オブジェクトにはそのすべての値が入ります。
非アロー関数での使用: アロー関数では arguments
オブジェクトは使用できません。
使用例
function exampleFunction() {
console.log(arguments); // 引数の全リストを出力
console.log(arguments[0]); // 最初の引数を出力
console.log(arguments.length); // 引数の数を出力
}
exampleFunction(1, 2, 3); // 引数として 1, 2, 3 を渡す
注意点
現代的なコードでは rest パラメータを推奨: arguments
オブジェクトは配列のメソッドを持っていないため、配列の機能を利用したい場合には ...rest
構文を使用することが推奨されます。
Rest パラメータの例
function exampleFunction(...args) {
console.log(args); // 引数の配列を出力
console.log(args[0]); // 最初の引数を出力
console.log(args.length); // 引数の数を出力
}
exampleFunction(1, 2, 3); // 引数として 1, 2, 3 を渡す
スコープと関数スタック (Scope and function stack)
スコープ (Scope)
スコープとは、特定の変数や関数がアクセスまたは使用できる空間や環境を指します。これらの変数や関数へのアクセスは、それらが定義されている場所に依存します。
JavaScript には以下のようなスコープがあります
グローバルスコープ (Global Scope)
スクリプトモードで実行されるすべてのコードのデフォルトのスコープです。グローバルスコープで定義された変数や関数は、どこからでもアクセスできます。
モジュールスコープ (Module Scope)
モジュールモードで実行されるコードのスコープです。モジュール内で定義された変数や関数は、そのモジュール内からのみアクセス可能です。
関数スコープ (Function Scope)
関数内で作成されるスコープです。関数内で定義された変数は、その関数内からのみアクセスできます。
ブロックスコープ (Block Scope)
中括弧(ブロック)で作成されるスコープです。let
や const
で宣言された変数は、ブロック内からのみアクセス可能です。
関数スタック (Call Stack)
関数スタックは、インタプリタが複数の関数を呼び出すスクリプトの実行状態を追跡する方法です。具体的には、どの関数が現在実行中で、どの関数がその関数内で呼び出されているかを管理します。これにより、関数の呼び出し順序やネストされた関数の実行状況を把握できます。
関数スタックの動作の例
- 最初にメイン関数が呼び出されると、それがスタックの一番上に積まれます。
- その中で別の関数が呼び出されると、その関数もスタックに積まれます。
- 最初の関数が終了すると、スタックから取り出され、次に積まれている関数が実行されます。
再起 (Recursion)
再帰とは、関数が自分自身を呼び出す強力でエレガントな概念です。このような関数を「再帰関数」と呼びます。再帰が行われると、再帰関数の内部コードは基底条件(ベースケース)を満たすまで何度も実行されます。
基底条件 (Base Case)
再帰関数には、再帰の実行を停止するための条件が必要です。この条件を「基底条件」と呼びます。基底条件が満たされない限り、関数は自分自身を再度呼び出し続けます。これにより、無限ループを防ぎ、再帰の終わりを設定します。
function factorial(n) {
// 基底条件
if (n === 0) {
return 1; // 0! は 1 と定義されている
} else {
return n * factorial(n - 1); // 自分自身を呼び出す
}
}
console.log(factorial(5)); // 出力: 120
静的スコープ (Lexical scoping)
クロージャ (Closures) とレキシカル環境 (Lexical Environment)
JavaScriptにおけるクロージャを理解する前に、まず「レキシカル環境」という用語を把握することが重要です。簡単に言えば、関数f
のレキシカル環境は、その関数の定義を囲む環境を指します。
レキシカル環境とは
レキシカル環境は、関数の定義が行われた場所や、その関数がアクセスできる変数のスコープを指します。
- 環境レコード (Environment Record) 変数や関数の情報を格納する場所。
- 外部レキシカル環境への参照 (Reference to the Outer Lexical Environment) 親のスコープに対する参照。
クロージャ (Closures)
関数クロージャ (Function Closures) の理解
関数クロージャは、JavaScriptの最も強力でありながら誤解されがちな概念の一つで、実際には非常にシンプルに理解できます。クロージャとは、ある関数とそのレキシカル環境を指します。これは、関数Aを別の関数Bから返す際に、Bで定義されたローカル変数をBが終了した後でも記憶していることを可能にします。
クロージャの基本概念
クロージャの定義 クロージャは、関数とその周囲の環境(スコープ)を結びつけるもので、関数が自らのスコープを「記憶」します。
ローカル変数の保持 クロージャを利用することで、関数が呼び出された際のローカル変数の状態を保持できます。
function outerFunction() {
let outerVariable = 'I am outside!'; // 外部関数の変数
function innerFunction() {
console.log(outerVariable); // 外部変数にアクセス
}
return innerFunction; // 内部関数を返す
}
const closure = outerFunction(); // クロージャを取得
closure(); // 出力: "I am outside!"
組み込み関数 (Built in functions)
メソッドの定義
JavaScriptにおいて、メソッドは関数定義を含むプロパティです。言い換えれば、オブジェクトに格納されているデータが関数である場合、それをメソッドと呼びます。
プロパティとメソッドの違い
プロパティ オブジェクトが持つもの。
メソッド オブジェクトが行うこと。
JavaScriptのオブジェクトとメソッド
JavaScriptのメソッドは、オブジェクトに対して実行できるアクションであり、そのためにはまずオブジェクトを持っている必要があります。JavaScriptには、使用できるいくつかの組み込みオブジェクトがあり、それらには多くの便利なメソッドが含まれています。
主なビルトインオブジェクトとメソッド
Array
-
push()
: 配列の末尾に要素を追加します。 -
pop()
: 配列の末尾の要素を削除して、その要素を返します。 -
map()
: 各要素に対して指定した関数を適用し、新しい配列を生成します。
const numbers = [1, 2, 3];
numbers.push(4); // [1, 2, 3, 4]
const doubled = numbers.map(num => num * 2); // [2, 4, 6, 8]
String
-
toUpperCase()
: 文字列をすべて大文字に変換します。 -
substring()
: 指定した範囲の文字列を抽出します。
const greeting = "hello";
console.log(greeting.toUpperCase()); // "HELLO"
console.log(greeting.substring(1, 4)); // "ell"
Math
-
Math.random()
: 0以上1未満のランダムな数を生成します。 -
Math.max()
: 引数の中から最大値を返します。
console.log(Math.random()); // 0と1の間のランダムな数
console.log(Math.max(1, 5, 2)); // 5
Date
-
Date.now()
: 現在のタイムスタンプをミリ秒で返します。 -
getFullYear()
: 年を取得します。
const now = new Date();
console.log(now.getFullYear()); // 現在の年
DOM APIs
HTMLのDOM(Document Object Model)を使用すると、JavaScriptはHTMLドキュメント内のすべての要素にアクセスし、属性やCSSスタイルを変更したり、要素を削除したり、ページ上に新しい要素を追加・作成したりできます。
Web APIは、ウェブ向けのアプリケーション・プログラミング・インターフェースを意味します。すべてのブラウザには、複雑な操作をサポートし、データにアクセスしやすくするための組み込みのWeb APIが用意されています。たとえば、ジオロケーションAPI、Webストレージ、ブラウザ履歴などが含まれます。
厳格モード (Strict Mode)
JavaScriptのstrict mode(厳格モード)は、JavaScriptの制限されたバリアント(変種)を選択する方法であり、それによって「sloppy mode」(非厳格モード)を暗黙的に解除します。strict modeは単なるサブセットではなく、意図的に通常のコードとは異なるセマンティクス(意味論)を持っています。strict modeをサポートしていないブラウザでは、strict modeのコードが異なる挙動を示す可能性があるため、strict modeを使用する際は、そのサポートを機能テストで確認することが重要です。strict modeのコードと非strict modeのコードは共存できるため、スクリプトは段階的にstrict modeに切り替えることが可能です。
strict modeは通常のJavaScriptのセマンティクスにいくつかの変更を加えます:
- 一部のJavaScriptの無音エラー(エラーメッセージが出ないエラー)を、エラーとしてスローするように変更します。
- JavaScriptエンジンの最適化を妨げるミスを修正します。その結果、strict modeのコードは、同じ内容の非strict modeのコードよりも速く実行されることがあります。
- 将来のECMAScriptのバージョンで定義される可能性のある一部の構文を禁止します。
"This" Keyword
JavaScriptにおけるthis
キーワードは、他の言語と少し異なります。this
はオブジェクトを参照しますが、それがどのように、またはどこで呼び出されたかによって参照する対象が異なります。また、strict mode(厳格モード)と非strict modeの間でも違いがあります。
- オブジェクトのメソッド内では、
this
はそのオブジェクトを参照します。 - 単独で使用すると、
this
はグローバルオブジェクトを参照します。 - 関数内では、
this
はグローバルオブジェクトを参照します。 - 関数内でstrict modeを使用すると、
this
はundefined
になります。 - イベント内では、
this
はイベントを受け取った要素を参照します。 -
call()
,apply()
,bind()
のようなメソッドを使うと、this
を任意のオブジェクトに設定できます。
メソッド内でのthis (this in a method)
メソッドは、オブジェクトのプロパティとして定義された関数です。メソッド内でのthis
の値は、メソッドを呼び出したオブジェクトと等しくなります。簡単に言うと、this
の値は「ドットの前」にあるオブジェクト、つまりメソッドを呼び出すために使われたオブジェクトです。
const car = {
brand: 'Toyota',
model: 'Corolla',
displayInfo: function() {
console.log(`Car brand: ${this.brand}, Model: ${this.model}`);
}
};
// メソッドの呼び出し
car.displayInfo(); // 出力: Car brand: Toyota, Model: Corolla
関数内でのthis (this in a function)
関数内で使用されるthis
キーワードは、グローバルオブジェクトを指します。
注意: ブラウザウィンドウ内では、グローバルオブジェクトはwindow
オブジェクトです。
function showGlobalThis() {
console.log(this);
}
// 関数を呼び出す
showGlobalThis(); // 出力: Window { ... } (ブラウザ環境では)
単独でのthis (Using this alone)
this
キーワードが単独で使用されると、グローバルオブジェクトを指します。
注意: ブラウザウィンドウ内では、グローバルオブジェクトはwindow
オブジェクトです。
console.log(this); // 出力: Window { ... } (ブラウザ環境では)
イベントハンドラー内のthis (this in event handlers)
this
キーワードがイベントハンドラ内で使用されると、イベントを受け取った要素を指します。
<button id="myButton">Click me</button>
<script>
const button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log(this); // 出力: <button id="myButton">Click me</button>
});
</script>
アロー関数内のthis (this in arrow functions)
this
キーワードがアロー関数内で使用されると、親のオブジェクトを指します。
const person = {
name: 'Alice',
greet: function() {
const arrowFunction = () => {
console.log(`Hello, my name is ${this.name}.`);
};
arrowFunction();
}
};
// メソッドの呼び出し
person.greet(); // 出力: Hello, my name is Alice.
関数の借用 (Function Borrowing)
関数の借用(Function borrowing)とは、あるオブジェクトのメソッドを別のオブジェクトで使用できるようにする手法です。これにより、メソッドをコピーして別々の場所で維持する必要がなくなります。関数の借用は、.call()
, .apply()
, または.bind()
を使用して実現され、これらのメソッドは借用するメソッドのthis
を明示的に設定するために存在します。
明示的バインディング (Explicit binding)
明示的バインディング(Explicit Binding)とは、call
またはapply
メソッドを使用して関数内のthis
の値を明示的に設定することを指します。明示的バインディングは、call()
, apply()
, およびbind()
を使用して適用できます。
call
call()
メソッドは、指定したthis
の値を使用して関数を呼び出し、引数を個別に渡すことができるメソッドです。
const person = {
name: 'Alice'
};
function greet(greeting, punctuation) {
console.log(`${greeting}, my name is ${this.name}${punctuation}`);
}
// callメソッドを使用してgreet関数を呼び出す
greet.call(person, 'Hello', '!'); // 出力: Hello, my name is Alice!
apply
apply()
メソッドは、Functionインスタンスが呼び出される際に、指定したthis
の値を使用して関数を呼び出し、引数を配列(または配列のようなオブジェクト)として渡すことができるメソッドです。
const person = {
name: 'Alice'
};
function greet(greeting, punctuation) {
console.log(`${greeting}, my name is ${this.name}${punctuation}`);
}
// applyメソッドを使用してgreet関数を呼び出す
greet.apply(person, ['Hi', '.']); // 出力: Hi, my name is Alice.
bind
bind()
メソッドは、JavaScriptで特定のコンテキストを持つ新しい関数を作成し、オプションで事前に引数を設定することができます。call()
やapply()
とは異なり、bind()
は関数を即座に呼び出すのではなく、後で呼び出すことができる新しい関数を返します。この機能は、関数がどのように呼び出されても特定のコンテキストを保持したいときに特に便利です。
const person = {
name: 'Alice'
};
function greet(greeting) {
console.log(`${greeting}, my name is ${this.name}.`);
}
// bindメソッドを使用して新しい関数を作成
const greetAlice = greet.bind(person);
// 新しい関数を呼び出す
greetAlice('Hello'); // 出力: Hello, my name is Alice.
非同期 JS (Asynchronous JavaScript)
非同期プログラミングは、プログラムが時間のかかるタスクを開始し、そのタスクが実行されている間も他のイベントに応じて応答できるようにする技術です。つまり、タスクが終了するのを待つ必要はありません。タスクが終了すると、プログラムは結果を受け取ります。
多くのブラウザが提供する関数、特に最も興味深いものは、時間がかかる可能性があり、したがって非同期です。例えば:
-
fetch()
を使用したHTTPリクエストの作成 -
getUserMedia()
を使用してユーザーのカメラやマイクにアクセス -
showOpenFilePicker()
を使用してユーザーにファイルを選択させる
したがって、独自の非同期関数を実装することはあまりないかもしれませんが、それらを正しく使用する必要がある場合が非常に多いです。
// 非同期関数の例
async function fetchData(url) {
try {
const response = await fetch(url);
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching data:', error);
}
}
// 使用例
fetchData('https://api.example.com/data');
setTimeout
setTimeout
は、指定された時間が経過した後に関数を実行するためのメソッドです。時間はミリ秒単位で指定します。
console.log('Start');
setTimeout(() => {
console.log('This message is shown after 2 seconds');
}, 2000); // 2000ミリ秒(2秒)後に実行
console.log('End');
setInterval
setInterval()
メソッドは、固定された遅延後に関数を繰り返し実行するために使用されます。このメソッドはユニークなインターバルIDを返し、このIDは後でclearInterval()
メソッドで使用して、関数のさらなる繰り返し実行を停止することができます。
setInterval()
はsetTimeout()
に似ていますが、違いがあります。setTimeout()
がコールバック関数を一度だけ実行するのに対し、setInterval()
は指定した時間間隔(ミリ秒単位)で永遠にコールバック関数を実行します。
let count = 0;
const intervalId = setInterval(() => {
count++;
console.log(`Count: ${count}`);
// 5回実行したら停止
if (count === 5) {
clearInterval(intervalId);
console.log('Interval cleared');
}
}, 1000); // 1000ミリ秒(1秒)ごとに実行
Callbacks
コールバック関数とは、別の関数に引数として渡される関数であり、その外側の関数内で呼び出されて、特定の処理やアクションを完了するために使用されます。
function processUserInput(callback) {
const name = prompt('Please enter your name:');
callback(name);
}
function greet(userName) {
console.log(`Hello, ${userName}!`);
}
// processUserInput関数にgreetをコールバックとして渡す
processUserInput(greet);
Callback Hell
コールバック地獄(callback hell)とは、非同期JavaScriptを記述する際に、コードの実行が視覚的に上から下に行われるようにしようとすると、コードがピラミッド状になり、多くの})
が必要になる状態を指します。これにより、コードが読みづらく、メンテナンスが難しくなることがあります。
getData(function(data) {
processData(data, function(processedData) {
saveData(processedData, function(success) {
if (success) {
console.log('Data saved successfully!');
} else {
console.log('Error saving data.');
}
});
});
});
Promises
プロミスは、JavaScriptにおける非同期コードの取り扱いにおいて、古くてエラーが発生しやすいコールバック方式よりもはるかに優れた方法です。プロミスはECMAScript 6でJavaScriptに導入されました。プロミスを使用することで、非常に複雑な非同期コードを厳密なエラーハンドリングを備えて管理でき、ほぼ同期的なスタイルでコードを書くことができるため、いわゆるコールバック地獄を避けることができます。
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true; // 成功のシミュレーション
if (success) {
resolve('データ取得成功!');
} else {
reject('データ取得失敗。');
}
}, 2000); // 2秒後に実行
});
}
// プロミスの使用
fetchData()
.then((result) => {
console.log(result); // 成功した場合の処理
})
.catch((error) => {
console.error(error); // エラーが発生した場合の処理
});
-
プロミスの作成
fetchData
関数は新しいプロミスを返します。setTimeout
を使って非同期処理を模倣し、成功した場合はresolve
を呼び出し、失敗した場合はreject
を呼び出します。 -
プロミスの使用
fetchData()
を呼び出し、.then()
メソッドで成功時の処理を定義し、.catch()
メソッドでエラー処理を定義します。
Async/Await
async/await
は、プロミスをより快適に扱うための特殊な構文です。async
キーワードを使用してプロミスを返す非同期関数を宣言し、await
キーワードを使うことで、関数がプロミスが解決されるまで待機します。
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('エラーが発生しました:', error);
}
}
fetchData();
Event Loop
イベントループは、Node.jsを理解する上で最も重要な要素の一つです。なぜこれが重要なのかというと、Node.jsが非同期で非ブロッキングI/Oを実現できる仕組みを説明しているからです。これがNode.jsの「キラーフィーチャー」となり、その成功の要因となっています。
イベントループの基本概念
非同期性
Node.jsは非同期プログラミングモデルを採用しており、I/O操作が完了するのを待たずに次の処理を行うことができます。これにより、アプリケーションは同時に多くのリクエストを処理できるようになります。
イベントキュー
イベントループは、タスク(コールバック関数など)がキューに追加され、順番に実行される仕組みです。タスクが完了するたびに、次のタスクがイベントキューから取り出され、実行されます。
非ブロッキングI/O
Node.jsは、I/O操作を非ブロッキングに処理することで、リクエストが完了するのを待つことなく他の処理を続けることができます。これにより、スケーラブルなアプリケーションが構築可能になります。
const fs = require('fs');
console.log('Start reading file.');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
console.log('File content:', data);
});
console.log('File read initiated.');
ファイルの読み込み
fs.readFile
メソッドを使用してファイルを非同期で読み込みます。この操作は非ブロッキングであり、ファイルが読み込まれるまで他の処理が続行されます。
出力の順序
上記のコードを実行すると、最初に「Start reading file.」と「File read initiated.」がコンソールに表示され、その後にファイルの内容が表示されます。
Working with APIs
リモートAPIを利用する際、APIとやり取りを行う方法が必要です。最新のJavaScriptでは、HTTPリクエストをリモートサーバーに送信するための2つのネイティブな方法が提供されています。それが、XMLHttpRequestとFetchです。
Fetch
fetch()
メソッドは、JavaScriptでサーバーにリクエストを送信し、ウェブページ上に情報を読み込むために使用されます。リクエスト先は、データ形式がJSONやXMLのAPIである場合が多いです。このメソッドはプロミスを返します。
-
非同期処理:
fetch()
は非同期で実行され、データの取得が完了するとプロミスが解決されます。 -
レスポンス形式:
fetch()
のレスポンスデータは、JSON形式やテキスト形式など様々なフォーマットで取得可能です。 -
エラーハンドリング:
fetch()
は成功した場合にresolve
され、エラーが発生した場合はreject
されるため、.catch()
やtry-catch
ブロックを用いてエラーを処理します。
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error('ネットワークエラー');
}
return response.json(); // JSONデータを解析
})
.then(data => {
console.log('データ:', data); // データをコンソールに表示
})
.catch(error => {
console.error('エラー:', error); // エラーハンドリング
});
XMLHttpRequest
XMLHttpRequest
(XHR)は、ブラウザに組み込まれているオブジェクトで、サーバーとやり取りを行うために使用されます。XHRを使用することで、ウェブページを再読み込みせずにデータを更新できます。名前に「XML」が含まれていますが、XHRはXML形式のデータだけでなく、JSON、ファイル、その他様々な形式のデータを取得するためにも使用できます。
- 非同期通信: XHRはページを再読み込みせずにバックグラウンドでサーバーにリクエストを送信し、レスポンスを取得できます。
- データ形式: XMLに限定されず、JSONやプレーンテキスト、バイナリデータなど、様々な形式のデータを処理できます。
- 古くからのサポート: XHRは長年にわたりサポートされており、互換性が高いですが、コードが複雑になりやすい欠点があります。
Classes
クラスはオブジェクトを作成するためのテンプレートです。データをカプセル化し、そのデータを操作するコードも含んでいます。JavaScriptのクラスはプロトタイプベースで構築されていますが、ES5のクラスに似た仕組みとは異なる構文や意味を持っています。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`こんにちは、私は${this.name}です。`);
}
}
const person1 = new Person('太郎', 25);
person1.greet(); // こんにちは、私は太郎です。
Javascript Iterators and Generators
イテレーターとジェネレーターは、ECMAScript 6で導入されたJavaScriptの中でも非常に便利な概念で、特に反復処理に関連しています。これにより、JavaScriptでデータの繰り返し処理を柔軟に行うことができるようになりました。
イテレーター(Iterator)
イテレーターは、イテレーター・プロトコルに従うオブジェクトで、配列や文字列などのシーケンス(データの集まり)を簡単に反復処理できるようにする仕組みです。例えば、for...of
ループで利用できます。
イテレーターの特徴
-
next()
メソッドを持ち、このメソッドを呼び出すことで次の要素にアクセスできます。 -
next()
メソッドはオブジェクトを返し、done
プロパティがtrue
になるまで反復処理が続きます。
const array = [10, 20, 30];
const iterator = array[Symbol.iterator]();
console.log(iterator.next()); // { value: 10, done: false }
console.log(iterator.next()); // { value: 20, done: false }
console.log(iterator.next()); // { value: 30, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
ジェネレーター(Generator)
ジェネレーターは、関数を使ってイテレーターを簡単に作成できる機能です。ジェネレーター関数は、通常の関数とは異なり、途中で処理を一時停止し、**yield
**キーワードを使って値を返しながら再開することができま
function* generatorFunc() {
yield 1;
yield 2;
yield 3;
}
const gen = generatorFunc();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
Modules
モジュールは、関数や変数などのコードをカプセル化し、他のファイルにその内容を公開する仕組みです。これにより、コードを別々のファイルに分けて、メンテナンスしやすくすることが可能です。モジュールは、JavaScriptの**ECMAScript 6(ES6)**で導入されました。
コードの分割
コードを論理的に別々のファイルに分けることで、可読性が向上し、バグの追跡や修正が容易になります。
再利用性の向上
一度定義したモジュールは、他のファイルでも簡単に再利用できます。これにより、重複コードを減らし、効率的に開発が進められます。
名前空間の分離
モジュールを使用することで、他のファイルやコードと衝突しない独自の名前空間を持つことができ、予期せぬ変数の上書きを防げます。
ESModules
ESModulesは、ES6(2015年)で導入されたJavaScriptモジュールの標準仕様です。これにより、JavaScriptモジュールの動作を標準化し、ブラウザでもこれらの機能がサポートされるようになりました。この標準は、Reactなどのフロントエンドフレームワークで広く使用されており、Node.jsのバックエンドでも利用可能です。
ブラウザ対応
ESModulesは、モダンブラウザで直接サポートされているため、サードパーティのライブラリやツールなしでモジュールを利用できます。
フロントエンドとバックエンドの両方で使用可能
フロントエンド開発ではReactやVue.jsなどのフレームワークで使われ、バックエンド開発ではNode.jsでESModulesがサポートされています。
.mjs
拡張子
一部の環境、特にNode.jsでESModulesを使う際、.mjs
(モジュールJavaScript)拡張子でファイルを命名することが推奨される場合があります。これは、モジュールとして実行されるJavaScriptファイルを示します。
ブラウザでは、<script>
タグのtype="module"
を指定することで、ESModulesを使用できます。
<script type="module">
import { add } from './math.js';
console.log(add(2, 3)); // 出力: 5
</script>
Node.jsでは、ファイルを.mjs
拡張子で保存することでESModulesを使用できます。.mjs
は、モジュールとして動作することをNode.jsに伝えるために使用されます。
// math.mjs
export function add(a, b) {
return a + b;
}
// main.mjs
import { add } from './math.mjs';
console.log(add(2, 3)); // 出力: 5
CommonJS
CommonJSモジュールは、Node.jsのためにJavaScriptコードをパッケージするための元々の方法です。Node.jsはブラウザや他のJavaScriptランタイムで使用されるESModules標準もサポートしていますが、CJSは依然としてバックエンドのNode.jsアプリケーションで広く使用されています。これらのモジュールは、時に.cjs
拡張子で書かれることがあります。
-
ESModulesとの違い
- ESMは静的で、コンパイル時に解析されるため、より最適化されたバンドルが可能。
- CJSはNode.jsで長い間使用されてきたため、膨大な既存のコードとライブラリがあり、互換性を保つために依然として使われています。
Memory Management
低水準言語(Cなど)は、malloc()
やfree()
のような手動メモリ管理機能を持っています。それに対して、JavaScriptはオブジェクトが作成されると自動的にメモリを割り当て、使用されなくなったときにはメモリを解放します(ガーベジコレクション)。この自動性は混乱を招く可能性があり、開発者にメモリ管理を気にしなくてよいという誤解を与えることがあります。
Cのような低水準言語では、プログラマーがメモリの割り当てと解放を手動で行う必要があります。これにより、メモリ使用に対する細かい制御が可能ですが、メモリリークや二重解放といったエラーを引き起こすリスクもあります。
JavaScriptでは、オブジェクトが作成されるとエンジンが自動的にメモリを割り当てます。使用されなくなったオブジェクトは、ガーベジコレクションによって自動的に解放されます。これにより、開発者はメモリ管理を気にせずにコードを書くことができますが、ガーベジコレクションのタイミングが不明であるため、パフォーマンスに影響を与える可能性があります。
Memory lifecycle
プログラミング言語に関係なく、メモリのライフサイクルはほぼ常に同じです:
- 必要なメモリを割り当てる
- 割り当てられたメモリを使用する(読み取り、書き込み)
- もはや必要でなくなったときに割り当てられたメモリを解放する
-
メモリの割り当て
プログラムが動作するために必要なメモリを確保します。低水準言語(Cなど)では、この操作が明示的に行われます。 -
メモリの使用
割り当てたメモリに対してデータの読み取りや書き込みを行います。この操作は、すべてのプログラミング言語で明示的です。 -
メモリの解放
使用が終了したメモリを解放することで、他の処理やプログラムが使用できるようにします。低水準言語ではこの操作が明示的に必要ですが、高水準言語(JavaScriptなど)ではほとんどが暗黙的に行われます。
Garbage Collection
JavaScriptにおけるメモリ管理は、自動的に行われており、私たちには見えない形で処理されます。プリミティブ、オブジェクト、関数などを作成することは、すべてメモリを消費します。JavaScriptにおけるメモリ管理の主な概念は「到達可能性(reachability)」です。
-
自動メモリ管理
JavaScriptは、開発者が手動でメモリを管理する必要がなく、自動的にメモリを割り当てたり解放したりします。これにより、メモリ管理の複雑さから解放され、開発者はロジックに集中できるようになります。 -
到達可能性の概念
メモリ管理における「到達可能性」とは、オブジェクトが他のオブジェクトから参照されているかどうかを示します。もしオブジェクトが参照されなくなった場合、そのオブジェクトはガベージコレクションの対象となり、メモリが解放されます。つまり、到達可能なオブジェクトは生存し、到達不可能なオブジェクトはメモリから除去されます。
解放
参照の管理
let myObject = { name: "Alice" };
// myObjectが不要になった場合、参照を解除
myObject = null; // これでガベージコレクションの対象になる
スコープの理解(クロージャ)
function outer() {
let largeArray = new Array(1000000).fill("large data");
return function inner() {
console.log(largeArray[0]);
};
}
const innerFunction = outer();
// outerのスコープにあるlargeArrayはinnerFunctionによって参照され続ける
// 使用後は、innerFunctionが不要になったら、largeArrayも解放
innerFunction = null; // 参照を解除
イベントリスナーの解除
const button = document.getElementById("myButton");
function handleClick() {
console.log("Button clicked!");
}
// イベントリスナーを追加
button.addEventListener("click", handleClick);
// 不要になったときにリスナーを解除
button.removeEventListener("click", handleClick);
大きなデータの扱い
let largeData = new Array(1000000).fill("big data");
// 大きなデータが不要になった場合
largeData = null; // メモリを解放するために参照を解除
JavaScript Chrome Dev Tools
ブラウザに組み込まれた一連のツールで、フロントエンド開発者がアプリケーション内のさまざまな問題(JavaScriptや論理的なバグ、CSSスタイリングの問題、DOMの一時的な変更など)を診断し、解決するのに役立ちます。
デベロッパーツールに入るには、右クリックして「検証」を選択するか、ctrl+shift+c
(Windows)またはcmd+opt+c
(Mac)を押します。ここで、CSSやHTMLの問題をデバッグできます。JavaScriptのログメッセージを確認したり、インタラクションを行いたい場合は、上部のタブから「Console」タブに移動します(またはctrl+shift+j
やF12
/ cmd+opt+j
を押して直接入ることができます)。Chromeデベロッパーツールの非常に便利な機能の一つに、パフォーマンスをチェックするためのLighthouseがあります。
Debugging issues
JavaScript開発を始めたばかりの頃は、デバッグのために変数の値をログに記録するために多くのconsole.log()
文をコードに使用するかもしれません。これらの結果は、Consoleパネルに表示され、発生源となったコードの行とファイルへの参照も表示されます。
しかし、より迅速で複雑なデバッグを行い、console.log()
でコードベースを汚さないためには、ブレークポイントとソースパネルが役立ちます。ブレークポイントを使用すると、特定のコード行で実行を一時停止し、その時点での変数の状態を確認したり、ステップ実行でコードの流れを追うことができます。これにより、デバッグプロセスが効率的になり、問題を特定しやすくなります。
Debugging Memory Leaks
JavaScriptでは、メモリリークは一般的にヒープに割り当てられたメモリ内で発生します。ここでは、短命のオブジェクトが長命のオブジェクトに関連付けられており、ガーベジコレクタがそのメモリを安全に解放できません。これは、短命のオブジェクトがルートセット(グローバルオブジェクト)から参照されているためです。
Debugging performance
開発者ツールに入り、Lighthouseタブをチェックしてください。これは、現在開いているウェブサイトをパフォーマンス、ページ速度、アクセシビリティなどに関連するさまざまなメトリックに基づいて分析する一連のテストです。「Analyze Page Load」ボタンをクリックしてテストを実行してみてください(拡張機能によるエラーを避けるために、シークレットタブで行うと良いでしょう)。結果が得られたら、じっくりと読み進めてください(各テスト結果に添付されたリファレンスページにもクリックして、さらに詳しく学んでください!)。