自習室

こもります

開眼!JavaScript:オブジェクトの振る舞いについて細かいとこメモ (1/3)

どんな本

開眼!  JavaScript ―言語仕様から学ぶJavaScriptの本質

開眼! JavaScript ―言語仕様から学ぶJavaScriptの本質

(ECMA-262 3rd edition 準拠の) オブジェクトの振る舞いについて、こまかーいところまで拾って説明してくれている本。

最近仕事でJavaScriptを書くことがふえたので、がむしゃらに書いて動いてはいるけど、いまいちよく分かっていないことが多いなぁ、と感じていたので、冷静に一度勉強してみようと思い手に取りました。

この記事は、自分のための勉強メモとして作成しています。

1. オブジェクト

オブジェクトとは、名前と値を持つプロパティを格納するコンテナにすぎない

JavaScriptで表現されるほとんどの値がオブジェクトである

'foo' や 5, trueといったプリミティブ値も存在するが、これらのそれぞれの方に対応する、ラッパーオブジェクトが存在し、プリミティブ値をオブジェクトのように扱うと、オブジェクトのように振る舞う。

var str = 'foo'; // 文字列リテラルで生成されたプリミティブ値
console.log( typeof str ); // 出力: string
console.log( str.toString() );
// このとき、strはtoString()というメソッドを持つオブジェクトになっているが、
// 終了後またstring型のプリミティブ値になる

コンストラクタは new 演算子を使って実行されない限りは、関数以上の何者でもない

new 演算子を伴って呼び出された場合、その関数スコープにおけるthisの値として、新たに生成されるオブジェクトを設定し、本来return文を宣言しない場合にはfalse相当の値を返すところを、コンストラクタ関数はその代わりに新たに生成されるオブジェクト(先述の this で参照するオブジェクト) を返し、インスタンスと見なされる。

var Person = function Person(age) {
  // this = new Object(); 新しいオブジェクトが生成される
  this.age = age;
  // return this; 的な事が行われる
};

var John = new Person(28);

ネイティブ/ビルトイン オブジェクト

JavaScriptは以下の9つのオブジェクトとプリミティブ型でほぼ構成されている

  • Number() // プリミティブ型
  • String() // プリミティブ型
  • Boolean() // プリミティブ型
  • Object()
  • Array()
  • Function()
  • Date()
  • RegExp()
  • Error()

コンストラクタ関数で作ったオブジェクトにはconstructorプロパティが自動的に生成される

var Person = function Person(age) {
  // this = new Object(); 新しいオブジェクトが生成される
  this.age = age;
  // return this; 的な事が行われる
};

var John = new Person(28);
console.log( John.constructor ); // 出力: function Person(age) { this.age = age } (Chromeでの挙動)
console.log( John.constructor === Person ); //  出力: true

リテラルは new 演算子を使用した場合と同じことをする。あくまでショートカット

// 全く同じオブジェクトを作る
var myArray = new Array('foo', 'bar');
var myArrayLiteral = ['foo', 'bar'];
console.log( myArray == myArrayLiteral ); // 出力:true
console.log( myArrayLiteral.constructor ); // 出力: function Array(){ [native code] }

リテラルはコード量が減って便利なのでガンガン使いましょう。

var number = 1;
var str = 'hoge';
var bool = true;
var obj = {};
var ary = ['hoge', 'fuga'];
var func = function() {};

string() などプリミティブ型についてはリテラルだとプリミティブ値を生成するけど、それはちょっと前に書いたとおりオブジェクトとしても扱える。

null は object, undefined は undefined

null は、プロパティが参照するオブジェクトまたは値が無い状態を表す。 undefinedはそのプロパティそのものが定義されていない状態を意味する

var nl = null; // nl は空っぽ
var und = undefined; // und は定義されていない(なので、こういった書き方は本来やらない)
console.log( typeof nl ); // 出力 'object'
console.log( typeof und ); // 出力 'undefined'

オブジェクトは参照によって保存・操作される

var obj = { hoge: 'hogehoge' };
var copyOfObj = obj; // 参照渡し
// copyOfObj と obj は同じメモリの内容を参照している

同値判定 === は参照で判定

console.log(copyOfObj === obj); // 出力:true

instanceOf 演算子

var Cnstrctr = function() { this.foo = 'bar'; };
var instanceOfCnstrctr = new Cnstrctr();
console.log ( instanceOfCnstrctr instanceOf Cnstrctr ); // 出力 true

どんなオブジェクトもミュータブル

ArrayだろうがBooleanだろうがおかまいなし。JavaScriptのワイルドさ。

var myArray = new Array();
myArray.myprop = 'hogehoge';
console.log( myArray.myprop ); // 出力: "hogehoge" - 全く以てArrayとして使っていない!

var myBool = new Boolean();
myBool.myprop = 'fugafuga';
console.log( myBool.myprop ); // 出力:"fugafuga" - 全く以てBooleanとして使っていない!

ただし、プリミティブ値にはプロパティを追加できない。

var myStr = 'string desu yo';
myStr.myprop = 'mo-iccho';
console.log(myStr.myprop); // 出力: undefined

2.プロパティ

ブラケット記法はドット記法と同等に使えるけど、そんなに好んで使いたい物ではない

var cody = new Object();
cody.age = 23;
cody['gender'] = 'male'; // cody.gender = 'male'; で良いでしょ
console.log( cody ); // 出力: Object:{age: 23, gender: "male" }

プロパティ名として数値から始まる物や予約後は不正だが、ブラケットだと特別に可能になる。けど、特に使いたい物ではない

cody.123 = 123; // SyntaxError: Unexpected number
cody['123'] = 123;
console.log( cody ); // 出力: Object {123: 123, age: 23, gender: "male"}  一応正しく格納されたようだ
console.log( cody.123 ); // でもやっぱりSyntax Error

delete 演算子で オブジェクトのプロパティを削除

var foo = { bar: 'bar' };
delete foo; // var で宣言したそのスコープの変数はdelete出来ません。
delete foo.bar; // (return true) 消せます
console.log( foo ); //出力:Object {}

インスタンスオブジェクトにはコンストラクタ関数のprototypeプロパティが継承されてくる

すべてのオブジェクトインスタンスは、インスタンスを生成したコンストラクタ関数のprototypeプロパティにリンクする秘密のプロパティ __proto__ を持っていて、そこからコンストラクタ関数の prototype プロパティを取得している。

var myArray = ['foo', 'bar'];
console.log( myArray.hasOwnProperty('join') ); // 出力: false myArray自身はjoinプロパティは持たない
// が、しかし
console.log( myArray.join() ); // 出力: "foo,bar" 機能する。
// myArray.join は実際には Array.prototype.join で定義されている!

参考に、この際のmyArrayオブジェクトを Chrome Developer Tools で展開するとこんな感じ

f:id:AMANE:20140627003940p:plain

joinも居ますね。

さらに、上記コンストラクタ関数の prototype プロパティは、Objectオブジェクトのprototypeプロパティを参照する

// 上の続き, Chrome の場合
myArray.__proto__ === Array.prototype; // 出力: true
myArray.__proto__.__proto__ === Object.prototype; // 出力: true

Object.prototype は、hasOwnProperty()isPropertyOf() といったすべてのオブジェクトに共通の基本メソッドを持っている。

参考に、先ほどの myArray オブジェクトを、Chrome Developer Tools で更に展開するとこんな感じ

f:id:AMANE:20140628142704p:plain

in 演算子

hasOwnProperty() はプロトタイプチェーンをさかのぼらずに自分自身が持っているかをチェックしますが、 in 演算子は、すべてのプロトタイプチェーンを走査します。

使い方

var myObject = {foo: 'bar' };
console.log( 'foo' in myObject ); // 出力:true

console.log( 'toString' in myObject ); // 出力:true

for-in ループ + hasOwnProperty で 自身のプロパティのみ列挙

// クラスの定義
var MyClass = function() {
  this.hoge = 'hohoge';
}
MyClass.prototype.fuga = 'fufuga'; // プロトタイプのプロパティとして追加する

// インスタンスの作成
var myObj = new MyClass();

// フィルタ無し
for( var key in myObj ) {
  console.log( key );  //出力: hoge fuga
}

// プロトタイプチェーンから継承されたプロパティは除いて列挙するためのフィルタを挟む
for( var key in myObj ) {
  if( myObj.hasOwnProperty( key ) ) {
    console.log( key ); // 出力: hoge のみ
  }
}

ホストオブジェクト window と DOMを解釈している window.document

ブラウザでJavaScriptを実行する際のホストオブジェクトwindowは、開いているページそのもの(DOM)やイベントに関連するプロパティを保持している。

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>開眼!JavaScript</title>
  <style type="text/css">
    p {
      color: pink;
    }
  </style>
</head>
<body>
  <h1>開眼!</h1>
  <p>JavaScript</p>
</body>
</html>

みため

f:id:AMANE:20140628151929p:plain

window.document オブジェクトを見てみる。ページを開いた状態でDeveloper Tools の Sources タブ中、 Watch Expressions に window.document を記入して展開すると以下の様な感じで、ウェブページがjs上でオブジェクトとして解釈されていることが分かる

f:id:AMANE:20140628152302p:plain

ホストオブジェクトの存在は、JavaScript標準とは関係が無く、ブラウザが独自に提供している機能である。

Underscore.js は便利

Underscore.js

つづく

これでようやく2章ぶん。続きます。