"JavaScript: The Definitive Guide (7th Edition)" を読んだ

感想

David Flanagan の “JavaScript: The Definitive Guide (7th Edition)” (O’Reilly Media) を紙の本で買って読んだ。

“JavaScript: The Definitive Guide” というと定評ある JavaScript のレファレンス本だった。1つ前の 6th Edition は2011年出版で、HTML5 と ECMAScript5 に対応しており、1000ページを超えていた。それから9年。JavaScript の進化は著しく、やっと最新の JavaScript (ECMAScript2010) に対応した 7th Edition が出版された。

よい本だった。さすがに JavaScript が強力になりすぎて、完全なレファレンスマニュアルでもあったセクションはなくなった。結果的に700ページには収まっている。内容はしっかりしているし、網羅性はあるし、より細かくは MDN を参照せよということになる。最新のトピックも JavaScript の周辺ツール(ESLint、Babel、webpack、React、Flowなど)もフォローされている。

JavaScript について触るなら、この本を読んでおくのがよい。こんな分厚いものを読まなきゃいけないのか? JavaScript が元の言語設計の失敗から変に機能追加していかなくてはならなかったこと、クライアントサイド JavaScript とサーバサイド JavaScript (Node.js) のトピックがそれぞれあることで、量が多い。これは JavaScript の責任であって本書の責任ではない。

今であれば素の JavaScript を使うより TypeScript を使うかもしれない。しかし、TypeScript 学習は基本的に JavaScript を知っている前提になっているし、結局 MDN のようなレファレンスを参照するときには JavaScript コードが出て来るので、JavaScript を知らないといけない。この本は TypeScript を取り上げず代わりに Flow を取り上げている。その理由(TypeScript は本質的に JavaScript と別言語である)は理解できるが、現実に今となっては Flow より TypeScript の方が興隆している以上、TypeScript を取り上げてほしかった。

以下、読書メモ。

読書メモ

Preface

Previous editions of this book included a comprehensive reference section. I no longer feel that it makes sense to include that material in printed form when it is so quick and easy to find up-to-date reference material online. If you need to look up anything related to ore or client-side JavaScript, I recommend you visit the MDN website ([https://developer.mozilla.org]). And for server-side Node APIs, I recommend you go directly to the source and consult the Node.js reference documentation ([https://nodejs.org/api]).

以前の版の本書はレファレンスセクションも含んでいたが、オンラインで最新のレファレンスが簡単に見つかるので、やめたという。具体的には MDN と Node.js レファレンスを紹介している。MDN は確かによいサイトなんだけれど、あれは Wiki で誰でも編集できるのにいいのかなぁとは少し思う。多分、英語のオリジナルバージョンについてはきちんと管理されているんだろうけれど。ただ、日本語訳は基本的に微妙。

実際 “JavaScript: The Definitive Guide” は版を重ねるごとにページ数が増えていって第6版(2011)では1000ページを超えていた。しかも第6版が扱っているのは ECMAScript5 (2009)なので、クラスやアロー関数などの大規模な言語仕様追加が行われた ECMAScript 6 (2015) 以降を含んでいない。素直に第7版(2020)でレファレンスを含むと1200ページから1300ページくらいにはなるんじゃないだろうか。この選択は正しいと思う。レファレンスがないため、700ページですんでいる。

第7版の変更点については、著者のブログ記事 Changes in the Seventh Edition of JavaScript: The Definitive Guide · djf.log() に詳細がある。

1. Introduction to JavaScript

1.1 Exploring JavaScript

ブラウザのデベロッパツールのコンソールか Node を使って、JavaScript を試すことができる。

1.2 Hello World

hello.js:

console.log("Hello World!");
$ node hello.js

1.3 A Tour of JavaScript

// Anything following double slashes is an English-language comment.

// A variable is a symbolic name for a value.
// Variables are declared with the let keyword:
let x;
// Values can be assigned to variables with an = sign
x  0;
x // => 0

// JavaScript supports several typs of values
x = 1;             // Numbers.
x = 0.01;
x = "hello world"; // Strings of text in quotation marks.
x = 'JavaScript';
x = true;          // A Boolean value.
x = false;
x = null;          // Null is a special value that means "no value."
x = undefined;     // Undefined is another special value like null.

// JavaScript's most important datatype is the object.
let book = {             // Objcts are enclosed in curly braces.
  topic: "JavaScript", // The property "topic" has value "JavaScript."
  edition: 7           // The property "edition" has value 7.
};                       // The curly brace marks the end of the object.

// Access the properties of an object with . or []:
book.topic                // => "JavaScript"
book["edition"]           // => 7
book.author = "Flanagan"; // Create new properties by assignment.
book.contents = {};       // {} is an empty object with no propeties.

// Conditionally access properties with ?. (ES2020):
book.contents?.ch01?.sect1 // => undefined: book.contents has no ch01 property.

// JavaScript also supports arrays (numerically indexed lists) of values:
let primes = [2, 3, 5, 7]; // An array of 4 values, delimited with [ and ].
primes[0]                  // => 2
primes.length              // => 4
primes[primes.length-1]    // => 7
primes[4] = 9;             // Add a new element by assignment.
primes[4] = 11;            // Or alter an existing element by assignment.
let empty = [];            // [] is an empty array with no elements.
empty.length               // => 0

// Arrays and objects can hodlother arrays and objets.
let points = {
  {x: 0, y: 0},
  {x: 1, y: 1}
};
let data = {
  trial1: [[1,2], [3,4]],
  trial2: [[2,3], [4,5]]
};

// Operators act on values (the operands) to produce a new value.
// Arithmetic operators are some of the simplest:
3 + 2                     // => 5: addition
3 - 2                     // => 1: subtraction
3 * 2                     // => 6: multiplication
3 / 2                     // => 1.5: division
points[1].x - points[0].x // => 1
"3" + "2"                 // => "32": + adds numbrers, concatenates strings

// JavaScript defines some shorthand arithmetic operators
let count = 0; // Define a variable
count++;       // Increment the variable
count--;       // Decrement the variable
count += 2;    // Add 2
count *= 3;    // Multiply by 3
count          // => 3

// Equality and relational operators test whether two values are equal.
// unequal, less than, greater than, and so on. They evaluate to true or false.
let x = 2, y = 3;
x === y           // => false: equality
x !== y           // => true: inequality
x < y             // => true: less-than
x <= y            // => true: less-than or equal
x > y             // => false: greater-than
x >= y            // => false: greater-than or equal
"two" === "three" // => false: the two strings are different
"two" > "three"   // => true: "tw" is alphabetically greater than "th"
false === (x > y) // => true: false is equal to false

// Logical operators combine or invert boolean values
(x === 2) && (y === 3) // => true: both comparisons are true. && is AND
(x > 3) || (y < 3)     // => false: neither comparison is true. || is OR
!(x === y)             // => true: ! inverts a boolean value

// Functions are parameterized bllocks of JavaScript code that we can invoke.
function plus1(x) { // Define a function named "plus1" with parameter "x"
  return x + 1;   // Return a value one larger than the value passed in
}                   // Functions are enclosed in curly braces

plus1(y) // => 4

let square = function(x) { // Functions are values and can be assigned to vars
  return x * x;          // Compute the function's value
};                         // Semicolon marks the end of the assignment.

square(plus1(y)) // => 16

const plus1 = x => x + 1;
const square = x => x * x;

// When functions are assigned to the properties of an object, we call them "methods."
// All JavaScript objects (including arrrays) have methods:
let a = [];      // Create an empty array
a.push(1, 2, 3); // The push() method adds elements to an array
a.reverse();     // Another method: reverse the order of elements.

// We can define our own methods, too.
// The "this" keyword refers to the object on which the method is defined: in this case, the points array from earlier.
points.dist = function() {
  let p1 = this[0];
  let p2 = this[1];
  let a = p2.x-p1.x;
  let b = p2.y-p1.y;
  return Math.sqrt(a*a + b*b);
};
points.dist() // => Math.sqrt(2)

// JavaScript statements include conditionals and loops using the syntax of C, C++, Java, and other languages.
function abs(x) {
  if (x >= 0) {
    return x;
  } else {
    return -x;
  }
}
abs(-10) === abs(10) // => true

function sum(array) {
  let sum = 0;
  for (let x of array) {
    sum += x;
  }
  return x;
}
sum(primes) // => 28

function factorial(n) {
  let product = 1;
  while (n > 1) {
    product *= n;
    n--;
  }
  return product;
}
factorial(4) // => 24

function factorial2(n) {
  let i, product = 1;
  for (i=2; i <= n; i++)
    product *= i;
  return product;
}
factorial2(5) // => 125

class Point {         // By convention, class names are capitalized.
  constructor(x, y) { // Constructo function to initialize new instances.
    this.x = x;       // This keywrod is the new object being initialized.
    this.y = y;
  }                   // No return is necessary in constructor functions.

  distance() {        // Method to compute distance from origin to point.
    return Math.sqrt(this.x * this.x + this.y * this.y);
  }
}

// Use the Point() constructor function with "new" to create Point objects
let p = new Point(1, 1);

// Now use a method of the Point object p
p.distance() // => Math.Sqrt(2)

1.4 Example: Character Frequency Histograms

class DefaultMap extends Map {
  constructor(defaultValue) {
    super();
    this.defaultValue = defaultValue;
  }

  get(key) {
    if (this.has(key)) return super.get(key);
    return this.defaultValue;
  }
}

class Histgram {
  constructor() {
    this.letterCounts = new DefaultMap(0);
    this.totalLetters = 0;
  }

  add(text) {
    text = text.replace(/\s/g, '').toUpperCase();
    for (let character of text) {
      let count = this.letterCounts.get(character);
      this.letterCounts.set(character, count + 1);
      this.totalLetters++;
    }
  }

  toString() {
    // Convert the Map to an array of [key, value] arrays
    let entries = [...this.letterCounts];

    // Sort the array by count, then alphabetically
    entries.sort((a, b) => {
      if (a[1] === b[1]) return a[0] < b[0] ? -1 : 1;
      return b[1] - a[1];
    });

    // Convert the counts to percentage
    for (let entry of entries)
      entry[1] = entry[1] / this.totalLetters * 100;

    // Drop any entries less than 1%
    entries = entries.filter(entry => entry[1] >= 1);

    // Now convert each entry to a line of text
    let lines = entries.map(([l, n]) => `${l}: ${"#".repeat(Math.round(n))} ${n.toFixed(2)}%`);

    return lines.join("\n");
  }
}

// This async (Promise-returning) function creates a Histogram object,
// asynchronously reads chunks of text from standard input, and adds those chunks to the histogram.
async function histogramFromStdin() {
  process.stdin.setEncoding('utf-8');
  let histogram = new Histgram();
  for await (let chunk of process.stdin) {
    histogram.add(chunk);
  }
  return histogram;
}

histogramFromStdin().then(histogram => { console.log(histogram.toString()); });

2. Lexical Structure

2.1 The Text of a JavaScript Program

  • JavaScript は大文字小文字を区別する言語。
  • JavaScript プログラムのトークン間のホワイトスペースは無視される。

2.2 Comments

  • // から改行まではコメントで、JavaScript 的に無視される。
  • /* から */ までもコメント。

2.3 Literals

リテラル(literal) は、プログラムで直接現れるデータ値。詳細は第3章。

12            // number
1.2           // number
"hello world" // string
'Hi'          // string
true          // boolean
false         // boolean
null          // Absence of an object

2.4 Ideentifiers and Reserved Words

識別子(identifier) は、定数・変数・プロパティ・クラスなどに付ける名前。識別子は、英字・アンダースコア・ドル記号から始めて、英字・数字・アンダースコア・ドル記号が続く。

ただし、予約語は識別子として使えない。以下の語はすべて JavaScript では特別で、一部は予約語だが、一部は識別子としても使える。from, set, target だけは、識別子としても、安全かつすでによく使われているので例外として、ほかの以下の語は識別子として使わない方がよい。

as
async
await
break
case
catch
class
const
continue
debugger
default
delete
do
else
enum
export
extends
false
finally
for
from
function
get
if
import
implements
in
instanceof
interface
let
new
null
of
package
private
protected
public
return
set
static
super
switch
target
this
thrwo
true
try
typeof
var
void
while
with
yield

2.5 Unicode

JavaScript プログラムは Unicode で書かれる。

入力が難しい Unicode 文字を書くために、4桁の16進数で \uXXXX または2-6桁の16進数で \u{XXXXXX} というエスケープシーケンスの記法がある。

Unicode 文字列は同じ文字列に対して複数の表現が可能なので、できるだけ正規化して書くべき。

2.6 Optional Semicolons

JavaScript はセミコロンで文を区切る。2つの文が別々の行であれば、2つの文間のセミコロンは省略できる。プログラムの終わりや綴じ波カッコ } の前のセミコロンも、省略できる。

次のコードで、最初のセミコロンは省略できる:

a = 3;
b = 4;

しかし、次のコードでは、最初のセミコロンは必要:

a = 3; b = 4;

JavaScript は改行を常にセミコロンとして扱うわけではない。改行をセミコロンとして扱わなければ、改行の次の非空白文字(トークン)が前の文の継続として解釈できないときだけ、改行をセミコロンとして扱う。

let a
a
=
3
console.log(a)

上のコードは次のように解釈される:

let a; a = 3; console.log(a);

このルールのため驚くべきことに、

let y = x + f
(a+b).toString()

は次のように解釈される:

let y = x + f(a+b).toString();

一般的に、文が (, [, /, +, - から始まっている場合、前の文の継続として解釈可能な可能性がある。

改行をセミコロンとして扱うルールには3つの例外がある。

  1. return, throw, yield, break, continue 文の直後に改行があった場合、JavaScript は必ず改行をセミコロンとして扱う。
  2. ++, -- を後置演算子として使う場合、これらの演算子は必ずオペランドと同じ行に書かなければならない。
  3. アロー関数の => は必ずパラメータリストと同じ行に書かなければならない。

3. Types, Values, and Variables

3.1 Overview and Definitions

JavaScript の型:

  • プリミティブ型(primitive types): immutable
    • 数字(number)
    • 文字列(string)
    • 真偽値(boolean)
    • null
    • undefined
    • シンボル(Symbol): ES6 で追加された
  • オブジェクト型(object types): mutable. 名前と値を持つ プロパティ(properties) の集まり
    • オブジェクト(object)
    • 配列(array)
    • Set
    • Map
    • 型付き配列(TypedArray)
    • 正規表現(RegExp)
    • Date
    • Error

3.2. Numbers

Number は整数と近似実数を表現する型。IEEE-754標準で定義された64ビット浮動小数点数なので、1.79... * 10^308 から 5 * 10^(-324) まで表現できる。-2^53 から 2^53 までの整数も表現できる。

整数リテラル:

0
3
1_000_000_000
0xff
0b10101 // in ES6
0o377   // in ES6

浮動小数点リテラル([digits][.digits][(E|e)[(+|-)]digits]):

3.14
.3333
6.02e23
1.47E-32

JavaScript の数学関数は、オーバーフロー、アンダーフロー、ゼロ除算でエラーにはならない。正のオーバーフローのときは Infinity、負のオーバーフローのときは -Infinity という特別な値になる。アンダーフローのときは 0、負数からのアンダーフローのときは -0 になる。ゼロ除算のときは NaN になる。Infinity, NaN はグローバル定数として定義されている。

NaN はどんな値と比較しても(NaN 自身と比較しても)等しくならない。NaNNaN かどうかをチェックするためには Number.isNaN(x) 関数を使う。一方、-0+0 と等しい。

IEEE-754浮動小数点は2進数表現なので、1/10, 1/100 のような数字は正確に表現できない。

let x = .3 - .2;
let y = .2 - .1;
x === y // false
x === .1 // false
y === .1 // true

ES2020 では、BigInt という数値型が導入された。この型は主に64ビット整数を表現できるようにするために追加された。整数リテラルの末尾に n を付けると、BigInt 型のリテラルになる。BigInt に対して +, -, *, /, %, ** 演算子を使うことができるが、BigInt と通常の Number の組み合わせに対して演算することはできない。

3.3. Text

文字列は不可変な16ビット値の配列で、要素1つ1つが通常 Unicode 文字になる。文字列の 長さ(length) は16ビット値の要素数。16ビット値1つで表現し切れない Unicode 文字はサロゲートペアという2つの16ビット値のペアで表現する必要がある。そのため、JavaScript の文字列の長さが 2 でも、1つの Unicode 文字しか含んでないこともある。ES6 では文字列は iterable になって、for/of ループまたは ... 演算子を使うと、16ビット値でなく、実際の文字ごとに処理することができる。

文字列リテラルは、シングルクォートかダブルクォートかバッククォートでテキストを囲む。文字列リテラルの中のバックスラッシュ文字は特別で、エスケープシーケンスが書ける。

シーケンス文字
\0NUL 文字
\bバックスペース
\t水平タブ
\n改行
\v垂直タブ
\fフォームフィード
\r復帰
\"ダブルクォート
\'シングルクォート
\\バックスラッシュ
\xNN16進数の Unicode 文字
\uNNNN16進数の Unicode 文字
\u{N}16進数の Unicode 文字

+ 演算子で文字列を 結合(concatenate) することができる。

ES6 以降では、バッククォートを使った テンプレートリテラル(template literal) が書ける。バッククォートの中で ${...} と書くと、JavaScript 式として評価されその結果が挿入される。

ES6 では、ビルトインのタグ関数 String.raw() がある。String.raw() 関数はバッククォート内の文字列をエスケープ処理せずにそのまま返す。タグ関数については 14.5 節参照。

String.raw`\n`.length

3.4. Boolean Values

真偽値は真 (true) または偽 (false) を表現する。

undefined, null,0, -0, NaN, "" は真偽値に変換すると false になり、また false のように扱われる(falsy)。それ以外の値はすべて真偽値に変換すると true になり、また true のように扱われる(truthy)。

3.5. null and undefined

null はキーワードで、値がないことを示す特別な値。typeof null"object" を返す。

undefined はグローバル定数で、やはり値がないことを示す特別な値。まだ初期化されていない変数や、オブジェクトの存在しないプロパティを参照したときに undefined が返される。typeof undefined"undefined" を返す。

nullundefined== で等しく扱われる(=== では異なる)。どちらも falsy な値。どちらもプロパティやメソッドを一切持たない。

David Flanagan 曰く、undefined はシステムレベルの予期しないエラーとして、値がないことを示す。null はプログラムレベルの通常のものとして、値がないことを示す。可能なら undefinednull も利用せず、利用する場合は null の方を使う。

Note: nullundefined があるのはちょっと面倒。Common Lisp は nil、Smalltalk は nil、Python は None、Ruby は nil といった感じで、ほかのメジャーな言語に2種類の「値がないことを示す値」がある事例はない。値がないのでなく偽値である false 値はもちろん別として。これは普通なら例外を吐くような場合も、JavaScript ではブラウザのスクリプト利用用途的に例外を出さないようにした(そもそも ECMAScript の歴史を見ると、2nd Edition までは例外処理を含んでいなかった)からだろうか、と想像する。だとしても結果的には、null だけにした方がまだ混乱を招かなかったように思う。一言では、これは言語設計ミスと言ってよい。

3.6. Symbols

シンボルはプロパティ名用に ES6 で導入された。

Symbol() 関数は、同じ引数を渡したとしても常に新しいシンボルを返す。Symbol() を呼ぶことで、新しいプロパティを追加して、既存の同名プロパティを上書きしてしまう心配はない。

Symbol.for() 関数は文字列引数をとって、その文字列に関連付けたシンボルを返す。まだその文字列のシンボルが関連付いていないときだけ、新しいシンボルが生成される。

Note: いまいちこの用途が分からない。Lisp の gensym を含んだシンボル機能なのは分かる。最初から JavaScript にシンボル機能があって全面的に使われているなら、分かる。ただ、あとから追加された機能のせいで、すごく微妙に思える。

3.7. The Global Object

JavaScript インタープリタがスタートしたとき、新しいグローバルオブジェクトが生成され、初期プロパティがいくつか定義される:

  • undefined, Infinity, NaN のようなグローバル定数
  • isNaN(), parseInt(), eval() のようなグローバル関数
  • Date(), RegExp(), String(), Object(), Array() のようなコンストラクタ関数
  • Math, JSON のようなグローバルオブジェクト

Node では、global という名前のプロパティがあって、グローバルオブジェクト自身を指す。

ウェブブラウザでは、window オブジェクトがグローバルオブジェクトとして働く。

ES2020 では、globalThis というグローバルオブジェクトが定義された。

3.8. Immutable Primitive Values and Mutable Object References

プリミティブ値は不可変(immutable)である。プリミティブ値の比較は値(by value)で行われる。

オブジェクトは可変(mutable)である。オブジェクトの比較はレファレンス(by reference)で行われる。同じプロパティ名と値を持っていたとしても、2つのオブジェクトは異なる。

3.9. Type Conversions

JavaScript は必要な型に応じて、値を自動で変換する。

Valueto Stringto Numberto Boolean
undefined"undefined"NaNfalse
null"null"0false
true"true"1
false"false"0
""0false
"1.2"1.2true
"one"NaNtrue
0"0"false
-0"0"false
1"1"true
Infinity"Infinity"true
-Infinity"-Infinity"true
NaN"NaN"false
{} (any object)["object Object]" (use toString())NaN (use valueOf())true
[] (empty array)""0true
[9] (one numeric element)"9"9true
['a'] (any other array)(use join())NaNtrue
function(){} (any function)"function(){...}"NaNtrue

数字としてパースできる文字列は、その数字に変換できる。先頭と末尾のスペースは許されるが、それ以外の文字が含まれていると NaN に変換される。

厳格な等価演算子 === は、型が同じでなければ等しくならない。より柔軟な等価演算子 == もあり、こちらは自動で型変換されて比較する:

null == undefined // => true
"0" == 0 // => true
0 == false // => true
"0" == false // true

明示的な型変換は、Boolean(), Number(), String() 関数を呼べばよい。また nullundefined 以外の値は toString() メソッドがあり、通常 String() 関数と同じ結果になる。

Number クラスの toString() メソッドはオプショナル引数を受け付け、変換するときの基数を指定できる。Number クラスの toFixed() メソッドは引数に指定した桁数の小数点以下を文字列に変換する。toExponential() メソッドも同様だが、指数記法になる。toPrecision() メソッドは引数に指定した有効数字桁の文字列に変換する。有効数字桁が整数部分を表示するのに不十分な場合、指数記法を使う。

Number() 関数で文字列を数字に変換するとき、基数10で整数または浮動小数としてパースして、無駄な文字列は許されない。グローバル関数 parseInt() は整数のみをパースして、parseFloat() は整数と浮動小数をパースする。文字列が 0x0X で始まっていたら parseInt() は16進数としてパースする。parseInt()parseFloat() も、先頭のホワイトスペースはスキップし、数字をパースしたあとの不要な文字列は無視する。最初の非空白文字が数字でなければ、NaN を返す。parseInt() はオプショナルな第2引数を受け付け、パースする基数を指定できる。

3.10. Variable Declaration and Assignment

ES6 以降では、let キーワードで変数を宣言して、const キーワードで定数を宣言する:

let i, sum; // = undefined
let message = "hello";

const H0 = 74;

let, const はブロックスコープである。つまり、その宣言があるブロック内でのみ定義される。トップレベルで宣言した場合、グローバル変数・定数になり、そのファイル全体のスコープになる。同じスコープで同じ名前を宣言するのはシンタックスエラーだが、内側のスコープで同じな名前を宣言するのは許される。

ES6 以前の JavaScript では、var キーワードが唯一の変数宣言の方法だった。varlet に似ているが、いくつかの点で異なる:

  • var はブロックスコープを持たず、その宣言がある関数全体がスコープになる。
  • トップレベルで var で宣言した場合、グローバル変数になり、かつ、グローバルオブジェクトのプロパティになる。
  • var は同じ変数名を複数宣言することができる。
  • var 宣言は ホイスト(巻き上げ) される。つまり、var 宣言はその宣言がある関数のトップに巻き上げされる。

ES6 では、分割代入(destructuring assignment) という構文がある。分割代入では、右辺の配列またはオブジェクトの要素を抽出して、右辺の変数群にセットする:

let [x, y] = [1, 2]; // same as let x=1, y=2
[x, y] = [y, x]; // Swap the value
[,x,,y] = [1, 2, 3, 4]; // x == 2; y == 4
[x, ...y] = [1, 2, 3, 4]; // y == [2, 3, 4]
let [a, [b, c]] = [1, [2,2.5], 3]; // a == 1; b == 2; c == 2.5

let {r, g, b} = {r: 0.0, g: 0.0, b: 0.0, a: 1.0}; // r == 0.0, g == 0.0; b == 0.0
const { cos: cosine, tan: tangent } = Math; // Same as const cosine = Math.cos, tangent = Math.tan

Noe: var 変数宣言は言うまでもなく let によって存在価値を失った。ただ廃止にしようにも、Web 上の古い JavaScript コードを切り捨てるわけにもいかないので、ずっと残る。

4. Expressions and Operators

4.1 Primary Expressions

原始式 (primary expressions) は、定数、リテラル、this、変数参照のいずれか。原始式は、ほかの式を含まない。

1.23
"hello"
/pattern/
true
false
null
this
i
sum
undefined

4.2 Object and Array Initializers

オブジェクト初期化子(object initializers)配列初期化子(array initalizers) は、オブジェクトまたは配列を生成する式である。

[]
[1+2, 3+4]

{}
{ x: 2.3, y: -1.2 }

4.3 Function Definition Expressions

関数定義式(function definition expressions) は、JavaScript を定義して返す。

let square = function(x) { return x * x; }
(x) => { return x * x; }

4.4 Property Access Expressions

プロパティアクセス式(property access expressions) は、オブジェクトのプロパティや配列の要素の値にアクセスする。

expression . identifier
expression [ expression ]

ES2020 ではオプショナルチェーニングをサポートする。これは左辺が nullundefined のとき、右辺のプロパティアクセスをやめて undefined を返す:

expression ?. identifier
expression ?.[ expression ]

let a = { b: null };
a.b?.c.d // => undefined

上の例で (a.b?.c).dundefined.d に評価された結果 TypeError になるが、カッコのない a.b?.c.d?. が short-circuit であるため undefined を返す。

4.5 Invocation Expressions

呼び出し式(invocation expressions) は、関数・メソッドを呼び出す構文である。s

f(0)
Math.max(x, y, z)
a.sort()

ES2020 では条件付き呼び出しが行える。これは左辺が null または undefined のときには、関数呼び出しをせず右辺も評価せず undefined を返す:

log?.(x)

4.6 Object Creation Expressions

オブジェクト生成式(object creation expressions) は、新しいオブジェクトを生成して、関数(コンストラクタ)を呼び出してプロパティの初期化を行う。

new Object()
new Object
new Point(2, 3)

Note: オブジェクト生成式はいるのか? 無意味に C++/Java の構文を真似ているだけじゃないか?

4.7 Operator Overview

PrecedenceOperatorOperationANTypes
1++Pre/post incrementR1lval -> num
1--Pre/post decrementR1lval -> num
1-負数R1num -> num
1+数字への変換R1any -> num
1~ビット反転R1int -> int
1!真理値の反転R1bool -> bool
1deleteプロパティの削除R1lval -> bool
1typeof型名R1any -> str
1voidundefined を返すR1any -> undef
2**累乗R2num, num -> num
3*, /, %乗算・除算・剰余L2num, num -> num
4+, -加算・減算L2num, num -> num
4+文字列結合L2str, str -> num
5<<左シフトL2int, int -> num
5>>符号付き右シフトL2int, int -> num
5>>>ゼロ埋め右シフトL2int, int -> num
6<, <=, >, >=数字の比較L2num, num -> bool
6<, <=, >, >=文字列の比較L2str, str -> bool
6instanceofオブジェクトクラスのテストL2obj, func -> bool
6inプロパティ存在のテストL2any, obj -> bool
7==非厳格な等価L2any, any -> bool
7!=非厳格な非等価L2any, any -> bool
7===厳格な等価L2any, any -> bool
7!==厳格な非等価L2any, any -> bool
8&ビット ANDL2int, int -> int
9^ビット XORL2int, int -> int
10``ビット ORL2
11&&論理 ANDL2any, any -> any
12``論理 ORL
13??1つ目の定義されたオペランドL2any, any -> any
14?:条件式R2any, any -> any
15=変数・プロパティへの代入R2lval, any -> any
15**=, *=, /=, %=, +=, -=, &=, ^=, `=, «=, »=, »>=`演算と代入R2
16,右辺を返すL2any, any -> any

Note: そういえば JavaScript には演算子オーバーロードがない。これは結構微妙なんじゃないか。その一方で delete まで演算子なので、行列演算に + は使えないし、独自コレクションクラスの要素削除に delete は使えない。

4.8 Arithmetic Expressions

+, -, ++, --, &, |, ^, ~, <<, >>, >>>。省略。

4.9 Relational Expressions

==, ===, !=, !==, <, >, <=, >=, in, instanceof。省略。

4.10 Logical Expressions

&&, ||, !。省略。

4.11 Assignment Expressions

=, +=, -=, *=, /=, %=, **=, <<=, >>=, >>>=, &=, |=, ^=。省略。

4.12 Evaluation Expressions

eval()。省略。

4.13 Miscellaneous Operators

?:, ??, typeof, delete, await, void, ,。省略。

5. Statements

5.1 Expression Statements

JavaScript の一番シンプルな文は、(副作用を持つ)式。

greeting = "Hello " + name;
i *= 3;
counter++;
delete o.x;
console.log(debugMessage);

5.2 Compound and Empty Statements

文ブロック(statement block) は波カッコで複数の文を囲って、複数の文を1つの 複合文(compound statement) にする。文ブロックの終わりにはセミコロンを付けない。

{
  x = Math.PI;
  cx = Math.cos(x);
  console.log("cos(n) = " + cx);
}

5.3 Conditionals

if 文は、 expression を評価してその結果が truthy なときだけ statement1 が実行される。else が続く場合には、expression が falsey なときだけ statement2 が実行される。

if (expression)
  statement1

if (expression)
  statement1
else
  statement2

switch 文は、expression を評価してその結果が case 式と等値 (===) のときだけその case 文を実行する。expression がどの case 式と一致せず default 文があれば default 文を実行する。case 文の終わりに break 文を付与しなければ、fall through して下の文に進む。

switch (expression) {
case expr1:
  // Execute code block #1.
  break;
case expr2:
  // Execute code block #2.
  break;
default:
  // Execute clode block #4.
  break;
}

5.4 Loops

while 文は、expression を評価してその結果が truthy なら statement を実行して、それを expression が truthy の間繰り返す。

while (expression)
  statement

do/while 文は、先に1度だけ statement を実行してから、あとは while 文と同じだ。

do
  statement
while (expression);

for 文は、まず initialize 式を実行する。それから test を評価して truthy の間、statement を実行して increment を実行する。initialize, test, increment は省略できる。

for (initialize; test; increment)
  statement

for/of 文 (ES6) は、iterable なオブジェクト(配列、文字列、セット、マップなど) object の要素を1つ1つ取り出して variable にセットして、statement を実行して繰り返す。変数 variable の前には let, const, var で宣言を付けることもできる。

for (variable of object)
  statement

for/in 文は、任意のオブジェクト object のプロパティ名を1つ1つ取り出して variable にセットして、statmenet を実行して繰り返す。変数 variable の前には let, const, var で宣言を付けることもできる。

for (variable in object)
  statement

5.5 Jumps

任意の文は、識別子とコロンで ラベル(label) を付けることができる。break または continue 文でラベル名を指定することで、ラベル付けした文に対して break または continue を実行することができる。

identifier: statement

break 文は、最も内側で囲まれているループまたは switch 文から抜ける。ラベル名を指定した break 文は、ラベルを付けた文から抜ける。

continue 文は、最も内側で囲まれているループの次の繰り返しまでスキップする。for 文の increment は実行される。ラベル名を指定した continue 文は、ラベルを付けたループの次の繰り返しまでスキップする。

関数定義内の return 文は、その関数を終了して expression を返す。expression を省略すると undefined を返す。return がないまま関数の終わりに来た場合も、unefined を返す。

return expression;

関数定義内の yield 文 (ES6) は、return 文に近く、関数から値を返す代わりに、ジェネレータ関数で次の値を生成する。

throw 文は、expression を例外として投げる。例外が投げられると、処理は中断して、最も内側の例外ハンドラまでジャンプする。例外ハンドラがなければ、エラーとして扱われユーザに報告される。

throw expression;

try/catch/finally 文は例外ハンドラで、try 節の statement1 を実行する。try 節の中で例外が投げられると、catch 節でその例外を変数 e にセットしてハンドリングして statement2 を実行する。finally 節の statement3 は、例外が発生してもしなくとも、return, break, continue が呼ばれたとしても、try/catch の処理のあとに最後に実行される。catch 節と finally 節は省略可能。

try {
  statement1
} catch (e) {
  statement2
} finally {
  statement3
}

Note: 例外クラスごとに catch を振り分ける機能はない。

5.6 Miscellaneous Statements

with 文は、指定したオブジェクト object のプロパティを変数スコープに加えて statement を実行する。

with (object)
  statement

debugger 文は、通常何もしない。デバッガプログラムが動いているときには、インタープリタはデバッガのブレークポイントとして処理してくれることを期待する。

"use strict" ディレクティブは ES5 で導入された。"use strict" ディレクティブは、スクリプトの最初か関数ボディの最初にのみ置くことができて、その範囲のコードが strict であることを意味する。strict モードは次のような制限がある:

  • with 文は、許されない。
  • すべての変数は、宣言しなければならない。
  • メソッドでなく関数として呼び出したときには、tihsundefined になる。
  • 書き込み可能でないプロパティへの代入や、拡張可能でないオブジェクトへのプロパティ追加は、TypeError になる。
  • eval() に渡すコードは、変数の宣言や関数の定義ができない。
  • 関数の Arguments オブジェクトは、関数に渡された値の静的なコピーになる。
  • delete 式に非修飾の識別子(変数、関数、関数パラメータなど)を渡すと、SyntaxError になる。
  • 設定可能でないプロパティを削除しようとすると、TypeError になる。
  • 同名のプロパティを複数回定義するようなオブジェクトリテラルは、SyntaxError になる。
  • 同名のパラメータ名を複数回定義するような関数宣言は、SyntaxError になる。
  • 8進数リテラルは、許されない。
  • evalarguments はキーワードのように扱われ、値を変更することは許されない。
  • argguments.callerarguments.callee は、TYpeError になる。

5.7 Declarations

const, let, var 宣言は3.10節で扱った。

function 宣言は第8章で扱う。

class 宣言は第9章で扱う。

import, export 宣言は第10章で扱う。

6. Objects

6.1 Introduction to Objects

オブジェクトは、名前と値からなる プロパティ(property) の順序なし集合である。オブジェクトは自身のプロパティのほかにも、プロトタイプという別のオブジェクトのプロパティも継承する。

6.2 Creating Objects

オブジェクトの生成は、オブジェクトリテラル、new キーワード、Object.create() 関数を使って行える。

オブジェクトリテラル(object literal) は、name: value ペアをコンマで区切られたリストで、波カッコで囲んだもの。プロパティ名は JavaScript 識別子か文字列リテラルである。

let empty = {};
let point = { x: 0, y: 0 };
let book = {
  "main title": "JavaScript",
  "sub-title": "The Definitive Guide",
  for: "all audiences",
  author: {
    firstname: "David",
    surname: "Flanagan"
  }
};

new 演算子は、コンストラクタ関数呼び出しを続けて、新しいオブジェクトを作成して初期化する。

let o = new Object();
let a = new Array();

Object.create() は、第1引数をプロトタイプとして新しいオブジェクトを作成する。プロトタイプがないオブジェクトを作成したいときには、null を引数に渡す(ただしこれは toString() のようなメソッドも含まない)。通常の空オブジェクト {} を生成したいときには、Object.prototype を引数に渡す。Object.create() の用途の1つは、ライブラリ関数にオブジェクトを渡すとき、望まない変更がオブジェクトに行われないように、コピーを渡すこと。

let o1 = Object.create({x: 1, y: 2});
o1.x + o1.y // => 3

6.3 Querying and Setting Properties

プロパティの値を取得するためには、ドット(.)または角カッコ([])演算子を使う。ドット演算子の場合、右辺はプロパティ名の識別子でなければならない。角カッコ演算子の場合、カッコ内の値は評価した結果がプロパティ名になるような式でなければならない。

let author = book.author;
let title = book["main title"];

プロパティを作成したりセットしたりするには、左辺にドットまたは角カッコを使ってプロパティを指定して、代入式を書く。

book.edition = 7;
book["main title"] = "ECMAScript";

存在しないプロパティを取得しようとすることは、エラーでない。undefined を返す。しかし、存在しないオブジェクト(null, undefined)のプロパティを取得しようとすることは、エラーになる。

book.subtitle // => undefined
book.subtitle.length // !TypeError

6.4 Deleting Properties

delete 演算子は、オブジェクトからプロパティを削除する。delete は、削除に成功したか、削除に意味がなかった場合(存在しないプロパティを削除しようとしたなど)、true を返す。configurable 属性が false なプロパティを削除しようとすると失敗して、strict モードでは TypeError になり、non-strict モードでは false を返す。

delete book.author;
delete book["main title"];

delete 1; // => true: nonsense, but true anyway
delete Object.prototype; // => !TypeError or false

6.5 Testing Properties

オブジェクトがある名前のプロパティ名を持つかは、in 演算子、hasOwnProperty() メソッド、propertyIsEnumerable() メソッドで調べられる。

in 演算子は、右辺のオブジェクトが左辺のプロパティ名を持つかどうかを返す。

hasOwnProperty() メソッドは、オブジェクトが引数のプロパティ名を自身で持つかどうかを返す。継承したプロパティの場合は false を返す。

propertyIsEnumerable() メソッドは、オブジェクトが引数のプロパティ名を自身で持ち、かつ、enumerabletrue であるかどうかを返す。

let o = { x: 1 };
"x" in o // => true
o.hasOwnProperty("x") // => true
o.propertyIsEnumerable("x") // => true

6.6 Enumerating Properties

オブジェクトの全プロパティを反復処理したいときには、for/in ループ、Object.keys() メソッド、Object.getOwnPropertyNames() メソッド、Object.getOwnPropertySymbols() メソッド、Reflect.ownKeys() メソッドが使える。

ES6 は Object.keys() などのメソッドのプロパティ順を定義している。

  1. 非負整数が名前の文字列プロパティ。数値の昇順で並ぶ。
  2. ほかの文字列プロパティ。オブジェクトに追加・定義された順序で並ぶ。
  3. Symbol プロパティ。オブジェクトに追加された順序で並ぶ。

6.7 Extending Objects

Object.assign() メソッドは、オブジェクトのプロパティを別のオブジェクトにコピーする。第1引数のオブジェクトに対して、第2引数のオブジェクトの enumerable な自身のプロパティをコピーして、第3引数のオブジェクトの enumerable な自身のプロパティをコピーして……と行う。

Object.assgin(o, defaults); // overwrites everything in o with defaults

o = Objet.assign({}, defaults, o); // create a new object, copy defaults into it, and override defaults with properties in o

6.8 Serializing Objects

JSOn.stringify()JSON.parse() は、JavaScript オブジェクトを JSON 形式でシリアライズしたりデシリアライズしたりする。JSON 構文は、JavaScript 構文のサブセットで、すべての JavaScript の値を表現できるわけではない。オブジェクト、配列、文字列、有限数、truefalsenull はサポートしている。NaN, Infinity, -Infinity はサポートしておらず、null にシリアライズされる。Date オブジェクトは ISO 形式の日付文字列にシリアライズされる。関数、RegExp、Error オブジェクト、undefined はシリアライズできない。オブジェクトは、enumerable な自身のプロパティしかシリアライズされない。

6.9 Object Methods

ほとんどすべての JavaScript オブジェクトは Object.prototype を継承している。Object.prototype はいくつかのメソッドを定義している。

toString() メソッドは、オブジェkツオの値を表現した文字列を返す。

toLocaleString() メソッドは、オブジェクトの文字列をローカライズした表現で返す。デフォルトでは toString() を呼ぶだけ。

valueOf() メソッドは、文字列以外のプリミティブな型(数字など)に変換する必要があるときに呼ばれる。デフォルトの valueOf() メソッドは何もしない。Date クラスの valueOf() は、日付を数字に変換する。

toJSON() メソッドは、デフォルトでは定義されていない。JSON.stringify() メソッドは、シリアライズするときに toJSON() メソッドがあるかを調べて、あれば toJSON() メソッドを呼んでシリアライズする。

6.10 Extended Object Literal Syntax

最近のバージョンの JavaScript は、オブジェクトリテラル構文を拡張している。

ES6 では、プロパティ名と値の変数が同名のときには、コロンを省略して簡略化できる。

let x = 1, y = 2;
let o1 = { x: x, y: y };
let o2 = { x, y };

ES6 では、算出プロパティ(computed property) という仕組みによって、動的に評価されたプロパティ名を使うことができる。

const PROPERTY_NAME = "p1";
function computePropertyName() { return "p" + 2; }
let p = {
  [PROPERTY_NAME]: 1,
  [computePropertyName()]: 2
};

ES6 では、シンボルをプロパティ名に使うことができる。

const extension = Symbol("my extension symbol");
let o = {
  [extension]: 0;
};

ES2018 では、スプレッド演算子(spread operator) ... を使うことで、既存のオブジェクトのプロパティをコピーすることができる。

let position = { x: 0, y: 0 };
let dimensions = { width: 100, height: 75 };
let rectr = { ...position, ...dimensions };

ES6 では、メソッドの定義を簡略化できる。

let square = {
  area() { return this.side * this.side; },
  side: 10
};

ES5 では、アクセサプロパティ(accessor property) によって、プロパティのゲット・セット時にメソッドを呼び出せる。

let o {
  dataProp: value,
  get accessorProp() { return this.dataProp; },
  set accessorProp(value) { this.dataProp = value; }
};

7. Arrays

7.1 Creating Arrays

要素をコンマで区切って角カッコで囲むと、配列リテラルによる新しい配列を作ることができる。

let empty = [];
let primes = [2, 3, 5, 7, 11];
let misc = [1.1, true, "a"];
let count = [1,,3];

ES6 以降では、スプレッド演算子 ... を使うことで、配列リテラルの中に別の配列要素を含めることができる。スプレッド演算子は配列のシャローコピーを作るのに便利。スプレッド演算子は任意の iterable オブジェクトに対して動く。

let a = [1, 2, 3];
let b = [0, ...a, 4]; // b == [0, 1, 2, 3, 4]
let copy = [...a];

Array()コンストラクタを使っても配列を作ることができる。

let a = new Array(); // empty array
let b = new Array(10); // length=10
let c = new Array(5, 4, 3, 2, 1, "testing, testing"); // two or more elements

ES6 以降では、Array.of() 関数を使って、引数をそのまま配列要素として配列を作ることができる。Array() コンストラクタは2つ以上の引数があるときだけだが、Arary.of() は1つの引数でも同じ挙動をしてくれる。

Array.of() // ==> []
Array.of(10) // ==> [10]
Array.of(1, 2, 3) // ==> [1, 2, 3]

ESS6 以降では、Array.from() 関数を使って、引数の iterabole オブジェクトのコピー配列を作ることができる。[..iterable] に等しい。

let copy  = Array.from(original);

7.2 Reading and Writing Array Elements

[] 演算子を使って配列の要素にアクセスできる。

配列はオブジェクトの一種にすぎない。数値のインデックス 1 は文字列 "2" に変換されて、プロパティ名として使われる。境界エラーはなく、配列の長さの範囲外にアクセスしても undefined が返されるだけ。

7.3 Sparse Arrays

疎な配列(sparse array) は、要素が連続していないような配列。疎な配列は、要素の数より length プロパティの値が大きくなる。

7.4 Array Length

配列には length プロパティがあり、最大のインデックス + 1 という値になる。配列の length を現在の値より小さな値にセットすると、新しい length 値以上のインデックスの要素はすべて削除される。逆に配列の lenghth を現在の値より大きな値にセットすると、剰余な部分は疎な配列領域になる。

7.5 Adding and Deleting Array Elements

配列に要素を追加するためには、値を新しいインデックスにセットすればよい。

let a = [];
a[0] = "zero";
a[1] = "one";

push() メソッドを使っても、配列の終わりに要素を追加できる。unshift() メソッドで、配列の最初に要素を追加できる。pop() メソッドで、配列の最後の要素を削除できる。shift() メソッドで、配列の最初の要素を削除できる。

let a = [];
a.push("zero");
a.push("one");

配列の要素を削除するためには、delete 演算子を使う。delete 演算子では length の値は変わらず、配列は疎になる。

let a = [1, 2, 3];
delete a[2]; // a == [1, 2, <1 empty item>]
2 in a // ==> false
a.length // ==> 3

7.6 Iterating Arrays

ES6 からは、for/of ループを使って配列の要素を繰り返し処理することができる。forEach() メソッドを使ってもよい。

7.7 Multidimensional Arrays

JavaScript は多次元配列をサポートしていないが、配列の配列を使ってそれらしいことはできる。

7.8 Array Methods

let a = [1, 2, 3, 4, 5], sum = ;
data.forEach(value => { sum += value; });

a.map(x => x*x) // => [1, 4, 9, 16, 25]

a.filter(x => x < 3) // => [1, 2]

a.findIndex(x => x === 3) // => 2
a.findIndex(x => x < 0) // => -1
a.find(x => x % 5 === 0) // => 5
a.find(x => x % 7 === 0) // => undefined

a.every(x => x < 10) // => true
a.every(x => x % 2 === 0) // => false
a.some(x => x % 2 === 0) // => true
a.some(isNaN) // => false

a.reduce((x, y) => x+y, 0)// => 15

[1, [2, [3]]].flat() // => [1, 2, [3]]
[1, [2, [3]]].flat(2) // => [1, 2, 3]

a.concat(6, 7) // => [1, 2, 3, 4, 5, 6, 7]

a.slice(0, 3) // => [1, 2, 3]

a.splice(1, 2) // => [2, 3]; a == [1, 4, 5]
a.splice(2, 0, "a", "b") // => []; a == [1, 2, "a", "b", 3, 4, 5]

a.indexOf(2) // => 1
a.lastIndexOf(2) // => 1

a.includes(2) // => true

a.sort(); // a == [1, 2, 3, 4, 5]

a.reverse(); // a == [5, 4, 3, 2, 1]

a.join() // => "1,2,3,4,5"
a.join(" ") // => "1 2 3 4 5"

Array.isArray([]) // => true

7.9 Array-Like Objects

7.10 Strings as Arrays

JavaScript 文字列は、UTF-16 Unicode 文字の読み込み専用配列のようにふるまう。

8. Functions

8.1 Defining Functions

関数宣言は、function name(param1, param2, ...) { ... } という構文になる。

function distance(x1, y1, x2, y2) {
  let dx = x2 - x1;
  let dy = y2 - y1;
  return Math.sqrt(dx*dx + dy*dy);
}

関数式は、関数宣言に近いが、式として現れ、関数名は省略できる。

const square = function(x) { return x*x; }

ES6 以降では、アロー関数というコンパクトな構文の関数式がある:(param1, param2, ...) => { ... }。ボディが return 文1つだけの場合、波カッコと return を省略することができる。また、引数が1つだけの場合、丸カッコを省略することができる。function キーワードによる関数定義と違ってアロー関数は、関数が定義されているコンテクストの this の値を継承する。

const sum = (x, y) => { return x + y; };
const add1 = x => x + 1;

8.2 Invoking Functions

関数呼び出しは、func(arg1, arg2, ...) という構文になる。non-strict モードでは、this の値はグローバルオブジェクトになる。strict モードでは、this の値は undefined になる。ただしアロー関数の場合、this の値はいずれにしても定義された箇所の this になる。

let d = distance(0, 0, 2, 1);

メソッド呼び出しは、obj.method(arg1, arg2, ...) という構文になる。obj オブジェクトの method プロパティが返す関数が呼び出され、this の値は obj になる。ネストした関数では、いったん別の変数にセットしておかないと親の this にアクセスすることができない。アロー関数ならこの問題を解決できる。

rect.setSize(width, height);

コンストラクタ呼び出しは、関数呼び出しまたはメソッド呼び出しの前に new キーワードを付ける。引数がない場合、丸カッコは省略できる。コンストラクタ呼び出しは、コンストラクタの prototype プロパティを継承した新しい空オブジェクトを生成して、this にバインドして、コンストラクタ関数を呼び出す。コンストラクタ関数が特に値を返さないか、プリミティブ値を返した場合、this の値が返される。

call() または apply() メソッドを使っても、間接的に関数を呼び出せる。8.7 節参照。

ほかにも、暗黙の関数呼び出しがある。ゲッター、セッターのメソッド呼び出し。文字列コンテクストにおける非文字列オブジェクトに対する toString() メソッドの呼び出し。iterable オブジェクトの反復処理におけるメソッド呼び出し。タグテンプレートリテラルにおける関数呼び出し。プロキシオブジェクト。

8.3 Function Arguments and Parameters

JavaScript 関数の呼び出しは、渡される引数の型も数もチェックしない。

関数が宣言したパラメータより少ない引数で呼び出されたとき、残りのパラメータはデフォルト値 undefined がセットされる。

ES6 以降では、関数のパラメータにデフォルト値を定義できる。

function getPropertyNames(o, a = []) {
  for (let property in o) a.push(property);
  return a;
}

ES6 以降では、残余パラメータ で、任意の数の引数で呼び出すことができる。

function max(first=-Infinity, ...rest) {
  let maxValue = first;
  for (let n of rest) {
    if (n > maxValue) maxValue = n;
  }
  return maxValue;
}

ES6 以前は残余パラメータがないため、Arguments オブジェクトを使って可変引数関数を書く。arguments という変数名で配列風の Arguments オブジェクトに参照できる。

function max(x) {
  let maxValue = -Infinity;
  for (let i = 0; i < arguments; i++) {
    if (arguments[i] > maxValue) maxValue = arguments[i];
  }
  return maxValue;
}

スプレッド演算子 ... を使って、配列の要素を展開することができる。

let numbers = [5, 2, 10, -1, 9, 100, 1];
Math.min(...numbers);

関数定義のパラメータリスト内を配列リテラルやオブジェクトリテラルで書くと、引数をその構造に分割代入する。

function vectorAdd([x1, y1], [x2, y2]) {
  return [x1 + x2, y1 + y2];
}

function vectorMultiply({x, y}, scalar) {
  return { x: x * scalar, y: y * scalar};
}

8.4 Functions as Values

JavaScript では、関数はただの構文ではなく値でもあり、変数に代入したり、オブジェクトのプロパティや配列の要素にセットしたり、関数の引数に渡したりできる。

JavaScript の関数はプリミティブ値ではなくオブジェクトの一種なので、プロパティを持つことができる。関数が “static” 変数を必要とする場合、関数のプロパティを使うと便利。

uniqueIneger.counter = 0;
function uniqueInteger() {
  return uniqueInteger.counter++;
}

関数内で宣言した変数は関数外からは見えない。一時的な名前空間として関数を定義することもできる。

(function() {
  // ttemporary namespace
}());

8.6 クロージャ

JavaScript は レキシカルスコープ(lexical scope) を使っている。

function counter(n) {
  return {
    get count() { return n++; }
    set count(m) {
      if (m > n) n = m;
      else throw Error("count can only be set to a larger value");
    }
  };
}

8.7 Function Properties, Methods, and Constructor

関数の length プロパティは読み込み専用で、関数の引数の数 アリティ(arity) を返す。

関数の name プロパティは読み込み専用で、関数を定義したときの名前を返す。

アロー関数以外の関数はすべて、prototype プロパティを持つ。

関数の call()apply() メソッドは、その関数を呼ぶ。call() メソッドの第1引数は呼び出し関数の this になり、第2引数以降はそのまま呼び出し関数の第1引数以降になる。apply() メソッドの第1引数は呼び出し関数の this になり、第2引数は呼び出し関数の引数群の配列になる。

関数 f に対してオブジェクト o を渡して bind() メソッドを呼ぶと、o のメソッドとして束縛された関数 f を返す。

Function() コンストラクタは新しい関数を返す。

const f = new Function("x", "y", "return x*y;");

8.8 Functional Programming

配列メソッド map()reduce() を使って、引数に関数を渡して配列処理ができる。

高階関数(higher-order function) は、関数を引数にとって新しい関数を返すような関数。

function not(f) {
  return function(...args) { return !(f.apply(this, args)); }
}

9. Classes

9.1 Classes and Prototypes

// factory function
function range(from, to) {
  let r = Object.create(range.methods);
  r.from = from;
  r.to = to;
  return r;
}

range.methods = {
  includes(x) { return this.from <= x && x <= this.to; },
  *[Symbol.iterator]() { for (let x = Math.ceil(this.from); x <= this.to; x++) yield x; },
  toString() { return "(" + this.from + "..." + this.to + ")"; }
}

let r = range(1, 3);
r.includes(2) // => true
r.toString() // => "(1...3)"
[...r] // => [1, 2, 3]

9.2 Classes and Constructors

// constructor
function Range(from, to) {
  this.from = from;
  this.to = to;
}

Range.prototype = {
  includes(x) { return this.from <= x && x <= this.to; },
  *[Symbol.iterator]() { for (let x = Math.ceil(this.from); x <= this.to; x++) yield x; },
  toString() { return "(" + this.from + "..." + this.to + ")"; }
}

let r = new Range(1, 3);
r instanceof Range // => true

9.3 Classes with the class Keyword

class Range {
  constructor(from, to) {
    this.from = from;
    this.to = to;
  }

  static parse(s) {
    let matches = s.match(/^\((\d+)\.\.\.(\d+)\)$/);
    if (!matches) {
      throw new TypeError(`Cannot parse Range from "${s}".`);
    }
    return new Range(parseInt(matches[1]), parseInt(matches[2]));
  }

  includes(x) { return this.from <= x && x <= this.to; }
  *[Symbol.iterator]() { for (let x = Math.ceil(this.from); x <= this.to; x++) yield x; }
  toString() { return "(" + this.from + "..." + this.to + ")"; }
}

class SSpan extends Range {
  constructor(start, length) {
    if (length >= 0) super(start, start + length);
    else super(start + length, start);
  }
}
  • class 宣言内のコードはすべて暗黙で strict モードになる。
  • 関数宣言と違って、クラス宣言はホイストされない。宣言前にインスタンス化することはできない。

9.4 Adding Methods to Existing Classes

新しいメソッドをプロトタイプオブジェクトに追加することで、既存クラスへのメソッド追加ができる。

String.prototype.startsWith = function (s) {
  return this.indexOf(s) === 0;
}

9.5 Subclasses

class/extends 記法以前でのサブクラスの作り方:

Function Span(start, length) { ... }
Span.prototype = Object.create(Range.prototype);
Span.prototype.constructor = Span;
  • extends キーワードでクラスを定義する場合、スーパークラスのコンストラクタを呼ぶために super() を呼ばないといけない。
  • サブクラスでコンストラクタを定義しなかったら、自動で super() を呼ぶコンストラクタが定義される。
  • super() を呼ぶまでは this キーワードは使わない方がよい。
  • new キーワードなしで呼ばれた関数では new.keyword が undefined になる。コンストラクタ関数では new.target はコンストラクタへの参照になる。

10. Modules

10.1 Modules with Classes, Objects, and Closures

旧来の JavaScript の機能の範囲でもモジュールのようなものは表現できる:

const modules = {};
funtion require(moduleName) { retuern modules[moduleName]; }

modules["stats.js"] = (function() {
  const exports = {}

  // The contents of the stats.js file go here:
  const sum = (x, y) => x + y;
  const square = x => x * x;
  exports.mean = function(data) { return data.reduce(sum) / data.length; };
  exports.stddev = function(data) {
    let m = mean(data);
    return Math.sqrt(data.map(x => x - m).map(square).reduce(sum) / (data.length-1));
  };

  return exports;
}());

const stats = require ("stats.js");
stats.mean([1, 3, 5, 7, 9]) // => 5

複数のファイルを1つのファイルにまとめあげる webpack や Parcel のようなコードバンドルツールによって、実質上のようなコードが生成される。

10.2 Modules in Node

NOde はグローバルな exports オブジェクトを定義している。Node モジュールを定義するときにはこの exports にエクスポートしたいプロパティをセットする。

exports.mean = function(data) { return data.reduce(sum) / data.length; };

ただし、1つのモジュールで1つの関数やクラスしかエクスポートしないときには、代わりに module.exports にエクスポートしたい1つの値をセットする。

module.exports = class BitSet { ... };

Node でモジュールをインポートするときには require() 関数を呼ぶ。その引数はインポートしたいモジュール名で、エクスポートされた値が返される。

Node ビルトインのモジュールやパッケージマネージャでインストールずみのおmジュールをインポートしたければ、モジュール名をそのまま指定すればよい。自分のコードのモジュールをインポートしたければ、そのモジュールのファイルパスを指定する。

モジュールが1つの関数かクラスをエクスポートしているだけなら、require すればその値が返ってくる。モジュールが複数のプロパティを持つオブジェクトであれば、オブジェクト全体をインポートしてもいいし、その中で特定のプロパティだけインポートしてもいい。

const fs = require('js');
const stats = require('./stats.js');
const { stddev } = require('./stats.js');

10.3 Modules in ES6

ES6 では importexport キーワードが追加されて、モジュールシステムをサポートするようになった。

定数・変数・関数・クラスをエクスポートしたければ、宣言の前に export を付ける。あるいは、エクスポートしたい値をまとめて export { ... } の中で設定する。

// A
export const PI = Math.PI;
export class Circle { ... }

// B
const PI = Math.PI;
class Circle { ... }
export { Circle, PI };

// C
export default class BitSet { ... }

また、宣言の前に export default を付ければ、単一の値をそのままエクスポートできる。

モジュールをインポートしたければ、import キーワードを使う。

// A
import { mean, stddev } from "./stats.js";

// B
import * as stats from "./stats.js";

// C
import BitSet from './bitset.js'

as キーワードを使えばリネームもできる:

import { render as renderImage } from "./imageutils.js";

ES2020 からは、import() 関数を使ったダイナミックインポートもできる:

import("./stats.js").then(stats => {
  let average = stats.mean(data);
});

11. The JavaScript Standard Library

11.1 Sets and Maps

オブジェクトはマップとして使えるが、文字列に限定されていたり、元々あるプロパティ toString があることによる複雑性があったりする。そのため、ES6 では真の SetMap クラスが導入された。

Set は値の集合だが、配列と違って順序がなく、重複も許されない。

let s = new Set();
s.add(1)
s.delete(1)
s.has(1)
s.clear()
s.size

Map はキーバリューの集合である。配列のようだが、配列と違って、整数でなく任意の値をインデックスとして使える。

let m = new Map();
m.set("one", 1)
m.delete("one")
m.get("one")
m.has("one")
m.clear()
m.size

WeakMap は、キーバリューが GC されうるような Map の変種である。キャッシュなどに用いられる。WeakSet も値が GC されうるような Set の変種である。

11.2 Typed Arrays and Binary Data

ES6 で 型付き配列(Typed arrays) が導入された。型付き配列は、要素の型と配列長が限定された配列で、要素は 0 で初期化されている。

  • Int8Array: 符号付きバイト
  • Uint8Array: 符号なしバイト
  • Uint8ClampedArray: 符号なしバイト(-1以上は0に、256以上は255に丸められる)
  • Int16Array: 符号付き16ビット整数
  • Uint16Array: 符号なし16ビット整数
  • Int32Array: 符号付き32ビット整数
  • Uint32Array: 符号なし32ビット整数
  • BigInt64Array: 符号付き64ビット BigInt (ES2020)
  • BigUint64Array: 符号なし64ビット BigInt (ES2020)
  • Float32Array: 32ビット浮動小数点数
  • Float64Array: 64ビット浮動小数点数(通常の JavaScript 数値の型)

型付き配列の作り方はいくつかある。

// 要素数を引数にしてコンストラクタを呼ぶ。
let bytes = new Uint8Array(1024);

// 要素群を引数にして of ファクトリメソッドを呼ぶ。
let white = Uint8ClampedArray.of(255, 255, 255, 0);

// 要素の配列を引数にして from ファクトリメソッドを呼ぶ。
let ints = Uint32Array.from(white);

// ArrayBuffer を引数にしてコンストラクタを呼ぶ。
let buffer = new ArrayBuffer(1024*1024);
let asbytes = new Uint8Array(buffer[, offset[, length]]);

型付き配列は buffer プロパティを持ち、対応する ArrayBuffer を返す。

型付き配列は、ネイティブのエンディアンを使う。リトルエンディアンのシステムでは、最下位から最上位の順にバイトが並ぶ。ビッグエンディアンのシステムでは、最上位から最下位の順にバイトが並ぶ。

DataView クラスを使えば、特定のバイト順で値を読み書きできる。ZIP ファイルを展開したり、JPEG ファイルからメタデータを抽出したりするのに使う。

let view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
view.getInt32(0) // ビッグエンディアンで整数を取得する
view.getInt32(4, false) // ビッグエンディアンで整数を取得する
int = view.getUint32(8, true); // リトルエンディアンで整数を取得する
view.setUint32(8, int, false); // ビッグエンディアンで整数を書き込む

11.3 Pattern Matching with Regular Expressions

正規表現 (regular expression) はテキストのパターンを表現するオブジェクトである。

正規表現リテラルはスラッシュ文字で正規表現を囲む。末尾にフラグを付けられる。RegExp() コンストラクタを使って正規表現文字列を引数にしても、作成できる。

let pattern = /s$/;
let pattern = /s$/i;
let pattern = new RegExp("s$");
CharacterMatches
[...]カッコ内の任意の1文字
[^...]カッコ内以外のの任意の1文字
.開業を除く任意の1文字
\w任意の ASCII 単語文字 ([a-zA-Z0-9_])
\W任意の ASCII 単語以外の文字 ([^a-zA-Z0-9_])
\s任意の Unicode 空白文字
\S任意の Unicode 空白以外の文字
\d任意の ASCII 数字 ([0-9])
\D任意の ASCII 数字以外の文字 ([^0-9])
{n,m}直前アイテムのn回以上m回以下の繰り返し
{n,}直前アイテムのn回以上の繰り返し
{n}直前アイテムのn回の繰り返し
?直前アイテムの0回または1回の繰り返し ({0,1})
+直前アイテムの1回以上の繰り返し ({1,})
*直前アイテムの0回以上の繰り返し ({0,})
``
(...)グルーピングして1つのアイテムにする。後方参照にも利用する
(?:...)グルーピングして1つのアイテムにする。後方参照にはならない
\N後方参照
^文字列の最初、または m フラグを付けると行頭
$文字列の終わり、または m フラグを付けると行末
\b単語境界
\B単語境界でない位置
(?=p)先読み
(?!p)否定先読み
(?<=p)後読み (ES2018)
(?<!p)否定後読み (ES2018)

繰り返しは greedy にマッチする。つまり、可能な限りたくさんマッチする。逆に可能な限り少なくマッチさせたければ ? を付与する。

FlagDescription
g最初のマッチだけでなくすべてマッチする
i大文字小文字を区別しない
m複数行モード
s. が改行も含んでマッチする (ES2018)
uUnicode コードポイントでマッチする (ES2018)
ysticky
"JavaScript".search(/script/ui) // => 4
text.replace(/javascritp/gi, "JavaScript");
"7 plus 8 equals 15".match(/\d+/g) // => ["7", "8", "15"]
"Visit my blog at http://www.example.com/~david".match(/(?<protocol>\w+):\/\/(?<host>[\w.]+)\/(?<path>\S*)/)
// => ['http://www.example.com/~david', 'http', 'www.example.com', '~david', index: 17, input: 'Visit my blog at http://www.example.com/~david', groups: { protocol: 'http', host: 'www.example.com', path: '~david' }]

Note: “JavaScript Standard Library” という章に正規表現が扱われている理由はぴんと来ない。正規表現リテラルがある以上、文字列と同じ次元の扱いのようにも思える。

11.4 Dates and Times

let now = new Date(); // current time
let epoch = new Date(0); // 1970/01/01 00:00:00
let century = new Date(2100, 0, 1, 2, 3, 4, 5);
now.toString() // => 'Sun Nov 22 2020 01:36:12 GMT+0900 (Japan Standard Time)'
now.toLocaleString() // => '11/22/2020, 1:36:12 AM'

11.5 Error Class

class HTTPError extends Error {
  constructor(status, statusText, url) {
    super(`${status} ${statusText}: ${url}`);
    this.status = status;
    this.statueText = statusText;
    this.url = url;
  }
  get name() { return "HTTPError" }
}

11.6 JSON Serialization and Parsing

let o = { s: "", n: 0, a: [true, false, null] };
let s = JSON.stringify(o); // s == '{"s":"","n":0,"a":[true,false,null]}'
let copy = JSON.parse(s); // copy == o

11.7 The Internationalization API

Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format("123456") // => '¥123,456'
Intl.DateTimeFormat("ja-JP", { era: "short" }).format(now) // => '西暦2020年11月22日'
["page10", "page9"].sort(new Intl.Collator(undefined, { numeric: true }).compare) // => [ 'page9', 'page10' ]

11.8 The Console API

  • console.log(): コンソールに出力する。

  • console.debug(), console.info(), console.warn(), console.error(): ほぼ console.log() と同じ。

  • console.assert(): 第1引数が真なら、何もしない。第1引数が偽なら、console.error() で出力する。

  • %s: 引数を文字列に変換する。

  • %i, %d: 引数を整数に変換する。

  • %f: 引数を数字に変換する。

  • %o, %O: 引数をオブジェクトとしてプロパティを表示する。

  • %c: 引数は CSS スタイルの文字列として扱われる。

11.9 URL APIs

let url = new URL("https://example.com:8000/path/name?q=term#fragment");
url.href // => 'https://example.com:8000/path/name?q=term#fragment'
url.origin // => 'https://example.com:8000'
url.protocol // => 'https:'
url.host // => 'example.com:8000'
url.hostname // => 'example.com'
url.port // => '8000'
url.pathname // => '/path/name'
url.search //=> '?q=term'
url.hash // => '#fragment'

url.searchParams.append('foo', 'bar');
url.toString() // => 'https://example.com:8000/path/name?q=term&foo=bar#fragment'
  • encodeURI(): 引数の文字列を URL エンコードして返す。/, ?, # のような文字はエンコードしない。
  • decodeURI(): 引数の文字列を URL デコードして返す。/, ?, # のような文字はデコードしない。
  • encodeURIComponent(): 引数の文字列を URL エンコードして返す。/, ?, # のような文字もエンコードする。
  • decodeURIComponent(): 引数の文字列を URL デコードして返す。/, ?, # のような文字もデコードする。

11.10 Timers

setTimeout(fn, timeout) は、timeout 秒後に fn 関数を呼ぶ。setInterval(fn, interval) は、interval 秒間隔で fn 関数を呼ぶ。

12. Iterators and Generators

12.1 How Iterators Work

for/of ループやスプレッド演算子は、iterable オブジェクトに対して動く。iterable オブジェクトの Symbol.iterator プロパティは、iterator オブジェクトを返す。iterator オブジェクトに対して、next() メソッドを呼んで次の要素を取得していく。next() の返り値はオブジェクトで、done プロパティはループの終わりにのみ true になり、value プロパティは次の要素を返す。

let iterable = [99];
let iterator = iterable[Symbol.iterator]();
for (let result = iterator.next(); !result.done; result = iterator.next()) {
  console.log(result.value); // 99
}

12.2 Implementing Iterable Objects

itarable なクラスを作るためには、Symbol.iterator のメソッドを実装しなければならない。このメソッドは next() メソッドと value, done プロパティと実装しなければならない。

class Range {
  constructor(from, to) { this.from = from; this.to= to; }
  [Symbol.iterator]() {
    let next = Math.ceil(this.from);
    let last = this.to;
    return {
      next() { return (next <= last) ? { value: next++ } : { done: true }; }
      [Symbol.iterator]() { return this; } // make the iterator itself iterable.
    }
  }
}

12.3 Generators

ジェネレータ関数(generator function) は、通常の関数と同様だが function* キーワードを使う。ジェネレータ関数を呼ぶと、実際にはジェネレータ関数の本体を実行せず、ジェネレータオブジェクト(イテレータ)を返す。next() メソッドを呼ぶと、ジェネレータ関数の本体を実行していって、yield 文まで達すると止まって、yield 文の値が next() の返り値になる。

function *oneDigitPrimes() {
  yield 2;
  yield 3;
  yield 5;
  yield 7;
}
let primes = oneDigitPrimes();
primes.next().value // => 2
primes.next().value // => 3
primes.next().value // => 5
primes.next().value // => 7
primes.next().done // => true

12.4 Advanced Generator Features

  • ジェネレータ関数が return で返した値は、ループが完了したあと(done が true になったとき)の value の値になる。
  • next() 関数の引数の値が、ジェネレータ関数の yield の返り値になる。最初呼んだ next() の引数は捨てられる。
  • ジェネレータオブジェクトは next() 以外にも、return()throw() メソッドがある。return(value) を呼ぶと、ジェネレータ関数内で return value したのと同じになる。throw(error) はジェネレータ関数内で throw error したのと同じになる。

13. Asynchronous JavaScript

13.1 Asynchronous Programming with Callbacks

  • 一番シンプルな非同期処理は、setTimeout(), setInterval() 関数により一定時間後にあるコードを実行させることである。
  • クライアントサイド JavaScript プログラムではイベント駆動が遍在している。ユーザの入力などの特定イベントに対してコールバック関数を登録すると、ブラウザはそのイベントが発生したときにコールバック関数を呼ぶ。コールバック関数は イベントハンドラ(event handler) または イベントリスナ(event listener) といい、addEventListener() 関数で登録する。
  • 別の非同期処理として、ネットワークリクエストがある。XMLHttpRequest クラス(または最近では fetch() 関数)を使って HTTP リクエストを送って、非同期に処理する。
  • Node.js サーバサイド環境では、多くの API がコールバックとイベントを使った非同期処理になっている。ファイル内容の読み込みも、デフォルトでは非同期で、内容が読み込まれたときにコールバック関数が呼ばれる。

13.2 Promises

プロミス(promise) は、非同期処理の結果を表したオブジェクトである。プロミスはコールバックの扱い方を変えたものにすぎないが、コールバックベースの非同期プログラミングはコールバックの中にコールバックがあってインデントが深くなって読みづらくなる。プロミスはコールバックのネストを プロミスチェーン(promise chain) という形にして、より読みやすくなる。コールバックの別の問題はエラー処理が難しいことだが、プロミスはエラー処理をうまく扱う。

非同期処理をする関数はプロミスオブジェクトを返す。プロミスオブジェクトは then(callback), catch(callback), finally(callback) メソッドをメソッドチェーンさせて呼ぶことができて、非同期処理が解決したときに then()callback が呼ばれ、非同期処理がエラーになったときに catch()callback が呼ばれて、非同期処理が解決してもエラーになっても最後に finally()callback が呼ばれる。then() のコールバックの返り値もプロミスオブジェクトなら、それもメソッドチェーンで then() で処理を続けられる。

Promise.all(promises) 関数によって、複数のプロミスをまとめて非同期処理できる。複数のプロミスがすべて解決したときに then() が呼ばれる。

13.3 async and await

ES2017 では async, await という2つのキーワードが導入されて、プロミスベースの非同期コードが簡単に書けるようになった。

await キーワードはプロミスオブジェクトを受け取って、非同期処理が解決するか拒否されるまで待って、その結果を返す。ただし await キーワードは、async キーワードで宣言された関数内でのみ使える。async な関数は、返り値が自動でプロミスになる。

async function getHgihScore() {
  let response = await fetch("/api/user/profile");
  let profile = await response.json();
  return profile.highScore;
}

// In another async function:
displayHighScore(await getHighScore());

// Or:
getHighScore().then(displayHgihScore).catch(console.error);

複数のプロミスを並行して待つ場合、Promise.all を使う:

let [value1, value2] = await Promise.all([getJSON(url1), getJSON(url2)]);

async 関数は以下のように変換できる:

async function f(x) { ... }

function f(x) {
  return new Promise(function(resolve, rejct) {
    try {
      resolve((function(x) { ... }(x)));
    } catch (x) {
      reject(e);
    }
  });
}

13.4 Asynchronous Iteration

ES2018 で、非同期イテレーションをサポートした。

for/async ループ:

const fs = require("fs");

async function parseFile(filename) {
  let streeam = fs.createReadStream(filename, { encoding: "utf-8" });
  for await (let chunk of stream)
    parseChunk(chunk);
}

非同期ジェネレータ:

async function* clock(interval, max=Infinity) {
  for (let count = 1; count <= max; count++) {
    await new Promise(resolve => setTimeout(resolve, interval));
    yield count;
  }
}

async function test() {
  for await (let tick of clock(300, 100)) console.log(tick);
}

14. Metaprogramming

14.1 Property Attributes

JavaScript オブジェクトのプロパティには3つの属性がある:

  • writable: プロパティの値を変更可能か。

  • enumerable: プロパティが for/in ループや Object.keys() メソッドで列挙可能か。

  • configurable: プロパティを削除できたりその属性を変更できたりするか。

  • Object.getOwnPropertyDescriptor(obj, propertyName) は、指定したプロパティのデスクリプタ(属性情報)を取得する。

  • Object.defineProperty(obj, propertyName, descriptor) は、指定したプロパティを定義する。

14.2 Object Extensibility

  • Object.preventExtensions(obj) は、オブジェクトを拡張不可能にする。拡張不可能にすると、新しいプロパティが追加できなくなる。
  • Object.seal(obj) は、オブジェクトを拡張不可能にして、全プロパティを nonconfigurable にする。
  • Object.freeze(obj) は、オブジェクトを拡張不可能にして、全プロパティを nonconfigurable/nonwritable にする。

14.3 The prototype Attribute

  • Object.getPrototypeOf(obj) は、オブジェクトのプロトタイプを返す。
  • Object.setPrototypeOf(obj, prototype) は、オブジェクトのプロトタイプをセットする。

14.4 Well-Known Symbols

  • Symbol.iterator
  • Symbol.asyncIterator
  • Symbol.hasInstance
  • Symbol.toStringTag
  • Symbol.species
  • Symbol.isConcatSpreadable
  • Symbol.toPriimtive
  • Symbol.unscopables

14.5 Template Tags

14.6 The Reflect API

14.7 Proxy Objects

15. JavaScript in Web Browsers

15.1 Web Programming Basics

HTML 内の <script> タグによって、JavaScript を含むことができる。defer 属性を付けると、ドキュメントがロードされパースされて準備完了になるまで実行されない。async 属性を付けると、可能な限りスクリプトを実行るがドキュメントのパースはブロックしないようにする。

<script defer src="script.js"></script>

15.2 Events

15.2.1 Event Categories

  • デバイス依存の入力イベント
    • mousedown, mosemove, mouseup, touchstart, touchmove, touchend, keydown, keyup
  • デバイス非依存の入力イベント
    • click, input, pointerdown, pointermove, ppointerup
  • UI イベント
    • focus, change, submit
  • 状態変化イベント
    • load, online, offline, popstate
  • API 特有のイベント
    • waiting, playing, seeking, volumechange, success, error

15.2.2 Registering Event Handler

イベントハンドラの登録方法は2つある。1つ目に初期の Web からあるやり方は、イベントターゲットのイベントハンドラプロパティをセットすること。2つ目のやり方は、イベントターゲットの addEventListener() メソッドにハンドラを渡すこと。

let b = document.querySelector('#mybutton');
b.onclick = function() { console.log('Thanks for clicking me!'); };

HTML 要素の属性として、イベントハンドラを直接セットすることもできる:

<button onclick="console.log('THank you');">Please Click</button>

addEventListener() でイベントハンドラを登録した場合、クリーンアップとして reomveEventListener() でイベントハンドラを登録解除する:

let b = document.querySelector('#mybutton');
function handler() {
  console.log('Thanks again!');
}

b.addEventListener('click', handler);
// ...
b.removeEventListener('click', handler);

15.2.3 Event Handler Invocation

イベントが発生したとき、登録したイベントハンドラが Event オブジェクトを引数にして呼ばれる。Event オブジェクトは次のようなプロパティを持つ:

  • type: イベントのタイプ。
  • target: イベントが発生したオブジェクト。イベントターゲット。
  • currentTarget: イベントがプロパゲートしたとき、この値は現在のイベントハンドラが登録されたオブジェクトになる。
  • timeStamp: イベントが発生した時刻。
  • isTrusted: ブラウザが引き起こしたイベントなら true、JavaScript コードが引き起こしたイベントなら false

15.2.4 Event Propagation

15.2.5 Event Cancelation

イベントハンドラで、Event の preventDefault() メソッドを呼ぶことで、ブラウザのデフォルト動作を抑止することができる。

15.2.6 Dispatching Custom Events

CustomEvent クラスで、独自のイベントオブジェクトを作ることがえきる。dispatchEvent() メソッドで、イベントを引き起こすことができる。

document.dispatchEvent(new CustomEvent('busy', { detail: true }));
document.addEventListener('busy', (e) => {
  if (e.detail) showSpinner();
  else hideSpinner();
});

15.3 Scripting Documents

15.4 Scripting CSS

15.5 Document Geometry and Scrolling

15.6 Web Components

15.7 SVG: Scalable Vector Graphics

15.8 Graphics in a

15.9 Audio APIs

15.10 Location, Navigation, and History

15.11 Networking

15.11.1 fetch()

fetch(url[, options]) 関数は、指定した URL をリクエストして、レスポンスを Promise で返す。

fetch('/api/users', {
  method: 'POST',
  headers: new Headers({'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}`}),
  body: JSON.stringify(user)
})
  .then(response => response.json())
  .then(user => displayUser(user));

15.11.2 Server-Sent Events

サーバクライアント間でコネクションを開いたままにしておいて、サーバ側で何かイベントがあればクライアントに通知するという手法がある。クライアントサイド JavaScript でそれをサポートするため EventSource API がある。URL を引数にして EventSource() コンストラクタを呼ぶと、クライアントはその URL にリクエストを送ってコネクションを開いたままにしておいて、サーバがデータをコネクションに書き込んだとき、EventSource オブジェクトはそのデータをイベントに変換してイベントリスナに処理させる。

let chat = new EventSource('/chat');
chat.addEventListener('chat', event => {
  let div = document.createElement('div');
  div.append(event.data);
  input.before(div);
  input.scrollIntoView();
});

15.11.3 WebSockets

wss:// から始まる URL を引数に指定して WebSocket() コンストラクタを呼ぶと、WebSocket オブジェクトが作成される。

クライアントがサーバに WebSocket を通してメッセージを送るには、WebSocket.prototype.send(data) メソッドを使ってメッセージを送信する。

クライアントがサーバから WebSocket を通してメッセージを受け取るには、onmessage プロパティをセットするか addEventListener('message', callback) メソッドを呼ぶ。

15.12 Storage

15.12.1 localStorage and sessionStorage

localStorage, sessionStorage は普通の JavaScript オブジェクトと同様に働くが、プロパティの値は文字列でなければならず、プロパティは永続的に保存される。localStorage, sessionStorage は同じドキュメントオリジンでは同じ内容を共有する。localStorage は明示的に消されるまでずっと保存されるが、sessionStorage はブラウザ(のウィンドウやタブ)が閉じられると消える。

15.12.2 Cookies

document.cookie で Cookie を文字列で取得・変更できる。

15.12.3 IndexedDB

IndexedDB はオブジェクトデータベースである。

15.13 Worker Threads and Messaging

Worker クラスによってマルチスレッドを実現できる。

16. Server-Side JavaScript with Node

16.1 Node Programming Basics

console.log() 関数は、標準出力に出力する。

process.argv は文字列の配列で、第1要素は Node 実行可能ファイルのパス、第2要素は Node が実行している JavaScript コードのファイルパス、残りの要素はコマンドライン引数になる。

process.env はオブジェクトで、プロパティ名が環境変数の名前、プロパティの値が環境変数の値になる。

process.exit() 関数は、プログラムを終了する。

16.2 Node Is Asynchronous by Default

16.3 Buffers

16.4 Events and EventEmitter

16.5 Streams

16.6 Process, CPU, and Operating System Details

16.7 Working with Files

16.8 HTTP Clients and Servers

16.9 Non-HTTP Network Servers and Clients

16.9 Working with Child Processes

16.10 Worker Threads

17. JavaScript Tools and Extensions

17.1 Linting with ESLint

今日 JavaScript で最も使われている linter は ESLint である。

17.2 JavaScript Formatting with Prettier

Prettier は、JavaScript のコードをパースして自動で整形する。

17.3 Unit Testing with Jst

1つのパッケージの中に必要なものがすべて含まれている有名なテストフレームワークとして、Jest がある。

17.4 Pacakge Management with npm

$ npm install express
$ npm install --save-dev prettier
$ npm install -g eslint jest
$ npm audit --fix

17.5 Code Bundling

webpack, Rollup, Parcel

17.6 Transpilation with Babel

[Babel])(https://babeljs.io)

17.7 JSX: Markup Expressions in JavaScript

17.8 Type Checking with Flow

Flow

Created at