"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つの例外がある。
return
,throw
,yield
,break
,continue
文の直後に改行があった場合、JavaScript は必ず改行をセミコロンとして扱う。++
,--
を後置演算子として使う場合、これらの演算子は必ずオペランドと同じ行に書かなければならない。- アロー関数の
=>
は必ずパラメータリストと同じ行に書かなければならない。
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
自身と比較しても)等しくならない。NaN
が NaN
かどうかをチェックするためには 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ビット値でなく、実際の文字ごとに処理することができる。
文字列リテラルは、シングルクォートかダブルクォートかバッククォートでテキストを囲む。文字列リテラルの中のバックスラッシュ文字は特別で、エスケープシーケンスが書ける。
シーケンス | 文字 |
---|---|
\0 | NUL 文字 |
\b | バックスペース |
\t | 水平タブ |
\n | 改行 |
\v | 垂直タブ |
\f | フォームフィード |
\r | 復帰 |
\" | ダブルクォート |
\' | シングルクォート |
\\ | バックスラッシュ |
\xNN | 16進数の Unicode 文字 |
\uNNNN | 16進数の 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"
を返す。
null
と undefined
は ==
で等しく扱われる(===
では異なる)。どちらも falsy な値。どちらもプロパティやメソッドを一切持たない。
David Flanagan 曰く、undefined
はシステムレベルの予期しないエラーとして、値がないことを示す。null
はプログラムレベルの通常のものとして、値がないことを示す。可能なら undefined
も null
も利用せず、利用する場合は null
の方を使う。
Note: null
と undefined
があるのはちょっと面倒。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 は必要な型に応じて、値を自動で変換する。
Value | to String | to Number | to Boolean |
---|---|---|---|
undefined | "undefined" | NaN | false |
null | "null" | 0 | false |
true | "true" | 1 | |
false | "false" | 0 | |
"" | 0 | false | |
"1.2" | 1.2 | true | |
"one" | NaN | true | |
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) | "" | 0 | true |
[9] (one numeric element) | "9" | 9 | true |
['a'] (any other array) | (use join()) | NaN | true |
function(){} (any function) | "function(){...}" | NaN | true |
数字としてパースできる文字列は、その数字に変換できる。先頭と末尾のスペースは許されるが、それ以外の文字が含まれていると NaN
に変換される。
厳格な等価演算子 ===
は、型が同じでなければ等しくならない。より柔軟な等価演算子 ==
もあり、こちらは自動で型変換されて比較する:
null == undefined // => true
"0" == 0 // => true
0 == false // => true
"0" == false // true
明示的な型変換は、Boolean()
, Number()
, String()
関数を呼べばよい。また null
と undefined
以外の値は toString()
メソッドがあり、通常 String()
関数と同じ結果になる。
Number クラスの toString()
メソッドはオプショナル引数を受け付け、変換するときの基数を指定できる。Number クラスの toFixed()
メソッドは引数に指定した桁数の小数点以下を文字列に変換する。toExponential()
メソッドも同様だが、指数記法になる。toPrecision()
メソッドは引数に指定した有効数字桁の文字列に変換する。有効数字桁が整数部分を表示するのに不十分な場合、指数記法を使う。
Number()
関数で文字列を数字に変換するとき、基数10で整数または浮動小数としてパースして、無駄な文字列は許されない。グローバル関数 parseInt()
は整数のみをパースして、parseFloat()
は整数と浮動小数をパースする。文字列が 0x
か 0X
で始まっていたら 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
キーワードが唯一の変数宣言の方法だった。var
は let
に似ているが、いくつかの点で異なる:
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 ではオプショナルチェーニングをサポートする。これは左辺が null
や undefined
のとき、右辺のプロパティアクセスをやめて undefined
を返す:
expression ?. identifier
expression ?.[ expression ]
let a = { b: null };
a.b?.c.d // => undefined
上の例で (a.b?.c).d
は undefined.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
Precedence | Operator | Operation | A | N | Types |
---|---|---|---|---|---|
1 | ++ | Pre/post increment | R | 1 | lval -> num |
1 | -- | Pre/post decrement | R | 1 | lval -> num |
1 | - | 負数 | R | 1 | num -> num |
1 | + | 数字への変換 | R | 1 | any -> num |
1 | ~ | ビット反転 | R | 1 | int -> int |
1 | ! | 真理値の反転 | R | 1 | bool -> bool |
1 | delete | プロパティの削除 | R | 1 | lval -> bool |
1 | typeof | 型名 | R | 1 | any -> str |
1 | void | undefined を返す | R | 1 | any -> undef |
2 | ** | 累乗 | R | 2 | num, num -> num |
3 | * , / , % | 乗算・除算・剰余 | L | 2 | num, num -> num |
4 | + , - | 加算・減算 | L | 2 | num, num -> num |
4 | + | 文字列結合 | L | 2 | str, str -> num |
5 | << | 左シフト | L | 2 | int, int -> num |
5 | >> | 符号付き右シフト | L | 2 | int, int -> num |
5 | >>> | ゼロ埋め右シフト | L | 2 | int, int -> num |
6 | < , <= , > , >= | 数字の比較 | L | 2 | num, num -> bool |
6 | < , <= , > , >= | 文字列の比較 | L | 2 | str, str -> bool |
6 | instanceof | オブジェクトクラスのテスト | L | 2 | obj, func -> bool |
6 | in | プロパティ存在のテスト | L | 2 | any, obj -> bool |
7 | == | 非厳格な等価 | L | 2 | any, any -> bool |
7 | != | 非厳格な非等価 | L | 2 | any, any -> bool |
7 | === | 厳格な等価 | L | 2 | any, any -> bool |
7 | !== | 厳格な非等価 | L | 2 | any, any -> bool |
8 | & | ビット AND | L | 2 | int, int -> int |
9 | ^ | ビット XOR | L | 2 | int, int -> int |
10 | ` | ` | ビット OR | L | 2 |
11 | && | 論理 AND | L | 2 | any, any -> any |
12 | ` | ` | 論理 OR | L | |
13 | ?? | 1つ目の定義されたオペランド | L | 2 | any, any -> any |
14 | ?: | 条件式 | R | 2 | any, any -> any |
15 | = | 変数・プロパティへの代入 | R | 2 | lval, any -> any |
15 | **= , *= , /= , %= , += , -= , &= , ^= , ` | =, «=, »=, »>=` | 演算と代入 | R | 2 |
16 | , | 右辺を返す | L | 2 | any, 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
文は、許されない。- すべての変数は、宣言しなければならない。
- メソッドでなく関数として呼び出したときには、
tihs
はundefined
になる。 - 書き込み可能でないプロパティへの代入や、拡張可能でないオブジェクトへのプロパティ追加は、TypeError になる。
eval()
に渡すコードは、変数の宣言や関数の定義ができない。- 関数の Arguments オブジェクトは、関数に渡された値の静的なコピーになる。
delete
式に非修飾の識別子(変数、関数、関数パラメータなど)を渡すと、SyntaxError になる。- 設定可能でないプロパティを削除しようとすると、TypeError になる。
- 同名のプロパティを複数回定義するようなオブジェクトリテラルは、SyntaxError になる。
- 同名のパラメータ名を複数回定義するような関数宣言は、SyntaxError になる。
- 8進数リテラルは、許されない。
eval
やarguments
はキーワードのように扱われ、値を変更することは許されない。argguments.caller
とarguments.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()
メソッドは、オブジェクトが引数のプロパティ名を自身で持ち、かつ、enumerable が true
であるかどうかを返す。
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()
などのメソッドのプロパティ順を定義している。
- 非負整数が名前の文字列プロパティ。数値の昇順で並ぶ。
- ほかの文字列プロパティ。オブジェクトに追加・定義された順序で並ぶ。
- 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 の値を表現できるわけではない。オブジェクト、配列、文字列、有限数、true
、false
、null
はサポートしている。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 では import
と export
キーワードが追加されて、モジュールシステムをサポートするようになった。
定数・変数・関数・クラスをエクスポートしたければ、宣言の前に 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 では真の Set
と Map
クラスが導入された。
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$");
Character | Matches |
---|---|
[...] | カッコ内の任意の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 にマッチする。つまり、可能な限りたくさんマッチする。逆に可能な限り少なくマッチさせたければ ?
を付与する。
Flag | Description |
---|---|
g | 最初のマッチだけでなくすべてマッチする |
i | 大文字小文字を区別しない |
m | 複数行モード |
s | . が改行も含んでマッチする (ES2018) |
u | Unicode コードポイントでマッチする (ES2018) |
y | sticky |
"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
17.6 Transpilation with Babel
[Babel])(https://babeljs.io)