lasciva blog

開発して得た知見やwebビジネスのストック

「JavaScript Primer」を読んだ

jsprimer.net

目的、モチベーション

最近はバックエンドしか触ってなくて、久しぶりにフロントエンドのキャッチアップしておきたかったから。

全体の感想

最近の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に変換できる。

文字列

正規表現オブジェクト

関数と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"'