【学習ログ】MDN JavaScript チュートリアル (17 回目)
はじめに
この記事は、JavaScript チュートリアルの学習ログです。
学習したもの
MDN にある JavaScript のチュートリアルを学習しました。
MDN JavaScript コーナー
┃
┗ チュートリアル
┣ 完全な初心者向け
┃
┣ 中級者向け
┃ ┣ クライアントサイド Web API
┃ ┣ JavaScript 「再」入門(本日の学習箇所)
┃ ┣ JavaScript のデータ構造
┃ ┣ 等価比較と同一性
┃ ┗ クロージャ
┃
┗ 上級者向け
┣ 継承とプロトタイプチェーン
┣ Strict モード
┣ JavaScript 型付き配列
┣ メモリ管理
┗ 同時実行モデルとイベントループ
概要
チュートリアルで習った事の概要は下記の通りです。
JavaScript 「再」入門
- JavaScript の基本である型について学習する。
ノート・メモ
数値
JavaScript における数値は、仕様によると「倍精度 64 ビットフォーマット IEEE 754 値 (double-precision 64-bit format IEEE 754 values)」です。
JavaScript には整数に当たるものがない。(?)
以下のようなことに注意してください:
javascript 0.1 + 0.2 == 0.30000000000000004;
整数値は 32 ビット int 型として扱われます。また一部の実装では、32 ビット整数値ではなく Number で有効な命令の実行を求められるまでの間、32 ビット整数値として保存します。これはビット演算を行う際に重要なことです。
- 10 進数の計算結果が出るとは限らない?微妙な誤差が出る?ということなのかな。
足し算、引き算、モジュロ (剰余) など、標準的な 算術演算 がサポートされています。
より高度な数学関数や定数を扱う Math という組み込みオブジェクトもあります。
組み込みの parseInt() 関数を使うことで、文字列を整数に変換することができます。この関数は省略可能な第 2 引数として変換の基数を取りますが、あなたは常にこの引数を与えるべきです。
古いブラウザーでは "0" から始まる文字列を 8 進数 (基数 8) とみなしますが、これは 2013 年以降のブラウザーにはあてはまりません。 文字列の形式を理解していなければ、古いブラウザーであなたは驚くような結果を得ることでしょう
浮動小数点数への変換を行う、組み込みの parseFloat() 関数があります。こちらは parseInt() と異なり、基数は常に 10 が用いられます。
単項演算子 + を使って値を数値に変換することもできます。
もし文字列が数でない場合、 NaN (非数、"Not a Number" の略) と呼ばれる特別な値が返ります。
NaN を入力してどの算術演算に与えても、その結果は同様に NaN になります。
組み込みの isNaN() 関数を使えば、NaN であるかを検査することができます。
JavaScript はまた、特別な値 Infinity と -Infinity を持っています。
javascript 1 / 0; // Infinity -1 / 0; // -Infinity
- 組み込みの isFinite() 関数を使えば、Infinity、-Infinity、NaN であるかを検査することができます。
文字列
JavaScript における文字列は、Unicode 文字 の連なったもの (sequences) です。より正確に言えば UTF-16 コード単位の連なったものであり、つまりそれぞれのコード単位は 16 ビットの整数で表されます。また、それぞれの Unicode 文字は 1 または 2 個のコード単位で表します。
文字列も オブジェクト です。また、文字列の操作や文字列に関する情報へのアクセスを可能にする メソッド も持っています。
その他の型
JavaScript は null と undefined を区別します。null は、意図的に 「値がない」 ということを指し示す値です (また、null キーワードによってのみアクセスできます)。対して undefined とは、初期化されていない値 ― すなわち、「まだ値が代入されていない」 ということを指し示す undefined 型の値です。
JavaScript では値を代入しないで変数を宣言することができるのです。そのようにした場合、その変数の型は undefined です。実際は、undefined は定数です。
真偽値
JavaScript は true と false (これらはともにキーワードです) を取りうる値とする真偽値型を持っています。どんな値でも以下のルールに基づいて真偽値に変換できます。
false、0、空文字列 ("")、NaN、null、および undefined は、すべて false になる
その他の値はすべて true になる
Boolean() 関数を使うことで、明示的にこの変換を行うことができます。しかしながら、これはほとんど必要ありません。なぜなら JavaScript は、if 文 (下記参照) の中といった真偽値が期待されるときに、無言でこの変換を行うからです。
&& (論理 AND) や || (論理 OR)、! (論理 NOT) などの真偽値演算がサポートされています。
変数
JavaScript における新しい変数は let、const、var の 3 つのキーワードのいずれかを使用して宣言します。
let は、ブロックレベルの変数を宣言できます。宣言した変数は、変数を包含する関数ブロックから使用できます。
const は、変更を意図しない変数を宣言できます。宣言した変数は、変数を宣言した関数ブロックから使用できます。
var は、もっとも一般的な宣言キーワードです。こちらは、他の 2 つのキーワードのような制約がありません。 これは、伝統的に JavaScript で変数を宣言する唯一の方法であったためです。var キーワードで宣言した変数は、変数を宣言した関数ブロックから使用できます。
もし値を代入しないで変数を宣言すると、その型は undefined になります。
Java など他の言語との重要な相違点は、JavaScript ではブロックがスコープを持たず、関数のみがスコープを持つことです。 よって、変数が複合文内 (例えば if 制御構造の内部) で var を用いて定義された場合でも、その変数は関数全体でアクセス可能です。ただし ECMAScript 2015 より、let および const 宣言でブロックスコープの変数を作成できます。
-
JavaScript の算術演算子は、+、-、*、/、そして剰余演算子の % (モジュロとは異なります) です。値は = を使って代入されます。また += や -= のような複合代入文もあります。これらは x = x 演算子 y と展開できるものです。
++ や -- を使ってインクリメントやデクリメントできます。これらは前置あるいは後置演算子として使うことができます。
+
演算子 は文字列の結合もします。文字列を数字 (や他の値) に足すと、すべてのものはまず最初に文字列に変換されます。空文字列を足すのは、何かを文字列に変換する便利な方法です。JavaScript における 比較 は、< や >、<=、>= を使ってすることができます。これらは文字列と数値のどちらでも機能します。
等価性はちょっと明快ではありません。二重イコール演算子は、異なる型を与えると型強制 (type coercion) を行います。
型強制を防いで厳密な比較を行うようにする場合は、三重イコール演算子を使います。
!= と !== 演算子もあります。
JavaScript は ビット演算子 も持っています。
制御構造
条件文は if と else でサポートされています。必要ならこれらを連鎖させることもできます。
JavaScript は while ループと do-while ループを持っています。1 つ目は普通のループ処理に適しており、2 つ目はループの本体が少なくとも 1 回は実行されるようにしたいときのループです。
JavaScript の for ループは C や Java のそれと同じです。これはループの制御情報を 1 行で与えることができます。
- JavaScript にはこの他に、特徴的な for ループが 2 つあります。ひとつは for...of です。
javascript for (let value of array) { // 値に関する処理 }
- もうひとつは for...in です。
javascript for (let property in object) { // オブジェクトのプロパティに関する処理 }
&& と || 演算子は、1 つ目のオペランドによって 2 つ目のオペランドを評価するか否かが決まる短絡論理 (short-circuit logic) を用いています。
これはあるオブジェクトの属性にアクセスする前に、それが null オブジェクトかをチェックするのに便利です。
あるいは値の格納にも便利です (falsy な値は無効であるとき)。
JavaScript はワンライン条件文のための三項演算子を持っています。
switch 文はある数値や文字列を元にした複数分岐に使われます。
break 文で処理から抜ける事が出来る。 break 文で処理を抜けないときは、以降の処理も実施される。(fall through)
default 節は省略できます。必要ならば、switch 部と case 部のどちらにも式を置くことができます。比較はこれら 2 つの間で === 演算子を使って行われます。
オブジェクト
JavaScript のオブジェクトは、名前と値のペアの単純なコレクションであると考えることができます。
JavaScript において (コアデータ型を除いた) すべてのものはオブジェクトなので、どんな JavaScript プログラムも自然と非常に多くのハッシュテーブルのルックアップ (検索) を伴います。良いことにそれがとても速いのです!
「名前」部は JavaScript における文字列であるのに対し、値は JavaScript のどんな値でも ― さらなるオブジェクトでも ― 構いません。この仕様が任意の複雑なデータ構造を作ることを可能にしています。
オブジェクトリテラル構文
- 空のオブジェクトを作成する時、下記のように記載することも出来る。
javascript var obj = {};
- EcmaScript 第 5 版より、予約語をオブジェクトのプロパティ名として"そのまま"使用できます。つまりオブジェクトリテラルの定義時に引用符で"括る"必要ありません。
Array (配列)
JavaScript における配列は、実はオブジェクトの特殊型です。普通のオブジェクトとほとんど同じように働きます (数字のプロパティは当然 [] の構文でのみアクセスできます) が、しかし配列は 'length' という魔法のプロパティを持っています。これは常に配列の一番大きな添字より 1 大きい値を取ります。
存在しない配列の添字を要求すると、undefined が得られます。
ECMAScript では配列のような iterable オブジェクト用に、より正確な for...of ループが導入されました。
for...in ループを使用して配列を繰り返すすることもできます。ただし、もし誰かが Array.prototype に新しいプロパティを追加していたら、それらもこのループで繰り返されてしまうので注意してください。よって、この方法は配列に対しては "推奨しません"。
配列を繰り返すもうひとつの方法が、 ECMAScript 5 で追加された forEach() です。
配列に要素を追加したいなら、このようにするのがもっともシンプルです。
javascript var a = [dog, cat]; a.push(bird);
上記の push() 以外にも、配列には多くのメソッドがついてきます。
関数
オブジェクトとともに、関数は JavaScript を理解するうえで核となる構成要素です。
JavaScript の関数は 0 以上の名前のついた引数を取ることができます。関数の本体は好きなだけたくさんの文を含ませることができ、またその関数内で局所的な変数を宣言することができます。
return 文は好きなときに関数を終了し値を返すために使うことができます。もし return 文が使われなかったら (あるいは値をつけない空の return が使われたら)、JavaScript は undefined を返します。
実のところ、名前のついた引数はガイドラインのようなもの以外の何物でもありません。あなたは期待された引数を渡さずに関数を呼ぶことができます。その場合引数には undefined がセットされます。
あなたはまた、関数が期待しているより多くの引数を渡すこともできます。関数はその本体の中で arguments と呼ばれる追加の変数を利用することができます。これはその関数へ渡されたすべての値を保持する配列のようなオブジェクトです。
Rest パラメーター記法
- この方法では、コードを最小限にしながら任意の数の引数を関数に渡すことができます。Rest パラメーター演算子は ...変数の形式で、関数宣言内で使用します。また、これは変数内に、関数を呼び出したときに未取得の引数すべてのリストが含まれます。
JavaScript は関数オブジェクトの apply() メソッドを使うことで、引数に任意の配列をつけて呼ぶことができます。同じ結果を関数呼び出しのスプレッド演算子でも達成できます。
JavaScript では無名関数 (anonymous functions) を作ることができます。
これは非常に強力です。あなたは普通は式を置くところならどこにでも完全な関数定義を置くことができるのです。
カスタムオブジェクト
古典的なオブジェクト指向プログラミングにおいて、オブジェクトとはデータとそのデータを操作するメソッドの集まりです。JavaScript は、C++ や Java に見られる class 文を持たない、プロトタイプベースの言語です。(これは、class 文を持つ言語に慣れたプログラマーを混乱させることでしょう) 代わりに、JavaScript は関数をクラスとして用います。
this
関数内で使われると、this は現在のオブジェクトを参照します。実際に意味するところは関数の呼ばれ方によります。
オブジェクト上で ドットの記法や角カッコの記法 を使って呼び出すと、そのオブジェクトが this になります。ドット記法を使わずに呼び出すと、this はグローバルオブジェクトを参照します。
new
new は this と強い関連があります。これは新しい空のオブジェクトを作り、this にその新しいオブジェクトをセットして、後に続く関数を呼びます。this に指定された関数は値を返しているのではなく、単に this オブジェクトを変更していることに注意してください。this オブジェクトを呼び出し元に返しているのは new です。
new によって呼ばれるよう設計された関数はコンストラクター関数と呼ばれます。new によって呼ばれるということがわかるよう、先頭を大文字にすることがよく行われています。
コンストラクタ名.prototype
apply() には姉妹関数 call があります。this を設定できる点は同じですが、引数に配列ではなく展開された値のリストをとります。
内部関数
JavaScript での関数宣言は他の関数内でも行えます。大事なことは内部関数内で親関数スコープの変数にアクセスできることです。
内部関数は保守しやすいコードを書くときに多大な利便性をもたらします。ある関数が他の部分のコードでは役立たない関数を 1 つか 2 つ使っているなら、これらのユーティリティ関数を他から呼び出される関数の入れ子にすることができます。内部関数はグローバルスコープでなくなるので、いいことです。
内部関数はグローバル変数を使うという誘惑に対する対抗措置です。複雑なコードを書くとき、複数の関数間で値を共有するためにグローバル変数を使いたくなります。しかし、これでは保守がしづらくなります。内部関数は親関数の変数を共有できるので、グローバルな名前空間を汚染せずに複数の関数をまとめる (いわば 'ローカルなグローバル') ことができます。この仕組みは注意して使用する必要がありますが、便利です。
-
ある関数の内部で定義された関数は、外側の関数が持つ変数にアクセスすることができます。内部関数で起きている事との唯一の違いは外側の関数が値を返していることであり、それゆえ一般的な考えではローカル変数は存在しなくなると考えられます。しかし、ここではローカル変数が残り続けます。
JavaScript で関数を実行するときは必ず、その関数内で作成されたローカル変数を保持する 'scope' オブジェクトが作成されます。それは関数の引数として渡された変数とともに初期化されます。これはすべてのグローバル変数やグローバル関数が存在している global オブジェクトに似ていますが、2 つの重要な違いがあります。
ひとつは、関数を実行し始めるたびに新たな scope オブジェクトが生成されること
もうひとつは、global オブジェクト (this としてアクセスでき、またブラウザーでは window として存在する) とは異なり、これらの scope オブジェクトに JavaScript のコードから直接アクセスできないことです。例えば、現存する scope オブジェクトのプロパティをたどる仕組みはありません。
JavaScript のオブジェクトシステムが利用するプロトタイプチェーンと同様に、scope オブジェクトはスコープチェーンと呼ばれるチェーンを構成します。
クロージャは、関数と関数が生成した scope オブジェクトを組み合わせたものです。クロージャは状態を保存します。従って、オブジェクトの代わりとしてよく使用されます。
分かった事
- 数値・文字列・その他の型・真偽値・変数・演算子・制御構文・オブジェクト・配列・関数あたりまでは、おおよそ分かりました。
分からなかった事
Rest パラメーター記法・カスタムオブジェクト・内部関数・クロージャあたりが、理解度に怪しい部分があります。
&& と || 演算子のところの「null オブジェクトかをチェック」、「あるいは値の格納にも便利です (falsy な値は無効であるとき)」と記載されている箇所の例文がよく分からなかったです。
無名関数の再帰呼び出しが分からなかったです。
まとめ
「再」入門とあるだけあり、今までにチュートリアルで学習してきた部分の詳細な説明が あって、理解できた部分がある一方で余計に分からなくなった部分もあり、複雑な心境です。
理解できる部分が増えてきているので、以前よりも成長していると思って、この調子で頑張っていきたいと思います。
最後までご覧頂きありがとうございました。