はじめに
JavaScriptの基本文法についてメモ。ブラウザ環境のJavaScriptの話。
参考
JavsScriptとは
- JavaScript言語を実行する実行環境(エンジン)
- 実行環境(エンジン)とは?
- JavaScriptのソースコードを解釈し、その指示通りに実際に動作するための環境やプロセスを提供するソフトウェアのコンポーネント
- 現在はWebブラウザに限らず、サーバにもJavaScriptの実行エンジンが登場して、サーバーでもJavaScriptが実行できる
Domとは
- ブラウザはHTMLやXML文書を読み取り、それを
DOMツリーとして内部的に表現
します - この
DOMツリーは、ウェブページの構造や内容を表現するもの
で、JavaScriptからアクセスすることでウェブページを動的に操作することができます - DOMはブラウザが提供するAPIの一部であり、JavaScriptはそのAPIを操作するための手段の一つ
- ブラウザごとにDOMの実装は異なります。DOMにはW3C(World Wide Web Consortium)によって定められた標準が存在するので、基本的な機能やインターフェースは一貫しています
- よってブラウザ以外の環境では利用できない
document.getElementByIdのdocument
ブラウザのJavaScript環境において、現在のウェブページの文書全体を表すグローバルオブジェクトを指します。具体的には、documentオブジェクトは現在のウェブページのDOM(Document Object Model)を表現するエントリーポイントです。
documentオブジェクトは、ウェブページの内容や構造を操作するための多くのメソッドやプロパティを提供しています。getElementByIdはその一例であり、指定したIDを持つ要素をDOMから取得するためのメソッドです。
JavaScriptとブラウザ
- JavaScriptからブラウザ実装のDomAPIや提供されるグローバルオブジェクトを利用して、ウェブページの内容や構造を操作する
ブラウザが提供するブラウザオブジェクト
- 各ブラウザで実装されていて、今は標準化も進んでいて、安全に使用できるみたい
- 最上位がWindowオブジェクト。クライアントサイドJavaScriptが起動するタイミングで自動的に作成されて、グローバル変数やグローバル関数にアクセスするために手段を提供
各オブジェクトはwindowオブジェクト経由でも、直接でもどちらでも使用できる
locaiton.reload()
window.location.reload()
-
documentオブジェクト
- ウェブページの内容や構造を操作するためのインターフェースを提供するDOMのエントリーポイント。
-
navigatorオブジェクト
- ブラウザやユーザーエージェントの情報、機能(例:オンライン/オフラインの状態の確認)を提供するオブジェクト。
-
locationオブジェクト
- 現在のページのURLやそのコンポーネントに関する情報や、ページのリダイレクトやリロードの方法を提供するオブジェクト。
-
historyオブジェクト
- ブラウザのセッション履歴を操作するための方法(例:戻る、進む)を提供するオブジェクト。
-
screenオブジェクト
- ユーザーのディスプレイスクリーンに関する情報(例:解像度、色深度)を提供するオブジェクト。
-
localStorageとsessionStorage
- ウェブページの永続的なデータストレージと、ページセッション中のデータストレージをそれぞれ提供するオブジェクト。
-
XMLHttpRequestとFetch API
- ウェブページから非同期でサーバーと通信するための方法を提供するオブジェクトやAPI。
これらは、ブラウザ固有のJavaScript環境で提供される主要なオブジェクトやAPIの一部です。さらに、各ブラウザやバージョンによっては、これらに加えて独自のAPIやオブジェクトが提供されることもあります。
モダンなJavaScript
- ECMAScriptとはJavaScriptの標準仕様の名前
- ほぼ毎年、6月に最新版がでている
- 基本的に後方互換性を破壊する変更は入らない
- 年号付きの使用署名で呼ぶことが推奨
- ES2015(ES6)
- ES2015以降がモダンといわれることが多い
- モダンな仕様が一気に盛り込まれたため
JavaScriptの仕様
変数
- constが第一選択
- var や constがないとグローバルスコープになる
- Javaのようなブロックレベルのスコープは存在しない
型
-
静的型付け言語
- 型がコンパイル時に決定されます。これは、プログラムが実行される前に型のチェックが行われることを意味します。TypeScript、Javaなど
- 型エラーを早期に検出できる
-
動的型付け言語
- 型はプログラムが実行時に決定されます。変数にはあらゆる型の値を代入でき、その型は実行時に解釈されます。JavaScript、Rubyなど
- 変数に対して型を明示的に宣言する必要はなく、任意の型の値を代入できます。
- 型関連エラーを実行時まで検出できないので、使い方によってはバグを生み出しやすい
データ型
- プリミティブ型とオブジェクト型に分けられる
プリミティブ型
- インスタンスメソッドを持たないデータ
- よく使うプリミティブ型
- Boolean型
- Number型
- String型
- Symbol型
- Null型
- Undefined型
- 宣言のみが行われた変数や、オブジェクト内の存在しないプロパティへのアクセスへ割り当てられる
リテラル
プリミティブ型の値を定義する値そのもの
、または、値の表現方法のこと
Boolean型: true、false
Number型: 数字を記述する
String型: ""または''でっ囲まれた文字列
Null型: null
Nan(Not a Number)
- Number型でありながら数値ではないことを示す値のこと
- 0同士の除算などで発生
未定義値 (undefined)
ある変数の値が定義されていないことを表す値。そもそも参照することを想定していないという状態
- ある変数が宣言済みであるものの値を与えられていない
- 未定義のプロパティを参照しようとした
- 関数で値が返されなかった
null
- 該当する値がないことを意味する値
- 意図した空であるという状態を表すための値
falsyなリテラル
-
false
、0
、''
、null
、undefined
、Nan
- それ以外はtruthy
関数
- 関数はオブジェクト
- コンストラクターで定義もできるが、基本しない
- Functionのインスタンスであり、
第一級オブジェクト
である- 他のオブジェクト型の値と同様に、変数へ代入したり、オブジェクトのプロパティ値にしたりできる
-
関数型プログラミング
に対応できる
- 関数式はFunctionオブジェクトを生成するリテラルである
- 単にオブジェクトのプロパティとなっている関数のことを
メソッド
と呼ぶ`
高階関数
- 関数を引数、戻り値として扱う関数
- ベースとなる機能をそのままに、具体的な処理内容だけを差し替えることができる
クロージャー
返されたクロージャーがcount変数への参照を持っているため、ガベージコレクションの対象とならず、count変数はメモリ上に保持され続けます。これにより、counter関数を呼び出すたびに、count変数にアクセスしてその値を増加させることができる
function createCounter() {
let count = 0;
return function() {
return count++;
};
}
const counter = createCounter();
console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2
プロトタイプベースのオブジェクト指向
- プロトタイプとはあるオブジェクトの元となるオブジェクトのこと。プロトタイプを利用してあらたなオブジェクトを生成していく
- class構文はあるが、シンタックスシュガー。性質はプロトタイプのオブジェクト指向
- コンストラクタ関数とはプロトタイプオブジェクトを継承してオブジェクトインスタンスを生成するための独立した関数
オブジェクト
JavaScriptの「オブジェクト」という言葉は、少なくとも2つの異なるコンテキストで使用される
- データ構造としてのオブジェクト(狭義のオブジェクト)
const person = {
name: "John",
age: 30,
greet: function() {
console.log("Hello, " + this.name);
}
};
- オブジェクト指向プログラミングの「オブジェクト」(広義のオブジェクト)
すべてObjectという標準組み込みオブジェクトを最終的な継承元に持っている
JavaScriptの動的な性質と、プロトタイプベースのオブジェクト指向アプローチのため、これらの2つの意味の境界はしばしば曖昧です。データ構造としてのオブジェクトも、メソッドを持つことができ、その意味でオブジェクト指向のコンセプトを持っています。
しかし、一般的に言って、コンテキストや使用方法によって、どちらの意味の「オブジェクト」であるかを識別することができます。
なぜプロトタイプオブジェクト指向
JavaScriptがプロトタイプベースを採用したのは、言語をシンプルで柔軟なものにするのが狙い
引数
- デフォルト引数
- 引数は省略できて、省略された場合デフォルト値が使用される
function greet(name = "Guest") {
console.log(`Hello, ${name}!`);
}
greet(); // "Hello, Guest!"
greet("John"); // "Hello, John!"
- レストパラメータ
- 残りの引数を配列として受け取る
function sum(...numbers) {
return numbers.reduce((prev, curr) => prev + curr, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
配列とオブジェクト
- オブジェクト
- 文字列をキーとして使う場合クォートを省略できる
- プロパティ名のショートハンド
const name = "John";
// これは "name: name" と同じ意味です
const person = { name };
console.log(person.name); // "John"
- キー名に変数やリテラルテンプレートを使用する場合
const prefix = "user";
const id = 123;
const obj = {
[`${prefix}_id`]: id,
[`${prefix}_name`]: "John Doe"
};
console.log(obj.user_id); // 123
console.log(obj.user_name); // "John Doe"
分割代入
配列やオブジェクトから要素やプロパティを抽出し、それらを変数に直接代入するための便利な機能
const colors = ["red", "green", "blue"];
const [firstColor, secondColor, thirdColor] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
console.log(thirdColor); // "blue"
- 特定の要素だけをスキップすることもできます
const [firstColor, , thirdColor] = colors;
console.log(firstColor); // "red"
console.log(thirdColor); // "blue
- オブジェクトの分割代入
const person = {
name: "John",
age: 30,
location: "New York"
};
const { name, age } = person;
console.log(name); // "John"
console.log(age); // 30
- 関数の引数に分割代入
function introduce({ name, age }) {
console.log(`My name is ${name} and I am ${age} years old.`);
}
const personDetails = {
name: "Alice",
age: 25
};
introduce(personDetails); // "My name is Alice and I am 25 years old."
スプレッド構文
配列やオブジェクトの名前の前に...
をつけることで中身が展開される。
const array1 = [1, 2, 3];
const array2 = [...array1, 4, 5, 6];
console.log(array2); // [1, 2, 3, 4, 5, 6]
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3, d: 4 };
console.log(obj2); // { a: 1, b: 2, c: 3, d: 4 }
オブジェクトのマージ
Object.assing()は破壊的メソッドであるため使われなくなってきている。
マージはスプレッド構文を使う
const defaults = { a: 1, b: 2 };
const overrides = { b: 3, c: 4 };
const merged = { ...defaults, ...overrides };
console.log(merged); // { a: 1, b: 3, c: 4 }
- シャーロットコピー
const original = {
primitive: "value",
nestedObj: {
key: "value"
}
};
const copied = { ...original };
// これはシャローコピーなので、copied.nestedObjはoriginal.nestedObjを参照しています
copied.nestedObj.key = "new value";
console.log(original.nestedObj.key); // "new value"
この例では、copied オブジェクトをシャローコピーで作成しましたが、copied.nestedObj と original.nestedObj は同じオブジェクトを参照しています。そのため、copied.nestedObj.key の値を変更すると、original.nestedObj.key の値も変わります。
Optional Chaining (?.)
const name = user?.profile?.name; // もし user または user.profile が null/undefined だったら、undefined が返される
?.でプロパティ名アクセスして、undefinedだったらの動作をif文を書かずに書けるようになる
Nullish Coalescing (??) 演算子
const value = null ?? "default"; // "default"
const number = 0 ?? 42; // 0
Nullish Coalescing(??
)は、ES2020 (ES11) で導入されたJavaScriptの新しい演算子です。この演算子は、左側のオペランドがnull
またはundefined
の場合に右側のオペランドを返します。それ以外の場合は、左側のオペランドを返します。
従来の||
(論理 OR)演算子とは異なり、Nullish Coalescing演算子はnull
やundefined
といった「nullish」な値に対してのみ動作し、他のfalsyな値(例: false
, 0
, ''
)に対しては動作しない点が特徴です。
使用例
let value;
let result = value ?? "Default Value";
console.log(result); // "Default Value" と表示される
上記のコードでは、value
が未定義(undefined
)なので、??
演算子は右側のオペランド"Default Value"
を返します。
||
との違い
以下は、||
演算子と??
演算子の違いを示す例です:
let myValue = 0;
console.log(myValue || "Default Value"); // "Default Value"
console.log(myValue ?? "Default Value"); // 0
この例では、myValue
が0
というfalsyな値を持っていますが、??
は0
をそのまま返します。一方、||
は0
をfalsyと評価し、右側のオペランド"Default Value"
を返します。
このように、Nullish Coalescing演算子は、null
やundefined
以外のfalsyな値(0
, false
, ''
など)を有効な値として扱いたい場面で非常に便利です。
JavaScriptのthis
- 基本的にthisはクラス構文内でしか使用しない
- クラス内ではアロー関数で使用する
thisの参照先は以下の条件で変化する
- トップレベルのコード(関数やメソッドの外側)でthisを使用すると、ブラウザの環境ではwindowオブジェクト(グローバルオブジェクト)を参照します。
- 通常の関数呼び出しの場合(例:myFunction())、thisはグローバルオブジェクトを参照します(strict modeの指定がれば、thisはundefined)。
- 関数がオブジェクトのメソッドとして呼び出される場合、thisはそのオブジェクトを参照します。
use strict
"use strict"(ストリクトモード)を使用すると、関数のデフォルトのthis値がグローバルオブジェクト(通常のブラウザではwindowオブジェクト)からundefinedに変わります。これは、グローバルオブジェクトの誤った変更を避けるための安全対策として導入
ただし、メソッドの中から普通の関数(アロー関数ではない)を呼び出して、その関数内でthisを参照する場合、そのthisの値は、ストリクトモードが有効な場合、undefinedとなります。
"use strict";
function outerFunction() {
console.log(this); // ここではthisはundefined
}
const obj = {
prop: "Hello",
method: function() {
outerFunction();
console.log(this.prop); // ここではthisはobj
}
};
obj.method();
この場合、アロー関数を使用する。
アロー関数は自身のthisを持たず、その代わりに外側のスコープのthisをキャッチします。これを利用すると、期待するthisの値を保持することができます。
"use strict";
const outerFunction = () => {
console.log(this); // ここでのthisはobj
};
const obj = {
prop: "Hello",
method: function() {
outerFunction();
console.log(this.prop); // ここではthisはobj
}
};
obj.method();
非同期処理(Promise)
Promiseは、非同期操作の最終的な完了 (もしくは失敗) およびその結果の値を表現するオブジェクト
使い方
const promise = new Promise((resolve, reject) => {
// 非同期の操作
if (/* 何らかの成功条件 */) {
resolve('成功時の結果');
} else {
reject('失敗時のエラー');
}
});
promise.then(result => {
console.log(result); // '成功時の結果' が表示される
}).catch(error => {
console.error(error); // '失敗時のエラー' が表示される
});
resolve -> thenを呼ぶ
rejcet -> catchを呼ぶ
thenのコールバック関数内で非Promise値(例: 文字列、数値、オブジェクトなど)をreturnすると、その値は自動的にPromiseにラップされて解決されます。これは、resolve(その値)を呼び出した場合とほぼ同じ動作と考えることができます。したがって、次のthenメソッドのコールバック関数はその値を受け取ることができます。
Promise.resolve() // 何も値を持たない解決されたPromiseを作成
.then(() => {
return "Hello, World!";
})
.then(result => {
console.log(result); // "Hello, World!" が表示される
});
非同期処理(async/await)
- async/awaitはJavaScriptの非同期処理をより直感的に、そして読みやすい形で書くための機能
- Promiseチェーンを.then().then()のような形式で書く代わりに、より同期的なコードのように非同期処理を書けることです。
sync function fetchUserData() {
const response = await fetch("https://api.example.com/user");
const data = await response.json();
return data;
}
fetchUserData().then(data => {
console.log(data); // APIから取得したユーザーデータが表示される
});
- asyncキーワードを使って関数を宣言すると、その関数は必ずPromiseを返
- await演算子は、async関数の中でのみ使用できます。これは、Promiseが解決されるのを待つ
上記の例では、fetch関数がPromiseを返すので、そのPromiseが解決されるまでawaitで待ちます。このようにして、非同期処理を直列に並べて書くことができます
function fetchUserData() {
return fetch("https://api.example.com/user");
}
function fetchUserPosts(userId) {
return fetch(`https://api.example.com/user/${userId}/posts`);
}
fetchUserData()
.then(response => response.json())
.then(user => fetchUserPosts(user.id))
.then(response => response.json())
.then(posts => {
console.log(posts);
})
.catch(error => {
console.error("Error:", error);
});
↓こうかける
async function fetchAndDisplayUserPosts() {
try {
const userResponse = await fetch("https://api.example.com/user");
const user = await userResponse.json();
const postsResponse = await fetchUserPosts(user.id);
const posts = await postsResponse.json();
console.log(posts);
} catch (error) {
console.error("Error:", error);
}
}
fetchAndDisplayUserPosts();