tanaka's Programming Memo

プログラミングについてのメモ。

パーフェクトJavaScript勉強メモ(5)

前へ | 次へ

5章 変数とオブジェクト

変数の宣言

  • p109 変数が宣言されていなければ初期値を代入するイディオム
var a = a || 7;
  • ||は4章でやった論理演算子。左のオペランドがtrueの時は左のイディオム、つまりaにもともと入っていた値が代入され、falseの時は右のイディオム、つまり7がaに代入される仕組み
  • よって、aに入っているのがブーリアン変換した際にfalseになる以下のものも7になる。
    • 数値0
    • 数値NaN
    • null値
    • undefined値
    • 空文字列

変数と参照

  • p110 オブジェクトには名前はなく、オブジェクトを呼び出すための名前をつけるのが変数
  • 参照とポインタの違いは、ポインタは演算ができる点
  • 変数には、値とオブジェクトの参照が入れられる
    • 値はそのものなので、変数aを変数bに代入しても、aとbに相関はない
    • オブジェクトの参照が入っている場合は、変数aを変数bに代入すると、参照先が渡されるので、aにアクセスしてプロパティを変更すると、bでのアクセスでもプロパティが変わる
    • p111 参照型変数の代入は、参照先のコピーであり、変数aと変数bを同一にするものではない。aをbに代入した後、aに別のオブジェクトを入れた場合は、aとbは違うオブジェクトを参照することになるので、aで変更したプロパティは、bでのアクセスでは別のプロパティ参照になり、影響を受けなくなる
関数の引数(値渡し)
  • p112 関数の引数は、値渡し
  • 参照型変数でも、参照先の内容の値をコピーするので、引数のもとには影響はない
  • p113 複数の結果を返す場合は、配列やオブジェクトを戻り値として返す
文字列と参照
  • 文字列型を引数に渡すと、参照で渡すが、利用時に文字列型になるので実質は文字列型として渡したようなもの
  • 文字列オブジェクトを代入した変数を渡す場合は参照型で渡す

変数とプロパティ

  • p114 JavaScriptでは同じもの
  • グローバル変数は、関数外で宣言した変数
  • ローカル変数は、関数内で宣言した変数
  • トップレベルコードのthisは、グローバルオブジェクトを指している
  • プロパティ名 in オブジェクトで、そのオブジェクトにプロパティが含まれているかを確認できる
  • p115 クライアントサイドJavaScriptでは、グローバル変数windowが渡される
  • 関数が呼び出されると、引数や関数内で宣言される変数が暗黙に生成される。これをCallオブジェクトという

変数名の解決

  • トップレベルコードでは、グローバルオブジェクトのプロパティを探す
  • ローカル変数は、Callオブジェクトのプロパティ→グローバルオブジェクトのプロパティの順で探す
  • 関数が入れ子の場合は、内側から検索して、最後にグローバルオブジェクトのプロパティを探す

変数の存在チェック

  • var a=a||7;
  • p116 a !== undefined ? a : 7;でチェック
  • typeof a !== 'undefined'でチェック
  • 'a' in thisでプロパティの有無をチェック
プロパティの存在チェック
  • p117 存在しない変数を評価しようとするとエラーが発生するが、存在しないオブジェクトのプロパティはundefinedを返す
  • undefinedのプロパティ(未初期化)を利用しようとするとTypeErrorが発生
  • TypeErrorの回避方法は以下のイディオムを利用。xがなければundefined、あればyにアクセス
obj.x && obj.x.y

オブジェクトとは

抽象データ型とオブジェクト指向
インスタンスの協調とオブジェクト指向
JavaScriptのオブジェクト
  • JavaScriptのオブジェクトでは、協調(メッセージング)は、お互いのプロパティを呼び合うことで、共通性(継承)はプロトタイプベースで行う
  • オブジェクトとは、データを保持し、その振る舞いをメソッド(関数)でまとめたもの。それらを使ってプログラムを分割して簡潔にしていく

オブジェクトの生成

  • オブジェクトの生成は、{}というリテラルで書くとJavaScriptっぽい
  • オブジェクトリテラルを使う場面
    • p119 シングルトンパターン的な用途
    • 多値データの利用法
      • 関数の引数などに、複数のデータを渡したい時に、すべてのパラメータ分の引数を用意すると、指定する順番を間違える可能性がある。そこで、オブジェクトのプロパティに値を代入するようにして、プロパティ名とデータを紐づけることで間違えにくくする。連想配列でも可
      • p120 オブジェクト渡しにすると、引数を省略するとundefined、指定すればオブジェクトの参照が渡されるので、||による省略時のイディオムが誤動作しづらくなる
      • 戻り値でオブジェクトリテラルを使うと、複数の結果を簡単に返すことができる
    • コンストラクタ代わりの関数での利用法
      • 関数の戻り値として、オブジェクトリテラルを返すことで、その関数を呼び出すごとにオブジェクトを量産できる
コンストラクタとnew式
  • p122 コンストラクタは普通の関数宣言と同じ形
  • newに続けて関数を書くとコンストラクタとして振舞う
  • newの評価値は生成されたオブジェクト参照
  • newで呼ばれたコンストラクタ内のthisの参照は、新規に作成されたオブジェクトの参照
  • newを呼び出すと、空のオブジェクトが生成され、そのオブジェクトをthisとして関数が実行し、式の評価値としてオブジェクト参照が返る
  • すべての関数はnewをつけて呼び出すことができ、コンストラクタとして振舞うことができる
  • p123 コンストラクタ関数名は、慣例的に大文字から始める
  • コンストラクタにreturnをつけると、生成したオブジェクト参照ではなく、returnで指定した戻り値が返ってしまう。コンストラクタにはreturnは書かないこと
コンストラクタとクラス定義
  • 基本的なクラスの例
  • このまま利用した場合の問題点
    • 同じ関数がインスタンスごとに生成されるのでメモリが無駄→プロトタイプで解決
    • 変数や関数の隠蔽ができない→クロージャで解決

プロパティのアクセス

  • p124 .か[]でプロパティにアクセス
  • .の後ろは識別子。[]内は文字列
  • オブジェクトリテラル内では、プロパティは文字列表記でも識別子表記でもどちらでもよい=既存の変数名を書いても、変数の値ではなく、そのまま識別子として扱われる(補足:混乱しない+メモリ節約のため、文字列型にはしないほうがよいだろう)
プロパティ値の更新
  • 代入で値設定可能
  • 存在しないプロパティを代入先に指定すると、新規に追加される
  • deleteでプロパティを削除できる
ドットとブラケットの使い分け
  • 通常はドット。以下のいずれかの時はブラケット
    • 識別子として使えないプロパティ名を利用するとき
    • 変数の値をプロパティ名として利用するとき
    • 式の評価結果をプロパティ名に利用するとき
プロパティの列挙
  • p126 for inでオブジェクトが持つプロパティを列挙できる。プロトタイプも含む

連想配列としてのオブジェクト

p127 連想配列の操作
p128 連想配列としてオブジェクトの注意点
  • プロトタイプに追加したプロパティは、インスタンスのプロパティをdeleteしても消えないので注意
  • p129 オブジェクトを連想配列として利用するために、オブジェクトリテラルで空のオブジェクトを生成しても、toString()などのオブジェクトがもともと持っているプロトタイプのプロパティを持っている
  • 'toString' in objとすると、trueが返ってくる。ただし、for inには列挙されない。これは、enumerable属性による
  • 自分自身が持っているかは、inではなく、hasOwnPropertyでチェックしたほうがよい(obj.hasOwnProperty('prop'))

プロパティの属性

  • プロパティには属性がある
  • p130 ECMAScript5のプロパティ属性
    • writable プロパティ値の書き換えが可能
    • enumerable for in文で列挙可能
    • configurable 属性を変更可能。削除可能
    • get ゲッター関数の指定可能
    • set セッター関数の指定可能
  • ECMAScript5以前は、enumerable属性のみであり、まだ一般的にはなっていない。利用は普及状況を見ながら

ガベージコレクション

  • 使わなくなったオブジェクトを常にdeleteで削除するなどの気配りはしなくてよい。システムに任せる
  • 循環参照をすると、メモリリーク(メモリの解放漏れ)が発生するのは気をつける
  • パターンがいくつかあるのでツールで検出できる(補足:Googleのleak-finderなど)

不変オブジェクト

  • p131 広義には変更しないオブジェクト。狭義には変更をしようにもできないオブジェクト
  • JavaScriptでは文字列オブジェクトは不変オブジェクト
不変オブジェクトの有用性
  • バグの原因の一つが不正な変数値の書き換え
  • これを禁止することでバグのリスクを減らせる
  • しかし、不変であることを徹底すると、メモリ効率が悪化するので、利用には兼ね合いが必要
不変オブジェクトの手法
  • プロパティに外部からアクセスできないように隠蔽する
  • ECMAScript5の関数を活用
    • p132に一覧
    • Object.preventExtensions(obj)、Object.seal(obj)、Object.freeze(obj)として属性を設定できる
    • p133 一度設定すると元に戻せない
    • プロトタイプは対象外なので、必要であれば明示的に設定が必要
    • sealはconfigurable属性をfalseに、freezeはwritable属性をfalseにしている。その操作でも同様のことが可能
  • Writable属性、Configurable属性、セッター、ゲッターの活用

メソッド

  • JavaScriptメソッドは、オブジェクトのプロパティに関数をセットしたもの
  • メソッドも関数により実装されているが、オブジェクトに関する手続きを目的としていることを明確にするために呼び分ける

this参照

  • p134 どこでも使える読み込み専用の変数
  • 処理対象のオブジェクトの参照が入っている変数で、実行する場所や関数の呼び出し方で中身が変化する
this参照の規則
  • トップレベルコードではグローバルオブジェクトを参照
  • 関数内では以下の通り
    • コンストラクタ呼び出し…生成したオブジェクト
    • メソッド呼び出し…メソッドの呼び出し元のオブジェクト
    • applyあるいはcall呼び出し…引数で指定したオブジェクト
    • それ以外の呼び出し…グローバルオブジェクト

this参照の注意点

applyとcall

  • f.apply(obj);やf.call(obj);で、thisをobjにして関数fを実行することができる
  • p137 applyは、第2引数に、関数に渡す引数を配列で渡す
  • callは、第2引数以降に、関数に渡す引数を同じようにコンマ区切りで渡す

プロトタイプ継承

プロトタイプチェーン
  • すべての関数(オブジェクト)はprototypeプロパティを持つ(関数名.prototype)
  • すべてのオブジェクトは、オブジェクト生成に使ったコンストラクタ関数(関数オブジェクト)のprototypeオブジェクトへの暗黙のリンクを持つ(this.prototypeない。オブジェクトのプロパティにアクセスすると、コンストラクタ関数.prototypeを遡ることができる。オブジェクトはコンストラクタ関数そのものではないので、クラスベースの言語のように、生成元と対応付けてprototypeにアクセスしているということは、暗黙でコンストラクタへのリンクを持っているということになる)
  • プロパティの検索順は以下の通り
    • オブジェクト自身
    • コンストラクタのprototypeオブジェクト(隠し)
    • 上記のオブジェクトのコンストラクタのprototypeオブジェクト(隠し)
    • 最終的に、Object.prototypeまで遡って検索
      • オブジェクトリテラルで作成したオブジェクトのprototypeはObject.prototypeを参照
  • プロパティの書き込みでは、prototypeを自動的にさかのぼることはしない
  • p139 暗黙リンクの参照先オブジェクトを、プロトタイプオブジェクトと呼び、プロパティ読み込み時に、プロトタイプオブジェクトのプロパティを継承する
  • プロトタイプ継承の図
プロトタイプチェーンの具体例
  • p140 prototypeを書き換えた場合は、そのprototypeを持つコンストラクタから生成したすべてのオブジェクトの振る舞いも変更する。持っているのがコンストラクタのprototypeの参照のため
プロトタイプ継承とクラス
プロトタイプチェーンのよくある勘違いと__proto__プロパティ
  • 自分自身のオブジェクトのプロパティに見つからなかった後、コンストラクタのプロパティを検索するという間違い。探すのはコンストラクタのprototypeのプロパティ
  • オブジェクトのprototypeのプロパティを探すというのは間違い。遡るのはコンストラクタのprototype
  • 一部のJavaScriptには__proto__プロパティで、オブジェクトが持っているコンストラクタのprototypeを参照できるものがある。ECMAScriptの仕様ではない
プロトタイプオブジェクト
  • p143 暗黙リンクが持つオブジェクトをプロトタイプオブジェクトというが、この呼び方は紛らわしい
  • コンストラクタのprototypeと、そこから生成したオブジェクトのプロトタイプオブジェクトが同一のもの。コンストラクタのprototypeは、コンストラクタのプロトタイプオブジェクトではない。コンストラクタのプロトタイプオブジェクトとは、コンストラクタを生成した関数のprototypeであるので、Function.prototypeになる
プロトタイプオブジェクトとECMAScript5
  • ECMAScript5から、Object.getPrototypeOf(obj)で、obj.__proto__と同じことができる

オブジェクトと型

  • p144 オブジェクトの型は"object"
  • どのようにプロパティを構成するかで、実質的な型は決まってくる
型判定(constructorプロパティ)
  • obj.constructorで、そのオブジェクトの生成に使ったコンストラクタ関数を参照できる
constructorプロパティの注意点
  • オブジェクト自身が持つプロパティではなく、プロトタイプチェーンで探した先のプロパティである
  • p145 プロトタイプチェーンを作成した時に、prototypeに設定したオブジェクトのコンストラクタ関数が呼び出されてしまう
  • 正しく呼び出されるようにするには、明示的にコンストラクタ.prototype.constructor = コンストラクタ;としておく
型判定(instanceof演算子とisPrototypeOfメソッド)
  • p146 constructorより一般的なのがinstanceofによる判定
  • オブジェクト instanceof コンストラクタ関数 として、該当オブジェクトがコンストラクタ関数で生成されていたらtrueが返る
  • instanceofの場合、プロトタイプチェーンにあるすべてのコンストラクタ関数のいずれでもtrueを返す
  • コンストラクタ.prototype.isPrototypeOf(obj)でも、同様のことができる
型判定(ダックタイピング)
  • in演算子で、プロパティの有無をチェックすることで、キーとなるプロパティで型を判定するもの
  • JavaScriptでは、コンストラクタでオブジェクトが生成されるとは限らず、後付けでプロパティが追加されることもあるので、こちらの方がより実用的
プロパティの列挙(プロトタイプ継承を考慮)
  • p147 for inでは、プロトタイプも遡ってすべてのプロパティを列挙する
  • 自分自身が持っているプロパティだけをチェックしたい場合は、for inの中で、obj.hasOwnProperty(key)で自分自身のプロパティかを判定して処理する
  • ECMAScript5では、keysとgetOwnPropertyNames()が利用できる
    • keysは、for-inとhasOwnProperty()で検索したのと同じ内容
    • getOwnPropertyNames()は暗黙のプロパティも含めて列挙する
  • enumerable属性は、propertyIsEnumerableメソッドでチェックできる

ECMAScript5のObjectクラス

  • p148 Object.create()で、プロトタイプオブジェクトを指定してオブジェクトを生成できる
  • Object.create(null)とすると、Objectへのプロトタイプを持たないオブジェクトを生成
  • prototypeへのオブジェクトの代入などの形にならないので、直感的にかける
プロパティオブジェクト
  • p149 createの第2引数には、プロパティと属性をセットにしたオブジェクトを渡す
  • 実例がある
アクセッサ属性
  • p150 get属性とset属性を指定すると、アクセッサーのみアクセスになる
  • getとsetを指定するとvalueが無効になる。逆も同様
  • getとsetを設定すると、該当プロパティへの代入でset、読み出しでgetが呼ばれる
  • オブジェクトリテラルでも、関数名の前にgetやsetを指定すると設定できる
  • アクセッサ関数内のthisは、該当オブジェクトを参照
  • p151 クロージャを使った利用例
その他の型判定
  • p152 ECMAScript5には、Array.isArrayメソッドがあるので、配列はこれでチェック

標準オブジェクト

  • ECMAScript5の組み込みクラス一覧
    • Object
    • グローバルオブジェクト
    • String
    • Array
    • Function
    • Number
    • Boolean
    • Math
    • Date
    • RegExp
    • JSON
    • Error
    • EvalError
    • RangeError
    • ReferenceError
    • SyntaxError
    • TypeError
    • URIError

Objectクラス

  • p153 Objectを明示してnewするより、オブジェクトリテラルを利用するのが一般的
  • ECMAScript5のObjectクラスが持つプロパティ一覧
  • p154 Object.prototypeが持つプロパティ一覧

グローバルオブジェクト

  • p155 クライアントサイドJavaScriptでは、windowがグローバルオブジェクトに該当する
  • ECMAScriptの処理系ではwindowのような名前はない。トップレベルコードのthisでアクセスできる
  • p156 サーバサイドJavaScriptなどでは、トップレベルコードでvar global = this;などとして、グローバルオブジェクトを代入しておくなどする
グローバルオブジェクトとグローバル変数
  • ECMAScript5のグローバルオブジェクトが持つプロパティ一覧
Mathオブジェクト
  • p157 Mathオブジェクトが持つプロパティ一覧
Errorオブジェクト
  • エラーのプロパティ一覧
  • p158 Error.prototypeの一覧
    • JavaScript独自拡張で、fileName、lineNumber、stackなどがある

前へ | 次へ