「JavaScript Primer」を読んだ
目的、モチベーション
最近はバックエンドしか触ってなくて、久しぶりにフロントエンドのキャッチアップしておきたかったから。
全体の感想
最近のJavaScriptを知らない人や、雰囲気で触っている人には良いと思った。
本がGitHubで管理されていて差分も把握しやすいため、新しいバージョンが公開されたときにも部分的にキャッチアップしやすくて良いと感じた。
これが無料で読めるのは本当にありがたいなと思った。
目次
- 目的、モチベーション
- 全体の感想
- 目次
- メモ
メモ
第一部: 基本文法
変数と宣言
varの使い方はletとほとんど同じで、varキーワードには同じ名前の変数を再定義できてしまう問題があります。
データ型とリテラル
BigInt
数値リテラルは倍精度浮動小数(64bit)で数値を扱うのに対して、BigIntでは任意の精度の整数を扱えます
console.log(1n); // => 1n // 2^53-1より大きな値も扱える console.log(9007199254740992n); // => 9007199254740992n // BigIntは整数を扱うデータ型であるため、次のように小数点を含めた場合は構文エラーとなります。 1.2n; // => SyntaxError
文字列(String)
"
(ダブルクォート)と '
(シングルクォート)はまったく同じ意味
テンプレートリテラル
`(バッククォート)で囲んだ範囲を文字列とするリテラルです。 テンプレートリテラルでは、複数行の文字列を改行記号のエスケープシーケンス(\n)を使わずにそのまま書くことができます。
`複数行の
文字列を
入れたい`; // => "複数行の\n文字列を\n入れたい"
[コラム] undefinedはリテラルではない
ただのグローバル変数
演算子
比較演算子
厳密等価演算子(===)
// 同じ型で同じ値である場合に、trueを返す console.log(1 === 1); // => true console.log(1 === "1"); // => false // オブジェクトの場合は、同じ参照のときtrueを返す const objA = {}; const objB = {}; console.log(objA === objB); // => false console.log(objA === objA); // => true
等価演算子(==)
console.log(1 == 1); // => true // オブジェクトは参照が一致しているならtrueを返す const objA = {}; const objB = {}; console.log(objA == objB); // => false console.log(objA == objA); // => true // オペランド同士が異なる型の値であった場合に、 同じ型となるように暗黙的な型変換をしてから比較する // 文字列を数値に変換してから比較 console.log(1 == "1"); // => true // "01"を数値にすると`1`となる console.log(1 == "01"); // => true // 真偽値を数値に変換してから比較 console.log(0 == false); // => true // nullの比較はfalseを返す console.log(0 == null); // => false // nullとundefinedの比較は常にtrueを返す console.log(null == undefined); // => true // null または undefined以外は厳密等価演算子を使うべき const value = undefined; /* または null */ // === では2つの値と比較しないといけない if (value === null || value === undefined) { console.log("valueがnullまたはundefinedである場合の処理"); } // == では null と比較するだけでよい if (value == null) { console.log("valueがnullまたはundefinedである場合の処理"); }
論理演算子
falsyな値とは次の7種類の値のこと
false
undefined
null
0
0n
NaN
""
(空文字列)
nulishとは、評価結果がnullまたはundefinedとなる値のこと
暗黙的な型変換
さまざまな暗黙的な型変換
1 + "2"; // => "12" 1 - "2"; // => -1
関数と宣言
可変長引数
// [ES2015] Rest parameters function fn(...args) { // argsは引数の値が順番に入った配列 console.log(args); // => ["a", "b", "c"] } fn("a", "b", "c"); // Spread構文 function fn(x, y, z) { console.log(x); // => 1 console.log(y); // => 2 console.log(z); // => 3 } const array = [1, 2, 3]; fn(...array); // arguments function fn() { // `arguments`はインデックスを指定して各要素にアクセスできる // Array ライクなだけで、Arrayのメソッドは使えない console.log(arguments[0]); // => "a" console.log(arguments[1]); // => "b" console.log(arguments[2]); // => "c" } fn("a", "b", "c");
[ES2015] 関数の引数と分割代入
// 第1引数のオブジェクトから`id`プロパティを変数`id`として定義する function printUserId({ id }) { console.log(id); // => 42 } const user = { id: 42 }; printUserId(user);
関数式
[ES2015] Arrow Function
// 仮引数の数と定義 const fnA = () => { /* 仮引数がないとき */ }; const fnB = (x) => { /* 仮引数が1つのみのとき */ }; const fnC = x => { /* 仮引数が1つのみのときは()を省略可能 */ }; const fnD = (x, y) => { /* 仮引数が複数のとき */ }; // 値の返し方 // 次の2つの定義は同じ意味となる const mulA = x => { return x * x; }; // ブロックの中でreturn const mulB = x => x * x; // 1行のみの場合はreturnとブロックを省略できる
次のような特徴がある。
- 名前をつけることができない(常に匿名関数)
this
が静的に決定できる(詳細は「関数とスコープ」の章で解説します)function
キーワードに比べて短く書くことができるnew
できない(コンストラクタ関数ではない)arguments
変数を参照できない
条件分岐
switch文
breakが必要で、厳密等価演算子で評価される。
ループと反復処理
for...in文
事故りやすいので、基本的には使わないこと。
オブジェクト
[ES2015] オブジェクトと分割代入
const languages = { ja: "日本語", en: "英語" }; const { ja, en } = languages; console.log(ja); // => "日本語" console.log(en); // => "英語"
プロパティの削除
delete
を使う。
const obj = { key1: "value1", key2: "value2" }; delete obj.key1; // key1プロパティが削除されている console.log(obj); // => { "key2": "value2" }
[コラム] constで定義したオブジェクトは変更可能
Objectを変更不可能にするには、 Object.freeze
を使う。
"use strict"; const object = Object.freeze({ key: "value" }); object.key = "value"; // => TypeError: "key" is read-only
プロパティの存在を確認する
定義されていない場合は、 undefined
が返るので以下の様に確認する。
const obj = { key: undefined }; if ("key" in obj) { console.log("`key`プロパティは存在する"); } if (obj.hasOwnProperty("key")) { console.log("`obj`は`key`プロパティを持っている"); }
[ES2020] Optional chaining演算子(?.)
const title = widget?.window?.title ?? "未定義"; console.log(`ウィジェットのタイトルは${title}です`);
オブジェクトの静的メソッド
const obj = { "one": 1, "two": 2, "three": 3 }; console.log(Object.keys(obj)); // => ["one", "two", "three"] console.log(Object.values(obj)); // => [1, 2, 3] console.log(Object.entries(obj)); // => [["one", 1], ["two", 2], ["three", 3]]
複製するときは、 Object.assign({}, obj)
で実現できるが、shallowCopyなので注意。
プロトタイプオブジェクト
in演算子とObject#hasOwnPropertyメソッドの違い
const obj = {}; // `obj`というオブジェクト自体に`toString`メソッドが定義されているわけではない console.log(obj.hasOwnProperty("toString")); // => false // `in`演算子は指定されたプロパティ名が見つかるまで親をたどるため、`Object.prototype`まで見にいく console.log("toString" in obj); // => true
オブジェクトの継承元を明示するObject.createメソッド
// const obj = {} と同じ意味 const obj = Object.create(Object.prototype); // `obj`は`Object.prototype`を継承している console.log(obj.hasOwnProperty === Object.prototype.hasOwnProperty); // => true // prototypeを継承しないオブジェクト const nullObj = Object.create(null);
配列
オブジェクトが配列かどうかを判定する
Array.isArray
を使う。
const obj = {}; const array = []; console.log(Array.isArray(obj)); // => false console.log(Array.isArray(array)); // => true console.log(typeof array); // => "object"
[コラム] undefinedの要素と未定義の要素の違い
const denseArray = [1, undefined, 3]; const sparseArray = [1, , 3]; console.log(denseArray[1]); // => undefined console.log(sparseArray[1]); // => undefined console.log(denseArray.hasOwnProperty(1)); // => true console.log(sparseArray.hasOwnProperty(1)); // => false
配列から要素を削除
const array = ["a", "b", "c"]; // 1番目から1つの要素("b")を削除 array.splice(1, 1); console.log(array); // => ["a", "c"]
const array = [1, 2, 3]; array.length = 0; // 配列を空にする console.log(array); // => []
[コラム] Array-likeオブジェクト
Array.from
メソッドでArrayに変換できる。
文字列
正規表現オブジェクト
- 正規表現リテラル: ソースコードをロード(パース)した段階で正規表現のパターンが評価される
- RegExpコンストラクタ: 通常の関数と同じように実際にRegExpコンストラクタを呼び出すまでパターンは評価されない
関数とthis
実行コンテキストとthis
<script> // 実行コンテキストは"Script" console.log(this); // => window </script> <script type="module"> // 実行コンテキストは"Module" console.log(this); // => undefined </script>
// ブラウザでは`window`オブジェクト、Node.jsでは`global`オブジェクトを参照する
console.log(globalThis);
非同期処理:コールバック/Promise/Async Function
非同期処理はメインスレッドで実行される
並列処理ではなく、並行処理。
[ES2015] Promise
Promise.all
で複数のPromiseをまとめることができるPromise.race
でもまとめることができるが、最初の結果だけが反映される。
[ES2015] Map/Set
WeakMap
const map = new WeakMap(); // キーとなるオブジェクト let obj = {}; // {} への参照をキーに値をセットする map.set(obj, "value"); // {} への参照を破棄する obj = null; // GCが発生するタイミングでWeakMapから値が破棄される
[コラム] キーの等価性とNaN
const map = new Map(); map.set(NaN, "value"); // NaNは===で比較した場合は常にfalse console.log(NaN === NaN); // => false // MapはNaN同士を比較できる console.log(map.has(NaN)); // => true console.log(map.get(NaN)); // => "value"
JSON
オブジェクトをJSON文字列に変換する
const obj = { id: 1, name: "js-primer", bio: null }; const replacer1 = (key, value) => { if (value === null) { return undefined; } return value; }; console.log(JSON.stringify(obj, replacer1)); // => '{"id":1,"name":"js-primer"}' const replacer2 = ["id"]; console.log(JSON.stringify(obj, replacer2)); // => '{"id":1}' // スペース2個でインデントされたJSON console.log(JSON.stringify(obj, null, 2)); /* { "id": 1, "name": "js-primer", "bio": null } */
JSONにシリアライズできないオブジェクト
シリアライズ前の値 | シリアライズ後の値 |
文字列・数値・真偽値 | 対応する値 |
null | null |
配列 | 配列 |
オブジェクト | オブジェクト |
関数 | 変換されない(配列のときはnull) |
undefined | 変換されない(配列のときはnull) |
Symbol | 変換されない(配列のときはnull) |
RegExp | {} |
Map, Set | {} |
toJSON
メソッドを使ったシリアライズ
オブジェクトがtoJSONメソッドを持っている場合、JSON.stringifyメソッドはtoJSONメソッドの返り値を使う。
const obj = { foo: "foo", toJSON() { return "bar"; } }; console.log(JSON.stringify(obj)); // => '"bar"'