Chapter 16. JavaScript Objects
2010/09/13(月) 24:20 Javascript親記事へこのエントリーをはてなブックマークに追加

JavaScriptはprototypal inheritanceを元にした言語。
クラスベースとは違い、最初にクラスを作ることを心配しなくて良い。
オブジェクトは再利用性が高い。

16.1 Defining a Basic JavaScript Object

newとthisキーワードについて。
オブジェクトの初期化(構築)を行う関数をコンストラクタ関数。
コンストラクタ関数にあるthisはインスタンス(実体)を示す。
// コンストラクタ関数
function Tune (song, artist) {
   this.title = song;
   this.artist = artist;
   this.concat=function() {
      return this.title + "-" + this.artist;
   }
}
window.onload=function() {
   var happySong = new Tune("Putting on the Ritz", "Ella Fitzgerald");
   // print out title and artist
   alert(happySong.concat());
}
コンストラクタをnewした時、thisは空のオブジェクト{}を示すので。var happySong = new Tune("Putting on the Ritz", "Ella Fitzgerald");は以下のような感じになる。
var happySong = {
   title  : song,
   artist : artist,
   concat : function() {
      return this.title + "-" + this.artist;
   }
}
newを付けないでTuneを呼んだ場合は、コンストラクタ内のthisはwindowを示すので、windowにメソッドやプロパティが追加されてしまう。
// treating Tune like a function
Tune("the title", "the singer");
alert(window.concat()); // lo and behold,
                        // "the title the singer" prints out
これのthisが示すものをもっとわかりやすくするとwindow.Tuneを呼び出してるので、thisが示すインスタンスはwindowとなっている。
window.Tune("the title", "the singer");
alert(window.concat()); 

16.3 Expanding Objects with prototype

prototypeの追加方法

16.4 Adding Getter/Setter to Objects

getterとsetterの使い方
function Tune() {
    var artist;
    var song;
    this.__defineGetter__("artist", function() {
        return artist
    });
    this.__defineSetter__("artist", function(val) {
        artist = "By: " + val
    });
    this.__defineGetter__("song", function() {
        return "Song: " + song
    });
    this.__defineSetter__("song", function(val) {
        song = val
    });
}
window.onload = function() {
    var happySong = new Tune();
    happySong.artist = "Ella Fitzgerald";
    happySong.song = "Putting on the Ritz";
    alert(happySong.song + " " + happySong.artist);// Song: Putting on the Ritz By: Ella Fitzgerald
}c

16.5  Inheriting an Object’s Functionality

constructor chainingについて。
(正直この継承は手順が複雑すぎるので使いにくい
function oldObject(param1) {
   this.param1 = param1;
   this.getParam=function() {
      return this.param1;
   }
}
function newObject(param1,param2) {
   this.param2 = param2;
   this.getParam2 = function() {
      return this.param2;
   }
   oldObject.apply(this,arguments);// oldに引数を渡して実行
   // thisのコンテキストはparam2と同じになる
   /* ここにoldObjectを書くのと大体同じ
   this.param1 = param1;
   this.getParam=function() {
      return this.param1;
   }
   */  
   this.getAllParameters=function() {
      return this.getParam() + " " + this.getParam2();
   }
}
   newObject.prototype = new oldObject();// 引数はない
   var obj = new newObject("value1","value2");// 一度にまとめて引数を渡したい
   // prints out both parameters
   alert(obj.getAllParameters());

16.6 Extending an Object by efining a New Property

*こんなとこを読むなら、ES5, Property Descriptor解説 - 枕を欹てて聴くを読みましょう。

コンストラクタ関数を変更しないで、新しいプロパティを追加するには
Object.definePropertyを使う。(ECMAScript 5)
まあ普通にドット演算子でプロパティ作ってやることもできますが、definePropertyはプロパティの性質(ここではPropertyDescriptorとしておきます)についても決定できる。
someObject.newProperty = "somevalue";
definePropertyの場合
Object.defineProperties(newBook, {
     "stock": {
        value: true,
        writable: true,
        enumerable: true,
     },
     "age": {
        value: "13 and up",
        writable: false
     }
   });
PropertyDescriptor
  • writable
trueならプロパティ値は変更できる
  • configurable
trueならプロパティは変更削除できる。
falseだとプロパティは削除できなくて、PropertyDescriptor
writable, enumerable, configurable の値を変更できない(プロパティ値の変更はできる)
=>ECMA3 の DontDelete
  • enumerable
trueならイテレート可能(for inで列挙される)
また、defineProperty内でgetterやsetterを決めたときはwritableの設定をすることができません。
 Object.defineProperty(TechBook, "category", {
    get: function () { return category; },
    set: function (value) { category = value; },
    value: "O'Reilly",//デフォルトの値
    enumerable: true,
    configurable: true});
var newBook = new TechBook(...);
newBook.publisher="O'Reilly";
設定されているenumerableなどのPropertyDescriptorの情報は Object.getOwnPropertyDescription を使う事で取得できます。
var propDesc = Object.getOwnPropertyDescriptor(newBook,"category");
alert(JSON.stringify(val)); // {"enumerable":true,"configurable":true}
多少違うけど

16.7  Enumerating an Object’s Properties

オブジェクトがどんなプロパティを持っているか知りたい。
今まではfor...inループを使ってたりしたけど、ECMA5ではObject.keysというメソッドが存在する。
alert(Object.keys(obj).join(", "));
// for ( var prop in obj ) if ( obj.hasOwnProperty( prop ) ) と似たような感じで
getOwnPropertyNamesというメソッドはObject.keysと似ているが、本来なら列挙されないプロパティ(配列のlengthなど)も取得できる。
var props = Object.getOwnPropertyNames(obj);

16.8 Preventing Object Extensibility

ECMA5にはObject.preventExtensionsというオブジェクトの拡張をロックするメソッドがある。
つまりオブジェクトに新しいプロパティやメソッドを追加させなくさせるメソッド
"use strict";
var Test = {
    value1 : "one",
    value2 : function() {
      return this.value1;
    }
};
try {
   Object.preventExtensions(Test);
   // the following fails, and throws an exception in Strict mode
  Test.value3 = "test";
} catch(e) {
   alert(e);
}
既にロックされているかは Object.isExtensibleメソッドで確認できる。
if (Object.isExtensible(obj)) {
   // extend the object
}
(Strict modeどうなるんですかねー

16.9  Preventing Object Additions and Changes to Property Descriptors

Object.preventExtensionsは新たに追加するのを防止するメソッドだったが、PropertyDescriptorの変更をできなくさせるメソッドがObject.sealである。
Object.preventExtensions + configurable:false
"use strict";
 var Test = {
    value1 : "one",
    value2 : function() {
      return this.value1;
    }
  }
  try {
     // オブジェクトをfreezeする
     Object.seal(Test);
     // プロパティは変更できる
     Test.value2 = "two";
     // 追加はできない。 throw an error in Strict Mode
     Test.newProp = "value3";
      // PropertyDescriptorはへんこうできない
     Object.defineProperty(Title, "category", {
         get: function () { return category; },
         set: function (value) { category = value; },
         enumerable: true,
         configurable: true});
   } catch(e) {
      alert(e);
   }
同じようにif (Object.isSealed(obj)) で既に適応されてるかを判定できる。

ECMA3.1の[[Flexible]]はECMA5だと[[configurable]]?

16.10 Preventing Any Changes to an Object

プロパティの追加、変更、削除、PropertyDescriptorの変更などすべてできなくさせるにはObject.freezeメソッドを使う。
"use strict";
  var Test = {
    value1 : "one",
    value2 : function() {
      return this.value1;
    }
  }
try {
  // freeze the object
  Object.freeze(Test);
  // さっきとは違いここでもエラーになる。変更も不可
  Test.value2 = "two";
   // so would the following
   Test.newProperty = "value";
   // and so would the following
   Object.defineProperty(Title, "category", {
      get: function () { return category; },
      set: function (value) { category = value; },
      enumerable: true,
      configurable: true});
} catch(e) {
   alert(e);
}
freezeするとno additions,no changes to existing propertiesになる。
if (Object.isFrozen(obj)) ...で確認。

つまり、Object.seal()、Object.freeze()、Object.preventExtensions() は Object.defineProperty() の糖衣構文 (syntax sugar) だったのだ。そりゃ、機能が制限されていて当然である。
http://end-of-file.net/blog/2008-08.html#date-2008-08-21

16.11 One-Off Objects and Namespacing Your JavaScript

ネームスペースを設けて衝突を防ぐ
var jQuery = {
   //プロパティとかメソッドとか
}
みたいな話です。
グローバルなものは一つか二つぐらいにするってやつ。

16.12  Rediscovering “this” with Prototype.bind

ECMAScript 5にはFunctionオブジェクトにbindというメソッドがあり、thisを設定できる。
   window.name = "window";
   var newObject = {
      name: "object",
      sayGreeting: function() {
            alert("Now this is easy, " + this.name);
            nestedGreeting = function(greeting) {
              alert(greeting + " " + this.name);
            }.bind(this);
            nestedGreeting("hello");
       }
   }
   newObject.sayGreeting("hello");

// 今まではprototype.jsのようにprottotype拡張してた
Function.prototype.bind = function(scope) {
  var _function = this;
  return function() {
    return _function.apply(scope, arguments);
  }
}
特にsetTImeoutとかの時にスコープを設定するのに使う事多い。

16.13 Chaining Your Object’s Methods

メソッドチェーンについて。
メソッドチェーンするにはthisを返すのが定説。
function Book(title, author) {
    var title = title;
    var author = author;
    this.getTitle = function() {
        return "Title: " + title;
    }
    this.getAuthor = function() {
        return "Author: " + author;
    }
    this.replaceTitle = function(newTitle) {
        var oldTitle = title;
        title = newTitle;
    }
    this.replaceAuthor = function(newAuthor) {
        var oldAuthor = author;
        author = newAuthor;
    }
}

function TechBook(title, author, category) {
    var category = category;
    this.getCategory = function() {
        return "Technical Category: " + category;
    }
    Book.apply(this, arguments);
    this.changeAuthor = function(newAuthor) {
        this.replaceAuthor(newAuthor);
        return this;// ここでthisを返してる
    }
}
window.onload = function() {
    try {
        var newBook = new TechBook("I Know Things", "Shelley Powers", "tech");
        alert(newBook.changeAuthor("Book K. Reader").getAuthor());//changeAuthorではnewBookが返るのでチェーンできる
    } catch (e) {
        alert(e.message);
    }
}
メソッドチェーンはコード量が減ったり、まとめて処理を行えるので便利だけどデバッグがしににくなったり読むのに慣れが必要。


短く書き捨てる場合はメソッドチェーンのメリットは光ますが、メソッドチェーンはデバッグを難しくします。 「数分でbugfixしなければ殺される」風味な状況で「メソッドチェーンの部分がネックでデバッグが進まない」という悲惨な状況に10年前遭遇したことがあります。


Twitter / uupaa: 短く書き捨てる場合はメソッドチェーンのメリットは光ま ...



ブラウザ間でちゃんと互換性があれば、かなりかたーいものも書けるようになるはずなんですよね。まだ未実装のブラウザの方が多い話。

名前:  非公開コメント   

  • TB-URL  http://efcl.info/adiary/078/tb/