JavaScript の言語特性
JavaScript は、解釈型であり、動的型付け、弱い型付けを採用したプログラミング言語です。これらの特性を理解することは、効率的なコード 작성에 필수적입니다。
解釈型言語は、コードを機械語に事前にコンパイルせず、実行時に逐次解釈しながら処理を行います。编译型言語が書籍全体を事前に翻訳するのと比べ、解釈型は一行ずつ翻訳しながら読むようなもので、実行効率は编译型に劣る場合がありますが、柔軟性が高い特徴があります。
動的型付けとは、変数宣言時に型を固定せず、実行時に代入された値によって型が決定される仕組みです。また、弱い型付けとは、異なる型間の演算が行われる際、言語側が自動的に型変換を行う性質を指します。
例えば、Python などの強い型付け言語では、数値と文字列の加算は明示的な変換がない限りエラーになります。
# Python の例
value = 10
print(value + 5) # 正常:15
# print(value + "text") # エラー:TypeError
一方、JavaScript では型変換が自動で行われるため、以下のよう な 操作も可能です。
let score = 10;
console.log(score + 5); // 15
console.log(score + " Points"); // "10 Points" 文字列結合として処理
変数宣言については、従来の var に加え、ES6 以降では let と const が推奨されています。
実行環境と読み込み順序
ブラウザにおける HTML と JavaScript の読み込み順序は、パフォーマンスに直結します。
- ブラウザは HTML を上から下へ解析し、レンダリングを行います。
- CSS や JavaScript ファイル encountered 場合、別途接続を確立してダウンロードします。
- JavaScript のダウンロードと解析中は、ページのレンダリングがブロックされます(阻塞加载)。
- CSS はダウンロード完了後、既存のスタイルと統合され、再レンダリングが発生します。
- 関数や変数の再定義があった場合、後の定義が前の定義を上書きします。
JavaScript の読み込みは並列処理されず、リクエスト完了まで待機するため、DOM 構築が中断されます。これは、スクリプトが DOM 構造を変更する可能性があるため、ブラウザが整合性を保つための挙動です。
ページ読み込み速度を最適化するための主な戦略は以下の通りです:
- コンテンツの最小化:不要な空格やコメントを削除し、外部ファイル化を行う。
- ファイル数の削減:複数の JS や CSS を統合し、HTTP リクエスト数を減らす。
- DNS 解決の削減:外部リソースのドメイン数を抑える。
- キャッシュの活用:ブラウザキャッシュを効果的に利用する。
- 読み込み順序の最適化:初期表示に必要なリソースを優先し、画像や動画は後回しにする。
- インラインスクリプトの抑制:
document.writeなどの使用を避け、DOM API を利用する。 - モダンな CSS とタグ:レイアウト崩れを防ぐため、画像サイズを明示する。
推奨される HTML 構造は、CSS を <head> に配置し、JavaScript を <body> の閉じタグ直前に配置することです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Sample Page</title>
<link rel="stylesheet" href="styles/main.css">
</head>
<body>
<div id="content">Main Content</div>
<script src="scripts/app.js"></script>
</body>
</html>
データ型システム
JavaScript のデータ型は、プリミティブ型とオブジェクト型に大別されます。
プリミティブ型
数値、文字列、真偽値、null、undefined が含まれます。これらは不変値です。
オブジェクト型
通常のオブジェクト、配列、関数、日付、正規表現などが含まれます。オブジェクトは変更可能な参照型です。
JavaScript にはクラスベースの継承ではなく、プロトタイプチェーンによる継承モデルが採用されています。すべてのオブジェクトは内部リンクを通じてプロトタイプオブジェクトを参照し、プロパティやメソッドを共有します。
构造函数を用いたインスタンス生成の例:
function User(id) {
this.id = id;
}
// プロトタイプに共有プロパティを追加
User.prototype.role = "guest";
const userA = new User(101);
const userB = new User(102);
console.log(userA.role); // "guest"
console.log(userB.role); // "guest"
// プロトタイプを変更すると全インスタンスに影響
User.prototype.role = "admin";
console.log(userA.role); // "admin"
この仕組みにより、メモリ効率が向上し、メソッドの共有が可能になります。
数値型の注意点
JavaScript の数値はすべて IEEE 754 規格の倍精度浮動小数点数として扱われます。整数と浮動小数の区別はありません。
二进制浮動小数点数の表現限界により、以下のような誤差が発生することがあります。
const valA = 0.3;
const valB = 0.6;
console.log(valA + valB);
// 0.8999999999999999 などの誤差が生じる可能性がある
対策として、整数に変換して計算するか、toFixed 等方法を用いて丸め処理を行います。
const result = (valA * 10 + valB * 10) / 10;
console.log(result);
文字列と日付
文字列は不変であり、操作すると新しい文字列が生成されます。ES6 では Unicode のコードポイントを直接記述できるようになりました。
const message = "Hello World";
console.log(message.substring(1, 4)); // "ell"
console.log(message.replace("H", "h")); // "hello World"
Date オブジェクトを用いて日時を処理できます。
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1; // 0 起点なので +1 が必要
null と undefined
null は意図的な空値を表し、undefined は未初期化または未定義を表します。比較演算では null == undefined は true になりますが、厳密比較では false です。
スコープと変数宣言
ES6 で導入された let と const は、ブロックスコープをサポートします。一方、var は関数スコープであり、ブロックスコープを持ちません。
{
let blockVar = "local";
var funcVar = "global";
}
// console.log(blockVar); // ReferenceError
console.log(funcVar); // "global"
ループ処理において、var を使用するとクロージャの問題が発生しやすいですが、let を使用すると各イテレーションで独立した変数が作成されます。
const actions = [];
for (let i = 0; i < 5; i++) {
actions.push(function() {
console.log(i);
});
}
actions[2](); // 2 を出力 (var の場合は 5 になる)
また、let には「一時死の帯(TDZ)」という概念があり、宣言前にアクセスするとエラーになります。
console.log(temp); // ReferenceError
let temp = 10;
const は再代入不可の定数を宣言するために使用します。
演算子と型変換
相等演算子には、緩い比較(==)と厳密な比較(===)があります。厳密比較は型変換を行わないため、バグを防ぐために推奨されます。
console.log(10 == "10"); // true (型変換発生)
console.log(10 === "10"); // false (型が異なる)
型確認には typeof を使用します。
typeof 123; // "number"
typeof "text"; // "string"
typeof undefined; // "undefined"
typeof null; // "object" (歴史的なバグ)
オブジェクトのインスタンスチェックには instanceof が利用できます。
const dateObj = new Date();
console.log(dateObj instanceof Date); // true
条件演算子(三項演算子)は、簡潔な条件分岐に使用されます。
const userName = "Alice";
const greeting = "Hello, " + (userName ? userName : "Guest");
カンマ演算子は、複数の式を連続して評価し、最後の式の結果を返します。
let x = 1, y = 2;
let result = (x++, y++); // result は 2