JavaScriptのObjectを理解したい 

タイトルは適当で、JavascriptのObjectと、それに付随する
Javascriptの特徴的な色々を理解したいのでまとめ。

  1. (ES5以前の)prototypeによるオブジェクトを理解する
  2. ES6以降におけるclass構文とprototypeの相互関係
  3. Objectインスタンスとは何か
  4. new Object(){...}Object.create()
  5. オブジェクトとプリミティブな値
  6. Javascriptにおけるイミュータブルな値
  7. おまけ

一通り書いてみて思ったのが、この記事の内容は、
「開眼JavaScript」っていうオライリーの本読めば、内容が若干古いものもあるにせよ、
だいたい書いてあることで、その前提知識があれば、ES6関連を流し読みするだけで
理解できる程度のものだと思います。

1. (ES5以前の)prototypeによるオブジェクトを理解する

(ES5以前)JavascriptにはClassは存在しない

prototypeでJava等のクラスみたいなことをやりたいとき。

Javascriptにはクラスという概念は存在せずに、コンストラクタが存在する。
コンストラクタとは何か、というとJavascriptではnewを使った関数呼び出しのこと。

newを使ってコンストラクタ呼び出しを行うと、内部的には暗黙的に以下のような処理がされる

なので空のオブジェクトが返ってくる。
参考リンクにもあるように、以下のコードでメソッドを定義することができるが、
よりベターなのはprototypeを利用すること。

newを使っても使わなくても同じような挙動をさせたいときは、
明示的にローカル変数をreturnすればOKです。

Qiita JavaScriptのクラス?コンストラクタ??

よりベターな書き方として、下記のような記法も参考リンクで紹介されています。

これに関しては好みなので、好きなほうで書いたらいいと思います。

それじゃ以降にも使えるように、簡単なサンプルを宣言してみます。

こんな感じ。
アクセス修飾子に関しては参考リンクに紹介されていますが、本質ではないので
一旦省略します。

所謂Javaのクラスみたいに振る舞っているけど、プロトタイプベースで
あることに注意

継承どうやるのか

定義はわかっても、それを継承できないときつい。

MDN web docs 継承とプロトタイプチェーン
POSTD JavaScriptにおける継承のパターン4種類の概要と対比
Microsoft Developer Network prototypeプロパティ

色々説明や継承を実現するための方法がかかれていますが、抑えて置くべきポイントは以下

  1. prototypeというプロパティとは何か > 自身の親への参照のようなもので、それを元にオブジェクトが生成される

厳密にはもっともっと細かい話が入るんですが、一旦シンプルにするために
説明はこの程度にしておきます。

このようにprototypeをコンソールに出してみると、showをメンバに持ったインスタンスをprototype
にもっていることが確認できると思います。

それではshowをメンバに持ったインスタンスのprototypeは何になっているでしょうか。
掘り下げられるだけ掘り下げてみましょう。

__proto__はオブジェクトにが持つprototypeへのgetter/setterになります。(詳しくはMDN見てほしい)

雑な説明だけど関数はprototype、オブジェクトは__proto__でprototypeにアクセスできる
と思っておいていただければ。

この結果を見てもらうとわかるように

ProgrammingLanguage > Object(showを定義したやつ) > Object > null

という感じになってます。
で、例えば

ここでtoString()メソッドはObjectで定義されていて、それをprototypeを順番に追っかけて
探します。これが所謂prototypeチェーン。

つまりprototypeに親を入れてあげれば継承が実現できるのでやってみるんだけど、そのための
関数が標準であります。

こんな感じ。EmployeePersonを継承し、呼び出されているのが確認できます。

これで、ES5以前のプロトタイプによるクラスと同等の機能が実現できるし、プロトタイプチェーン
の概要もつかめました。

(多重継承と、アクセス修飾子は仕様上難しい。後者は命名規則で運用されているケースが多いようです。)

2. ES6以降におけるclass構文とprototypeの相互関係

参考 MDN web docs クラス

僕にとっては、既にこっちのほうが馴染みがありますが、ES6でサポートされたclass構文。
これはJavaのような言語のクラスをJavascriptで可能にしたものではなく、あくまで前述の
プロトタイプベースのシンタックスシュガーであることに注意してください

書き方はJavaみたいな書き方ができます。
これをまず書いてみて、その後、同等のprototypeで書かれたコードに置き換えてみます。

まず最小限のコード

これでHogeインスタンスが生成されます。
メンバもプロパティも存在しません。

これは下記のコードと同等

まずはコンストラクタを定義してみます。

これは下記と同等

次はメソッドを定義してみます。

これは以下と同等

こんな感じで、従来のprototypeによる宣言と、互換性があるのが
確認できたと思います。

継承もextendsキーワードで同じようにできます。

staticとかgetとかsuperとかその他のキーワードも
ありますが、省略します。
(前項の同じ例を何回も出すだけになるので)

MDNのサンプルで紹介されてるMix-inおもしろかった。

3. Objectインスタンスとは何か

参考 MDN web docs Object

デフォルトでインスタンスを生成すると(newすると)prototype(proto)に
設定されているObjectというオブジェクトが何者なのか、見ていきます。

一旦参考リンクの用語について、書いておきます。

  • コンストラクタプロパティ
    • Javaでいう静的プロパティ
  • コンストラクタメソッド
    • Javaでいう静的メソッド
  • プロパティ
    • Javaでいうメンバ変数
  • メソッド
    • Javaでいうメンバメソッド

コンストラクタ、という文言が、Java等に馴染んでる自分には最初混乱しました。

これは、ちゃんと説明できる知識が僕になくて申し訳ないんですが、コンストラクタを提供してる
Objectプロトタイプオブジェクトと、それをnewして生成されるインスタンスを
明確に区別して整理する必要があります。
(クラスベースでいう、クラスがプロトタイプオブジェクト、っていう感覚でここはよいと思います。
プロトタイプベースと呼ばれる言語をJavascript以外に触ったことがないので、おそらく
明確な定義で違いはたくさんありそうですが。)

よく使うけど、よくわかってなかったメソッドとかの一部を紹介してみようと思います。

  • Object.assign
    • 参考
    • とりあえず雑にオブジェクトのコピーしたいときとかに使います。
      • Object.assign({}, source) みたいな感じで
  • Object.create
    • setPrototypeOfあるからあんまり使わないかも。
    • 引数に渡したオブジェクトをprototypeに設定して返す。
    • Object.create({a:1, b:2, c:3})
    • これの戻り値を他のオブジェクトのprototypeに設定することで継承を実現するサンプルが参考リンクにあります。
    • Object.createは、他のことと一緒に後述します。
  • Object.freeze
    • 参考
    • オブジェクトを不変(イミュータブル)にする。
    • ネストしているオブジェクトまでは不変にできるわけじゃないので注意
    • そして、上書きしようとしてもエラーが出るわけではないのでそこも注意
      • Javascript
        let g = Object.freeze({a:1, b:2, c:{d:3, e:4}})

      g.a = 100
      console.dir(g.a) // 1 書き換わってない。でもエラーが出るわけじゃない

      g.c.d = 100
      console.dir(g.c.d) // 100 ネストしたオブジェクトまでfreezeできるわけじゃない

  • Object.isFrozen
    • 引数に受け取ったオブジェクトがfreezeされてるかBoolean型で返す。
  • Object.values/Object.keys
    • Object.values
      • 引数に渡されたオブジェクトの値のリストを返す
    • Object.keys
      • 引数に渡されたオブジェクトのキー(プロパティ、メソッド名)のリストを返す
  • Object.setPrototypeOf
    • 第一引数のprototypeに第二引数のオブジェクトを設定する
  • Object.prototype.constructor
    • これはこのメソッドを覚えるっていうよりはprototype.constructorを意識できると○
    • 要するにコンストラクタ呼び出しされたときに実行される関数が入ってる。
      • Javascript
        var Hoge = (function() {
        var c = function(name) {
        this.name = name
        }
        var p = c.prototype
        p.greet = function() {
        return 'Hello, My name is ' + this.name
        }
        return c
        })()
      • ここでいう、cがprototype.constructorにあたる。

まとめると

  1. Objectプロトタイプオブジェクト
    • Objectの生成に関するいろいろな機能を提供する
  2. Objectインスタンス
    • JavaScriptにおけるすべてのオブジェクトのプロトタイプになる

4. new Object()と{…}とObject.create()

ここではインスタンス生成の色々な方法について紹介します。

  1. コンストラクタ関数によるオブジェクト生成

  1. Object.createによるオブジェクト生成

既存のObjectから新しいオブジェクトを作成する。

これを他のオブジェクトのprototypeに設定することで継承と同等
のものが実現できる。

setPrototypeOfはこれのシンタックスシュガー的なもの、だと思ってるけど
合ってるかな。。。

  1. オブジェクト初期化子によるオブジェクト生成

これが簡単で色々できて、一番おなじみかもしれない。

  1. コンストラクタ関数にオブジェクト、もしくはプリミティブな値を入れてみる
  • プリミティブな値を渡してみる。

Number,String,Booleanといったプリミティブな値を渡すと、それをラップした
オブジェクトが返ってくる。(これはプリミティブな型とは同盟のオブジェクトの型であることに注意)

  • オブジェクトを渡してみる。

オブジェクトが返ってくる。
次は、「プリミティブな値」と「オブジェクト(インスタンス)」を調べてみる。

5. オブジェクト型とプリミティブな値

String, Boolean, Number, Null, UndefinedがそれぞれJavasciptの
プリミティブな型になります。

NullとUndefinedはそれぞれ値が1種類しかないので、省略します。
それ以外のものに関して、調べてみます。

1. String型

こんな感じになってます。
Javascriptの等価演算子の==は比較時に型をチェックしませんので、trueが返ってきますが、
===はfalseになってます。

それぞれstring型とobject型となっています。
この結果からNumberとBooleanも想像ができますが、一応書いておきます。

2. Number型

3. Boolean型

想像の通り。型は違うのが確認できました。
String,Number,Booleanは文法が用意されていますが、同名の
オブジェクトとは型が違うことに注意しなければなりません。
(通常、String,Number,Booleanをオブジェクト型で用いるケースは多くないと思いますが。)

6. Javascriptにおけるイミュータブルな値

まずは参考リンクをいくつか。

注意してほしいのが、参考リンクにおける再代入不可能製とイミュータブルを混同しないことで、
ここで説明したいのはイミュータブルについて、です。
(Scala始めたばっかりの頃、少し混乱したので念のため。再代入不可能性はconstキーワードを
用いることで実現できます。)

本題に戻ると
Object.freezeを利用することで、オブジェクトをイミュータブルにすることができます。

この通り、Object.freeze()に渡したオブジェクトは、変更をしても、その値を
オブジェクトが保持していない(変更していない)のが確認できると思います。

ただ、注意点として、Object.freeze()実行後に、変更しようとしても
変更されませんが、エラー等が出るわけではありません。

そしてもう一つ、ネストしたオブジェクトに対しては、ネストしているオブジェクトに
対してもObject.freeze()していないとイミュータブルではありません。

以下に例を挙げます。

このように、ネストしているcはイミュータブルではありません。
再帰的に処理する等の対処法が必要です。

Facebook製のImmutable.jsという、イミュータブルなオブジェクトとかコレクションを
使えるようにしてくれるライブラリがあるので、それの実装を
どこかで見れたらいいなぁ。

7. おまけ

この記事を書くにあたって色々調べた過程で見つけた、面白い参考リンク
貼っておきます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です