これは何?
JavaScriptを使おうと思ってみたものの歴史が長いため,特殊な仕様だったり,よくわからない言葉がたくさんある気がする。
最近は生成AIでなんとなく動くものは作れてしまうが,もやもやが晴れないので一念発起して気になっていることをちゃんと調べてまとめてみた。
これからJavaScript,TypeScriptを学習しようとする方や,自分のようになんとなく動くものは作れるけどもやっとすることがある方の助けになれば良いなと思う。
なぜ,今JS/TSを学ぶ気になったのか
- VSCodeの拡張のロジックはJS/TSで書かれるから
- (GitHub Actionsの拡張作りたいみたいなのもあったりするが)
- フレームワークの移り変わりは早いが
ESModuleとCommonJS
フロントエンドではimport
を使っているのにnode.js(バックエンド)ではrequire
が使われていて,同じ言語なのにライブラリを使用する構文に違いがあるのだ?と不思議な気持ちになったことはあるだろうか。
これはESModuleとCommonJSという二種類のモジュールシステムが存在しているためだ。
基本的に新しいESModuleを使う方が主流にはなってきているが,一部のライブラリがESModule(import
)に対応していない場合にはCommonJS(require
)を使うこともある。
これらの違いについて公式ドキュメントをもとに記載した。
ESModule(import
を使う方)
ECMScriptの第6版(2015)に追加された機能。
Some of its major enhancements included modules, class declarations, lexical block scoping, iterators and generators, promises for asynchronous programming, destructuring patterns, and proper tail calls. The ECMAScript library of built-ins was expanded to support additional data abstractions including maps, sets, and arrays of binary numeric values as well as additional support for Unicode supplementary characters in strings and regular expressions. The built-ins were also made extensible via subclassing. The sixth edition provides the foundation for regular, incremental language and library enhancements. The sixth edition was adopted by the General Assembly of June 2015. 引用元
ECMAScript(エクマスクリプト)は、Ecmaインターナショナルにおいて標準化されたJavaScriptの国際規格である。https://ja.wikipedia.org/wiki/ECMAScript
-
Node.js ESM Documentationのサンプルコードにあるように
import {}
を使ってライブラリを読み込み,export {}
を使ってライブラリを外部からアクセス可能にする。
addTwo.mjs// addTwo.mjs function addTwo(num) { return num + 2; } export { addTwo };
app.mjs// app.mjs import { addTwo } from './addTwo.mjs'; console.log(addTwo(4)); // 6と出力
CommonJS(require
を使う方)
CommonJS modules are the original way to package JavaScript code for Node.js. Node.js also supports the ECMAScript modules standard used by browsers and other JavaScript runtimes.
Node.js v23.9.0 documentationより
CommonJSはNodo.jsで作成されたコードをパッケージ化する昔ながらの方法といったところでしょう。
同ドキュメントのサンプルコードを見ると,require()
を使ってライブラリを読み込み,export.要素名
で外部からアクセス可能にすることがわかります。
foo.jsconst circle = require('./circle.js'); console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);
circle.jsconst { PI } = Math; exports.area = (r) => PI * r ** 2; exports.circumference = (r) => 2 * PI * r;
拡張子のあれこれ
JavaScript
-
.js: 基本的なJava Scriptの拡張子
-
.mjs: どのファイルがモジュールで、どのファイルが通常の JavaScript であるかを明確にするためにV8のドキュメントで推奨されている拡張子。.mjsが使用されている場合にはESModuleが使われている。
.mjsを使うことでサーバが拡張子を正しく判断できなくなる可能性があることに注意する
ブラウザーでモジュールを正しく動作させるためには、サーバーが Content-Type ヘッダーで JavaScript の MIME タイプ、例えば text/javascript などを含めて提供していることを確認する必要があります。そうしないと、"The server responded with a non-JavaScript MIME type" のような厳格な MIME タイプチェックエラーが表示され、ブラウザーは JavaScript を実行しません。ほとんどのサーバーでは、.js ファイルにはすでに正しい MIME タイプが設定されていますが、.mjs ファイルにはまだ設定されていません。すでに .mjs ファイルを正しく提供しているサーバーには、GitHub Pages や Node.js の http-server などがあります。
-
.cjs: Node.jsでは.cjs拡張子を使っているとCommonJSとして扱われる。
-
.jsx: JavaScriptの拡張であり、JavaScriptファイル内にHTMLのようなマークアップを書けるようにするもの
// 公式ドキュメントから引用 export default function TodoList() { return ( // This doesn't quite work! <h1>Hedy Lamarr's Todos</h1> <img src="https://i.imgur.com/yXOvdOSs.jpg" alt="Hedy Lamarr" class="photo" > <ul> <li>Invent new traffic lights <li>Rehearse a movie scene <li>Improve the spectrum technology </ul> ); }
TypeScript
上記のTypeScriptバージョンなので割愛
- .ts
- .mts
- .cts
- .tsx
JSON
{
"browsers": {
"firefox": {
"name": "Firefox",
"pref_url": "about:config",
"releases": {
"1": {
"release_date": "2004-11-09",
"status": "retired",
"engine": "Gecko",
"engine_version": "1.7"
}
}
}
}
}
- 構造化データを表現するための標準のテキストベースの形式
- JSONのテキストは有効なJavaScriptの式になる ※全てのJavaScriptがJSONになるわけではない
- JavaScriptと文法は微妙に違う
VSCodeのsettings.json
はVSCodeがJSONに独自の拡張を加えているらしいので,本来コメントは使えない。
参考
最近,MCPでAIモデルとデータソース間のデータのやり取りがJSONで行われていることもあり,非プログラマの方の間でも多少ホットなデータ保存形式なのではないだろうか。
参考: node.jsでESModuleとCommonJSの優先順位の決まり方
- .cjsを使っている→CommonJS,.mjsを使っている→ESModule
- .jsの場合: package.jsonを確認する
- package.jsonなし→CommonJS
- package.jsonに
type
フィールドなし→CommonJS - package.jsonの
type
フィールドに"commonjs"
が指定→CommonJS - package.jsonの
type
フィールドに"module"
が指定→ESModule
- .mjs,.cjs,.json,.node,.js以外の拡張子の場合
-
import()
を使うならESModule -
require()
を使うならCommonJS
↓せっかくなのでフローチャートにしてみた。
変数の宣言の違い
未宣言の変数
未宣言の変数は暗黙的にグローバル変数になる。
厳格モード(strict)を使うことにより,誤ってグローバル変数が作成されてしまうことを防げる。
未宣言の変数への代入
厳格モードでは、偶発的にグローバル変数を作成できないようにします。厳格モードでない場合は、代入文で変数名の綴りを間違えるとグローバルオブジェクトに新しいプロパティが作成され、そしてそれは動作し続けます。厳格モードでは、代入文で偶発的にグローバル変数を作成せずにエラーが発生します
function foo() {
x = 1;
console.log(x); // 1
}
foo();
console.log(x); // 1
var
var
で宣言された変数のスコープは、その現在の実行コンテキストとそのクロージャであり、その中で宣言された関数、あるいは関数の外で宣言された変数の場合はグローバルになります。
このため,ブロック内等のあらゆる場所で変数が上書きされる恐れがあります。
- グローバルフィールドで
var
を使って宣言するとグローバル変数になる。
// この場合xはグローバル変数
var x = 1;
// ブロック内でグローバル変数を上書きしてみる
if (x === 1) {
var x = 2;
console.log(x); // 2
}
console.log(x); // 2に上書きされている
- 関数無いで
var
を使って宣言すると関数内でのみ有効な変数になる。
// 関数内でvarを使う
function foo() {
var x = 1;
console.log(x); // 1
}
foo();
console.log(x); // ReferenceError: x is not defined 関数内でのみ有効なため
- 再代入可能
- 同名の変数がいても問題ない
var x = 1;
var x = 2; // 再代入可能
console.log(x); // 2
let
let を使用することで、それが使用されたブロックの文または式にスコープを限定した変数を宣言することができます。
-
ブロック内変数のため,スコープ外では使えない。
for (let i = 1; i <= 3; i++) { console.log(i); } console.log(i) // ReferenceError: i is not defined
比較のため
var
で置き換えてみるとスコープ外でも変数が有効なのがわかる。for (var i = 1; i <= 3; i++) { console.log(i); } console.log(i) // 4
-
同名変数の再宣言を禁止
let x = 1; let x = 2; // SyntaxError: redeclaration of let x
const
letの定数バージョン
- ブロック内変数
- 上書き禁止
- 再宣言禁止
関数の書き方
function宣言
おそらく,最も普通の書き方
function sum(a, b) {
return Number(a) + Number(b)
}
result = sum(1, 2)
Functionコンストラクタを使う(非推奨)
eval()のようにコンストラクタを呼び出すことで動的に関数を生成できるのが特徴
(現代であえて使うユースケースは思いつかないのでこんなのあるんだ〜くらいに思っておけばよさそう)
const sum = new Function("a", "b", "return a + b");
result = sum(1, 2)
関数式
function宣言と違い,関数式は無名関数を生成する。
const sum = function (a, b) {
return (a + b)
};
result = sum(1, 2);
callback関数で使う
コールバック関数は、引数として他の関数に渡され、外側の関数の中で呼び出されて、何らかのルーチンやアクションを完了させる関数のことです。
Callback function
以下の例はボタン要素に対してクリックイベントが発生した場合に実行する処理を関数式で記述したもの。
button.addEventListener("click", function (event) {
console.log("button is clicked!");
});
function宣言を使う処理よりも簡潔にかける
function handleClick(event) {
console.log("button is clicked!");
}
// addEventListener に関数名を渡す
button.addEventListener("click", handleClick);
後述するアロー関数式を使うのが現在では一般的だと思われる。
即時実行関数(IIFE)を使用する例
無名関数を作成してそのまま実行することが可能。
再利用しないことを前提にした書き方になる。
(function () {
console.log(1 + 2);
})();
// こっちでも良い
!function () {
console.log(1 + 2);
}();
即時実行関数を使用することでグローバルスコープを汚さずに処理が実行できるのがメリットになる。
昔はJavaScriptのIIFEとモジュールパターンのようにカプセル化のために使用されていたらしい。
現在はclassが使えるので特にこの使い方はしないと思うが参考までに
アロー関数式
アロー関数式は、従来の関数式の簡潔な代替構文ですが、意味的な違いや意図的な使用上の制限もあります。
基本的には関数式を使った書き方の省略形くらいに思っておけば良さそう
const sum = (a, b) => {return a + b};
result = sum(1, 2)
関数式と同様に,callback関数で使用するのがメインの使い方になる
button.addEventListener("click", (event) => {
console.log("button is clicked!");
});
既に省略した書き方だが,省略記法がある。
e.g. 引数event
を使わないなら省略可能で,処理が1行なら{}
も省略可能。
button.addEventListener("click", () => console.log("button is clicked!"));
パッケージマネージャ
npmとyarnの違い
- npmがnode.jsの公式パッケージマネージャ
- yarnはMetaが作った代替品
yarnの方が高性能という認識で良さそう。
npmとnpxの違い
npx is a command-line utility that comes with npm 5.2 and higher, and is used to execute packages
npx allows you to run packages without installing them globally, making it easier to use packages without cluttering your global package space.
npmと違ってパッケージをインストールせずに直接実行可能なため,npmと違ってglobaフィールドを汚さずに実行できるのが嬉しい。
セミコロンを文末につけるべきか
JavaScript では、命令は文 (statement) と呼ばれ、セミコロン (;) によって区切られています。
文が単独の行で書かれている場合、文の後にセミコロンは必要ではありません。しかし、行の中に複数の文が必要な場合は、セミコロンで区切る必要があります。
ECMAScript も文末に自動的にセミコロンを挿入するルールがあります (ASI)。(詳しくは、JavaScript の 字句文法についての詳細なリファレンスを参照してください。)
必須ではないとしても、文の後に常にセミコロンを記述することをお勧めします。これによって、コード中にバグが発生する機会を減らすことができます。
文法とデータ構造
自分は以下のように考えている。
-
自動挿入されるらしいが,想定外のバグが発生する可能性がありそうなのでフォーマッタでつける
prettierの場合はデフォルトでセミコロンを入れるようになっているが明示的に指定するなら以下
.prettierrc{ "semi": true }
-
LLMにコードを食わせる際には
;
があることで文の区切りがわかりやすいなどのメリットはありそう