menu

JavaエンジニアのためのJavaScript入門(ES5)【前編】

公開日:2019年02月06日 カテゴリー:Java, JavaScript, 開発

【JavaScriptを体系的に学習したい】

そんな声が新入社員からあがりました。
JavaScriptについて体系的にまとまっている記事は意外に少なく、
この際なので弊社エンジニアに記事にまとめてもらいました。

ボリュームたっぷりになりましたので、前後編になります。
まずは前編からどうぞ。

はじめに

これはJavaの既存の知識ベースを元にJavaScriptを体系的に学習する為の記事です。

※この記事では、ES5までのJavaScriptについて書いています。

それでは「Javaとの違い、JavaScriptではこうなる」という形でまとめていきましょう。

本記事のECMAScriptのバージョンについて

ECMAScriptはEcma internationalが仕様を策定するJavaScriptの標準です。
このECMAScriptに則ることでその仕様に準拠する環境での動作が保証されます。

※繰り返しますが、今回は「ES5までのJavaScriptについて」まとめています。

ECMAScript5のstrict mode

コードの先頭行に以下のディレクティブを書くと、strict modeとなります。
サポートされていない環境では無意味な文として無視されます。

strict modeによって、JavaScriptの言語仕様の落とし穴を防ぐことができるため、書くことをお勧めします。

strict modeの代表的な制約

  • 暗黙のグローバル変数の禁止
  • 関数内でthis参照がグローバルオブジェクトを参照しない
  • NaN, Infinity, undefinedをRead Onlyに
  • 同名のプロパティ名を禁止
  • arguments.calleeアクセスの禁止
  • Functionオブジェクトのcallerプロパティのアクセス禁止

変数と型

JavaScriptは動的型付けを行う言語です。
変数は型を持たず、格納される値を指し示すことを役割とします。

変数

JavaScriptにおける変数の特徴は以下になります。

  • 変数は値に名前をつけるものです。
  • 変数には型が存在しませんが、値には型が存在します。
  • 変数は、下から上ではなく、内から外へ探索されます。
  • 変数は宣言時にはundefined値を保持します。

JavaScriptの型

JavaScriptの型は以下の特徴を持ちます。

  • 値のみが型を持ち、組み込みの演算子などの挙動を決めます。
  • 評価の際には相互に暗黙の型変換を行うことがあります
  • 新たにユーザ定義型を追加することはできません。

JavaScriptの型一覧

JavaScriptに存在する型は以下の6つです。

  • 文字列型
  • 数値型
  • 真偽値型
  • object型
  • null型
  • undefined型

プリミティブ型とオブジェクト型

JSでは、値はプリミティブまたはオブジェクトです。
そしてオブジェクトは全てobject型です。
そしてobject型以外をプリミティブ型と呼びます。

プリミティブ型について

プリミティブ型であるとは、不変であることを意味します。
つまり文字列”alpha”と文字列”beta”を連結した時には、新たな文字列”alphabeta”が作られます。
これはちょうど数字の3と4を足した時に7が作られることと同様で、この場合元あった3や4とは異なる新たな値が作られています。

オブジェクト型について

JavaScriptでは、ユーザ定義型を作成することはできません。
よって、ユーザが作成する型は全てオブジェクト型となります。
オブジェクトの型が一つであることは、その機能を限定することを意味しません。
むしろ、型が一つであることでより自由になっていると言えます。
オブジェクトは、その時々で様々な値や形式をとることができる変幻自在なものです。

文字列

JavaScriptにおける文字列は、連続した文字です。
JavaScriptに文字型は存在しないため、単にunicodeのテキストを表します。

文字列リテラルの囲い文字について

JavaScriptの文字列リテラルの囲い文字は、’と”のどちらも利用でき、これらに区別はありません。
しかしどちらかといえば、個人的には、’をお勧めします。
なぜなら、JSON文字列を記述する際のプロパティ名の囲い文字は”であり、JSON文字列をエスケープすることなく記述する際には’を用いるからです。

数値

JavaScriptの数値の型は1つのみです。
整数型や浮動小数点型と行った区別は存在しません。
そのため、速度や精度が求められる数値演算には向きません。

数値リテラル

JavaScriptの数値リテラルは以下の4つです。

  • 10進数
  • 2進数
  • 8進数
  • 4進数

特殊な値

数値リテラルはではないですが、JavaScriptの数値演算の結果として、以下の値をとることがあります。
これらは基本的に無効な数値演算の結果です。
そのため、数値演算を行う際には以下の結果にならないよう気を使う必要があります。

無効な値
  • Infinity
  • -Infinity
  • NaN

また、以上の無効な数値を判定する関数としては、以下が定義されています。

無効な値の判定のための関数
  • isNaN
  • isFinite

Numberオブジェクトのプロパティ

数値の中でも、重要な値はNumberオブジェクトのプロパティとなっています。

真偽値

真偽値はtrueまたはfalseの値をとります。
Cなどの言語では、0をfalseとして扱い、それ以外の全ての数値をtrueとして扱います。
JavaScriptもこれと少し似た挙動で、一部の値をfalseとして扱い、それ以外の全ての値をtrueとして扱います。

falseとして扱われる値

falseとして扱われる値を以下に列挙します。逆に言えば、これ以外は全てtrueになります。

  • 数値0
  • 数値NaN
  • null値
  • undifined値
  • 文字列”(空文字列値)

nullとundefined

nullとundefinedはどちらも存在しないものを表しています。
null型がとる値はnull一つで、undefined型がとる値はundefined一つとなります。

これらの違いを簡単に言ってしまうと、nullは開発者が設定する値で、undefinedはJavaScriptの処理系が設定する値です。

例えば、変数宣言時に値を設定していない場合、undefinedとなります。
値を初期化したいが、該当する値が存在しない場合は、自分自身でnullを設定します。

そのため、初期化の意味で自分でundefinedを設定しないでください。
他の開発者が、値が一度も設定されていないのだと勘違いする恐れがあります。

オブジェクト

型の視点から見たとき、JavaScriptのオブジェクトは特別な型です。
プリミティブ型は一つの値しか表現せず、不変ですが、オブジェクトは複数の値や複雑な値を表すことができ、処理の進行に伴って値も変化することができます。

基本的にはオブジェクトは入れ物として考えることができ、内容は時間とともに変化します。
しかし内容が変わってもそれは同じオブジェクトです。

オブジェクトに記録されるものをプロパティと呼びます。

typeof演算子

typeof演算子で型の名称の文字列値を取得でき、これにより型の判定ができます。
以下にその実例を列挙します。

値の同値比較

同値比較の際は、==演算子または===演算子を用います。

前者は暗黙の型変換を行なった後に比較し、後者はそのまま比較します。

!==演算子と!-演算子の評価結果は、ちょうど上記の否定となります。

数値の同値比較の注意点

JavaScriptの数値は、倍精度浮動小数点数です。
倍精度は近似値のため、倍精度の値を比較した際の結果が誤差によって期待通りにならない場合があります。

このような場合は、近似値による比較を行うことで、正常に動作させることができます。
Number.EPSILONという定数があり、これは二つの数値が等しいとみなすことができる差を表しています。

型変換

JavaScriptにおいては、式の評価の際に暗黙の型変換が行われます。
例えば、文字列’1’と数値1を+演算子で演算した際の結果は、文字列’11’となります。

暗黙の型変換を利用したイディオム

JavaScriptにはこの暗黙の型変換を利用したイディオムが多く存在します。
以下は、undefined値を論理演算するとfalseになることを利用したイディオムです。

こうすることで、xが初期化されていなければ初期値を、初期化されていればそのままその値を使います。

このように、状況に応じて、明示的な型変換と暗黙的な型変換を適切に利用することが必要です。

明示的な型変換と暗黙の型変換

以下に基本型についての明示的な型変換と暗黙の型変換の例を示していきます。

文字列から数値

文字列から数値への型変換には以下があります

  • 明示的
    • Number関数
    • parseInt関数
    • parseFloat関数
  • 暗黙的
    • 加算以外の算術演算
    • 単項+演算子

文字列から数値への変換で気をつけるべきは、NaNです。
文字列に対し、算術演算を行った場合、評価結果はNaNとなります。
NaNは数値ですが、一度NaNになった値はどんな演算をしてもNaNのままです。
参考までに、NaNかどうかを判定するときは、isNaN関数を用います。

数値から文字列

数値から文字列への型変換には以下があります。

  • 明示的
    • String関数
    • toString関数
  • 暗黙的
    • 文字列連結演算

真偽値から数値

真偽値から数値は、三項演算子を使った以下のイディオムで変換できます。
多用するのであれば、関数として定義しても良いかもしれません。

真偽値型への型変換

論理演算が行われるときや、論理判定が行われる時に、真偽値型への変換が行われます。
具体的には、if文やwhile文にて、真偽値型の型変換が起こります。

以下に真偽値型への変換方法を示します。

  • 明示的
    • Boolean関数
  • 暗黙的
    • !!演算子
    • 論理演算

その他の型変換

演算子

JavaScriptの演算子には、いくつかJavaには存在しないものもあります。

  • カンマ演算子
  • void演算子

カンマ演算子

カンマ演算子を使うことで、複数の式を組み合わせることができます。
カンマ演算子は、オペランドである2つの式を評価して、2つ目の結果を返します。
つまり、複数のカンマ演算子で3つ以上の式を組み合わせた時は最後の結果を返します。

void演算子

void 演算子は、評価結果としてundefinedを返します。

リテラル表記

豊富かつシンプルなリテラル表記はJavaScriptの特徴の一つです。
平易で汎用性が高いその便利さはJSONの普及にも裏付けされています。
JavaScriptのリテラルはそれ単体でプログラム中に存在できます。
JavaScriptのリテラルには以下の特徴があります。

  • 文字列や数値、真偽値だけでなく、オブジェクトや関数もリテラルとして記述できます
  • リテラルは独自の型を持ち、変数に格納されることで参照されます。
  • リテラルには入れ子にして記述することができるものもあります

オブジェクトリテラル

{}でブロックを作り、:で区切ったkeyとvalueのペアを,で列挙することで平易に記述できます。

オブジェクトリテラルの利用

オブジェクトリテラルを使って、値の受け渡しの表現が容易に記述できます。

関数リテラル

関数リテラルによって、ブロックでまとめたひとまとまりの処理が平易に記述できます。

関数リテラルの利用

関数リテラルを使うことで、処理の受け渡しの表現が記述できます。

オブジェクト指向

JavaScriptにおけるオブジェクト思考は、プロトタイプベースです。
クラスベースのオブジェクト指向に馴染みのある方にとっては、その型にはまらないやり方に驚くこともあるでしょう。
しかし、知識を深めるにつれて、実は同じ問題を全く違う方法で解決しているのだと気付くと思います。

JavaScriptにおけるオブジェクト指向には以下の特徴があります。

  • オブジェクトは本質的に名前を持ちません。
  • オブジェクトの階層関係は、実行時に子から親へ参照を辿ることで実現されます。
  • オブジェクトのプロパティやインターフェースは、実行時に変更可能で流動的です。

オブジェクトの生成

オブジェクトリテラルによる生成

オブジェクトリテラルを使って、状態と振る舞いを持つオブジェクトを定義すると以下の様になります。

コンストラクタとnew式による生成

関数によってオブジェクトを定義することもできます。
定義された関数は、new式とともにコンストラクタとして呼び出されます。
慣習的にコンストラクタとしての関数名は大文字から始めます。

オブジェクトのプロパティアクセス

オブジェクトのプロパティには通常.演算子を用いることでアクセスできます。
JavaScriptでは上記に加え文字列のプロパティ名と[]演算子を用いることもできます。

.演算子と[]演算子の使い分け

通常は平易な.演算子を用い、.演算子ではプロパティにアクセスできない場合に飲み[]演算子を用いると良いとされています。

以下に.演算子を使えない場合を列挙します。

  • 識別子に使えないプロパティ名を使う場合
  • 変数の値をプロパティ名に使う場合
  • 式の評価結果をプロパティ名に使う場合

プロパティの存在確認及び追加と削除

JavaScriptのオブジェクトは連想配列と考えることもできます。
そのため、実行時にプロパティの存在確認及び追加と削除を行うことができます。

連想配列はkeyとvalueのペアを要素に持つ構造体で、JavaにおけるMapと同義です。

連想配列に対する操作
  • プロパティの存在確認: in演算子を用いる
  • プロパティの追加: オブジェクトに存在しないプロパティを代入文の左辺にする
  • プロパティの削除: delete演算子の右辺に既存のプロパティを指定する

自身のプロパティの存在確認

in演算子でプロパティの存在確認を行う場合、プロトタイプ継承をしたプロパティが存在する場合も真となります。

オブジェクト自身のプロパティの存在確認を行いたい場合は、hasOwnProperty関数を使います。

for in 文でプロパティ名の列挙

for in 文はJavaScriptのオブジェクトのプロパティ名を列挙するための構文です。
オブジェクトのプロパティの数だけ、ループ変数にプロパティ名を代入しつつブロック内の処理を繰り返します。

以下のように記述を行います。

for in 文の注意点
  • ループ変数に格納される値の順番は保証されません。
  • 列挙できないプロパティも存在します。
  • プロトタイプ継承したプロパティも列挙します。

オブジェクトの不変化

JavaScriptのプロパティには、アクセス修飾子といった概念はありません。
そのため、オブジェクトを不変にしたい場合はObjectが持つ関数を用います。
以下の関数にオブジェクトを渡す事で、オブジェクトを不変に出来ます。

  • Object.preventExtentions
  • Object.seal
  • Object.freeze

this参照の規則

thisの参照先は、thisが参照される場所によって異なります。
以下にその対応をまとめます。

  • トップレベル: グローバルオブジェクト
  • コンストラクタ: 生成対象オブジェクト
  • メソッド: メソッドのレシーバーオブジェクト
  • applyまたはcall: 引数に渡したオブジェクト

プロトタイプ継承

JavaScriptはクラスベースではなく、プロトタイプベースのオブジェクト指向言語です。
オブジェクト間の共通化は、型ではなくプロトタイプから性質を引き継ぐことで実現します。

プロトタイプチェーン

プロトタイプ継承による引き継ぎは、プロトタイプチェーンによって実現しています。
その前提は以下の二つです。

  • 全ての関数(関数オブジェクト)はprototypeというプロパティを持つ
  • 全てのオブジェクトは、生成に使ったコンストラクタのprototypeへの暗黙リンクを持つ

オブジェクトのプロトタイプ読み込みの際には、以下の順にプロパティを探します
1. オブジェクト自身のプロパティ
2. 暗黙リンクの参照オブジェクト
3. 2のオブジェクトの暗黙リンクの参照オブジェクトのプロパティ
4. 3を探索が終わるまで続ける(終端はObject.prototypeオブジェクト)

プロトタイプによる振る舞いの設定

プロトタイプ継承により、プロトタイプ経由でオブジェクトに振る舞いを持たせることができます

プロトタイプによる親子関係の実現

プロトタイプ継承によって親子関係を実現することもできます。

型判定

プログラミングにおける型とは、その値に何ができるかを規定するためにあります。
JavaScriptにおける型は曖昧な概念であり、あらかじめ規定しておくことはできません。
そのため、プログラマが適切に見立てる必要があります。

constructorプロパティの参照先

オブジェクトのconstructorプロパティには生成時のコンストラクタの参照が格納されます。
これにより、そのオブジェクトの振る舞いの一端を知ることができます。

instanceof演算とisProtoTypeOfメソッド

constructorの値を判定するよりも一般的な方法として以下の2つがあります。
これらはプロトタイプチェーンで拡張継承している場合も判定できます。

  • instanceof演算
  • isProtoTypeOfメソッド
instanceof演算

左辺にオブジェクト参照、右辺にコンストラクタを指定する判定方法です。

isPrototypeOfメソッド

ObjectのisPrototypeOfメソッドを利用する方法です。

コンストラクタのprototypeプロパティをレシーバーオブジェクトとして呼び出し、引数にインスタンスを渡して判定します。

ダックタイピング

ダックタイピングとは、以下の言葉で表される動的型付け言語における型の作法です。

もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルである

あるオブジェクトがあるインターフェースを宣言的に実装していなくとも、そのインターフェースに求められる全ての振る舞いを行うことができるならば、それは実行時にそのインターフェースを実装しているとみなせるということです。

JavaScriptにおいては、実行時にオブジェクトのプロパティとして振る舞いを追加していくことは日常茶飯事のため、その時点での振る舞いを知る必要があります。

in演算によるダックタイピング

in演算子の右辺に文字列のプロパティ名、右辺にオブジェクト参照を指定することで、あるオブジェクトがプロパティを保持しているかが判定できます。
そのプロパティが関数であれば、そのプロパティ名を持つ関数を実行できるということを意味します。

グローバルオブジェクト

グローバルオブジェクトとは、起動時から暗黙的に存在するオブジェクトです。
ホスト環境から受け継いだホストオブジェクトのことを指します。

ホスト環境とホストオブジェクト

JavaScriptは拡張言語として開発されてきた歴史があります。
拡張言語は、実行時のコンテキストをホスト環境から受け継ぎます。
JavaScriptでは実行コンテキストをホストオブジェクトとして受け取ります。

クライアントサイドJavaScriptを例にとります。
ホスト環境はWebブラウザであり、ホストオブジェクトはwindowオブジェクトです。
よって、グローバルオブジェクトはwindowオブジェクトとなります。

グローバルオブジェクトへの参照とプロパティ

トップレベルにてthis参照をするとグローバルオブジェクトにアクセスできます。

グローバル変数とグローバル関数

トップレベルにて宣言された変数をグローバル変数、関数をグローバル関数と呼びます。

グローバル変数とグローバル関数はグローバルオブジェクトのプロパティです。

実はJavaScriptには型名と言った概念は存在せず、全て変数です。
ObjectやStringも型名ではなく、全てグローバルオブジェクトのプロパティとなります。

このグローバルオブジェクトがどんなプロパティを持つかは、実行環境に依存します。

まとめ

今回は基礎的な知識についてまとめました。

後編でも、引き続きJavaScriptの基礎知識についてまとめていきます。

 

☆☆ウィズテクノロジーでは、大阪、東京を中心としたシステム開発を行っております
システムのご相談はもとより、一緒に働くエンジニアもお待ちしております。☆☆