JavaScript
melangの各noteで他言語と比較した、JavaScriptの設計をまとめたページ。ここに書かれている内容は、各noteの比較表から自動で集めたものです。
型
型注釈という概念自体がない。
let age = 20;let age = 20;
age = "Alice"; // 再代入で型が変わる
age + 10; // 暗黙の型変換で意図しない挙動になりうるTypeScriptの土台となる言語で、型注釈の構文がない。暗黙の型変換によって、意図しない挙動が実行時に初めて表面化することがある。
型注釈の位置
型注釈という構文自体が存在しない。
let age = 20;let age = 20;
function greet(name) { // 引数・戻り値ともに型注釈は書けない
return "Hello, " + name;
}変数宣言・関数引数・戻り値のいずれにも型注釈を書く構文がない。TypeScriptが型注釈を後付けで追加した土台であり、prefix/postfixという比較軸自体が存在しない基準点として位置づけられる。
型変換
変換関数とメソッドを併用し、失敗はNaNや例外で表す。
const n = Number(s);Number("42"); // 42
Number("abc"); // NaN
parseInt("42px"); // 42(途中まで読める)
(10).toString(); // "10"
BigInt(1.5); // RangeError`Number("abc")`は`NaN`、`parseInt("42px")`は先頭の数字だけを読んで`42`を返すなど、失敗や部分的な成功が値の中に紛れ込む。`BigInt(1.5)`のように例外で失敗する変換もあり、失敗の表現が統一されていない。
暗黙の型変換
演算子ごとに異なる規則で型を暗黙変換する。
let x = 10;let x = 10;
x + "1"; // "101"(数値が文字列に暗黙変換されて結合される)
"5" - 2; // 3(文字列が数値に暗黙変換される)演算子ごとに異なる規則でオペランドの型を暗黙に変換する。`+`は片方が文字列なら文字列結合、`-`などは数値変換を試みるといったように規則が演算子ごとに異なり、意図しない挙動の温床になりやすい。
Enum / Sum Type
組み込みのenumがなく、慣用的にオブジェクトで代替する。
const status = { kind: 'success', user };const status = { kind: 'success', user };
switch (status.kind) {
case 'success':
console.log('ok', status.user);
break;
// 'failure'を書き忘れてもエラーにならない
}組み込みのenumがなく、タグ付きのプレーンオブジェクト等で慣用的に代替する。網羅性を検査する仕組みは言語自体にはない。
値の不変性
`const`のオブジェクトも変更できる。`freeze`は浅く、任意の手段である。
const x = ... / Object.freeze(obj)const user = { name: "alice", tags: ["a"] };
user.name = "bob"; // OK: constでもプロパティは変更できる
user.tags.push("admin"); // OK
const frozen = Object.freeze({ name: "alice", tags: ["a"] });
frozen.name = "bob"; // 無視される(strict modeではTypeError)
frozen.tags.push("admin"); // OK: freezeは浅く、tagsの中身は変更できる`const`は束縛だけを固定する。`Object.freeze`は最上位のプロパティしか固定せず、入れ子のオブジェクトや配列の中身は変更できる。深く不変にするには、再帰的に凍結するか、ライブラリを使う必要がある。
名前付き引数
TypeScriptと同様、オブジェクトを渡して分割代入するパターンが一般的。
function foo({ width, height })function foo({ width, height }) {}
foo({ width: 10, height: 20 }); // オブジェクトで意味を明示
function bar(width, height) {}
bar(10, 20); // 通常の引数は位置のみ言語機能としての名前付き引数はなく、TypeScriptと同じくオブジェクト引数+分割代入のパターンで代替する。型がない分、キーの必須・省略はドキュメントやデフォルト値で表すしかない。
関数の引数
プリミティブは値、オブジェクトは参照の値が渡される。
function f(n, obj)function f(n, obj) {
n = 1; // 呼び出し側には影響しない
obj.v = 1; // 呼び出し側から見える
}
const o = { v: 0 };
f(0, o);
console.log(o.v); // 1プリミティブは値が渡され、オブジェクトは参照の値が渡される。引数の再代入は呼び出し側に影響しないが、オブジェクトの中身の変更は影響する。渡し方を選ぶ構文はなく、値の種類によって暗黙に決まる。
if 文/式
`if`はstatement。値の分岐は三項演算子`?:`で書く。
if (cond) { ... } else { ... } / cond ? a : bconst x = cond ? 1 : 2;
if (cond) {
process();
}`if`はstatementで、値の分岐には三項演算子を使う。式として書ける`do`式の提案はあるが、標準化されていない。
I/O の明示
型注釈自体がなく、I/Oを示す仕組みもない。
function findUser(id) { ... }function findUser(id) {
return fetch(`/users/${id}`).then(r => r.json());
}
function getUser(id) {
findUser(id); // 呼べる。async/awaitを付けなくてもよい
}`async`/`await`はあるが、静的検査がないため、非同期関数を`await`せずに呼んでも実行時までエラーにならない。I/Oかどうかを示す構文自体が存在しない。
言語名
Javaとの類似性を利用したマーケティング目的の命名(言語的関連はない)。
開発当初`Mocha`、次に`LiveScript`と呼ばれていたが、当時人気だったJavaに肖ってNetscapeが`JavaScript`と改名した。技術的にはJavaと直接の関係を持たないにもかかわらず類似名を採用したため、初学者の混同や「JavaはJavaScriptの一部」という誤解を今も生み続けている。