Chapter 3. Literals and Constructors
2011/01/02(日) 22:05 Javascript親記事へこのエントリーをはてなブックマークに追加

Object Literal

オブジェクト
// 空のオブジェクトを作る
var dog = {};
//プロパティ、メソッドを追加したり
dog.name = "Benji";
dog.getName = function () {
    return dog.name;
};
//プロパティやメソッドを削除したり
delete dog.name;

The Object Literal Syntax

オブジェクトリテラルのルール
  • {...}で囲う
  • 区切りに,を入れる。ただしIEは終端に,があるとエラーにある
  • プロパティネーム : プロパティ値
  • 最後の}の後に;を入れる

Objects from a Constructor

オブジェクトはリテラル以外にもnew Object()することで作れるがリテラルから作った方がよい。
// リテラルを使った方法
var car = {goes: "far"};
// built-inコンストラクタを使った方法
// warning: this is an antipattern
var car = new Object();
car.goes = "far";
リテラルの方がよい理由としては、リテラルで書いた方が短く書くことができる。
また他の理由として、オブジェクトはシンプルで可変なハッシュであり、"レシピ"(クラス)から作られた何かではないということを強調できるからだ。
他には、Objectコンストラクタにたいしてリテラルはスコープ解決の手間がないため早い。
後はbuilt-inコンストラクタを書き換えちゃったりできる場合があるからとか、JavaScriptベストプラクティス30選-jsEdu | Web scratchでもリテラルの方がいいって話書いた。


JavaScript では Object = ... とか Array = ... で書き換えがありますしね。(そんな状況は無いと思いますけど)
Twitter / edvakf: @azu_re あと JavaScript では O ...




Object Constructor Catch

new Object()コンストラクタを使う理由はないから、素直にリテラルを使いましょう。
また、Object()コンストラクタから作ったオブジェクトは予想とは違ったコンストラクタプロパティを返すことがあります。
下のような場合はコンストラクタプロパティがObjectなのはいいですが、
// an empty object
var o = new Object();
console.log(o.constructor === Object); // true
new Object(引数);として引数をいれてオブジェクトを作ったときに問題が発生します。
下のように予想しないコンストラクタプロパティになることがあり得るため、new Object()コンストラクタを使わないようにしましょう。
// a number object
var o = new Object(1);
console.log(o.constructor === Number); // true
// a boolean object
var o = new Object(true);
console.log(o.constructor === Boolean); // true

Custom Constructor Functions

var adam = new Person("Adam");
としたときにPersonコンストラクタ関数ではどういうことが起きているか
var Person = function (name) {
    // {}を使ってオブジェクトを作る
    // var this = {};
    // メソッドやプロパティを追加する
    this.name = name;
    this.say = function () {
        return "I am " + this.name;
    };
    // return this;
};
prototype拡張した時も同じような感じで、thisの部分で拡張済みのオブジェクトが返ってくる
// var this = {} が
// var this = Object.create(Person.prototype); に変わるような感じだ

Constructor’s Return Values

下のような場合、thisを拡張したオブジェクトが返ってくると予測するが、実際にはthatのオブジェクトが返ってくる。
var Objectmaker = function () {
    this.name = "This is it";
    // creating and returning a new object
    var that = {};
    that.name = "And that's that";
    return that;
};
// test
var o = new Objectmaker();
console.log(o.name); // "And that's that"
これはコンストラクタ関数のreturnで返す値がオブジェクトだったらインスタンスの代わりにそのオブジェクトを返すことになっているからだ。
JavaScript の new 演算子の意味: Days on the Moonの下の部分で判定しているのがわかる。
return (result instanceof Object) ? result : instance;

Patterns for Enforcing new

コンストラクタ関数を実行する時にnewを忘れると大変。
thisがグローバル(window)を指してしまって好ましくないし、そもそもエラーになる場合もある。
ECMAScript 5のstrict modeではthisがグローバルオブジェクトを示すことはないらしい。

Naming Convention

前のNaming Conventionsでも言ったけど、コンストラクタ関数は大文字から始めるとわかりやすい。

Using that

コンストラクタ関数内でthis使わないで、var that = {};作ってそれを拡張してreturn that;をする方法。
returnでオブジェクトを返せばthisを使わなくてもnewしたときにインスタンス化できる。
function Waffle() {
    var that = {};
    that.tastes = "yummy";
    return that;
}
またthatを使わなくてもオブジェクトを返せばいいので、下のようなコンストラクタも可能
function Waffle() {
    return {
        tastes: "yummy"
    };
}
このようにthatを使えば、newを忘れていても同じ結果が返ってくる。
var first = new Waffle(),
    second = Waffle();
console.log(first.tastes); // "yummy"
console.log(second.tastes); // "yummy"
だけど、prototypeへのリンクはどこか行ってしまうのが欠点でもある。
なので時と場合によって使い分けるか、次のようにする。

Self-Invoking Constructor

new してもしなくても同じインスタンスになるようにするには、コンストラクタ関数の内部で自分自身をnewする方法がある。
function Waffle() {
    if (!(this instanceof Waffle)) {
        return new Waffle();
    }
    this.tastes = "yummy";
}
この方法はnewつけないでWaffleを呼んだときは二回Waffleしてしまってるので、下のようにするとよりよいかも。(書籍には書いてなかった)
function Waffle() {
    if (this instanceof Waffle) {
        this.tastes = "yummy";
    }else{
        var Traits = function(){}
        Traits.prototype = Waffle.prototype;
        var obj = new Traits();
        Waffle.apply(obj, arguments);
        return obj;

    }
}
Waffle.prototype.wantAnother = true;
// testing invocations
var first = new Waffle(),
    second = Waffle();
console.log(first.tastes); // "yummy"
console.log(second.tastes); // "yummy"
console.log(first.wantAnother); // true
console.log(second.wantAnother); // true
arguments.calleeを使えばコピペで書けるけどstrict modeでは許されないのでハードコーディングしている。

Array Literal

Objectと同じようにArrayも二つの書き方がある。
var a = new Array("itsy", "bitsy", "spider");
// リテラル
var a = ["itsy", "bitsy", "spider"];
console.log(typeof a); // Arrayもまた"object"
console.log(a.constructor === Array); // true

Array Constructor Curiousness

new Arrayにはいくつかのトラップがある。
new Array(数値);で数値を入れると、その数だけundefinedで埋められたオブジェクトが返ってくる。
var a = new Array(3);
console.log(a.length); // 3
console.log(typeof a[0]); // "undefined"
そのため、new Array()に;小数点などを渡すとエラーになる。
var a = new Array(3.14); // RangeError: invalid array length
console.log(typeof a); // "undefined"
ただ、これにも利用法があり下のように、n-1個分の何かが欲しいときに利用できる。
new Array(256).join(' ');// 255個のホワイトスペース

Check for Array-ness

先ほど言ったようにArrayもまたObjectである。
console.log(typeof [1, 2]); // "object"
そのためArrayかを判定するためにの方法を探すときに、lengthプロパティをみる方法が浮かぶがlengthはArray.prototype.slice.call(items);など作ったArrayライクなオブジェクトにも存在するため適切ではない。
ECMA5ではArraryを判定する Array.isArray()というメソッドが用意されているが、他の方法として Object.prototype.toString()を利用する方法がある。
if (typeof Array.isArray === "undefined") {
    Array.isArray = function (arg) {
        return Object.prototype.toString.call(arg) === "[object Array]";
    };
}
Object.prototype.toString()でstringで返ってきたもので判定する。

JSON

JavaScript Object Notationについて。

Working with JSON

JSONはevalでも得られるが、セキュリティ的な観点などからJSON.parseを利用する方がよい。
ES5ではネイティブでJSONを扱えるが、ネイティブにJSONを扱えない場合はライブラリを利用する。

Regular Expression Literal

正規表現もコンストラクタとリテラルにより利用できる。

Regular Expression Literal Syntax

二つの違いは動的生成new RegExp()と静的生成//
リテラルの場合はパース時に一度だけ正規表現オブジェクトが作られる。
そのためES3の正規表現リテラルでは同じオブジェクトが使い回されるためちょっとバグっぽい動作をすることがある。
function getRE() {
    var re = /[a-z]/; // 正規表現オブジェクト 
    re.foo = "bar";
    return re;
}
var reg = getRE(),
    re2 = getRE();
console.log(reg === re2); // true => 同じオブジェクトが返ってきてる。
reg.foo = "baz";
console.log(re2.foo); // "baz" <-同じオブジェクトを参照しているため
そのため、ループなどで正規表現リテラルを使うときは気をつける必要がある。
この動作はES5では毎回新しいオブジェクトを作るように変更されているため問題ない。

Primitive Wrappers

JavaScriptではPrimitive型に number, string, boolean, (null, undefined)があるが、この3つをラップしたオブジェクトをprimitive wrapper objectsと呼び、これはbuilt-inコンストラクタである Number(), String(), Boolean()によって作成できる。
// a Number object
var nobj = new Number(100);
console.log(typeof nobj);
// "object" primitive wrapper objectが返る
このwrapperオブジェクトは便利なプロパティやメソッド持っているがそれはprimitiveなものでも同じことができる。

Error Objects

built-inのエラーコンストラクタにはError(), SyntaxError(),TypeError()のようなものがある。
これらのオブジェクトはnameとmessageプロパティを持っている。
そして、エラーをthrowするときには任意のものを投げることができるので自分自身でエラーオブジェクトを作成することも可能だ。
try {
    // throw an error
    throw {
        name: "MyErrorType", // custom error type
        message: "oops",
        extra: "This was rather embarrassing",
        remedy: genericErrorHandler // なんかの関数
    };
} catch (e) {
    alert(e.message); // "oops"
    // gracefully handle the error
    e.remedy(); // calls genericErrorHandler()
}
throwはオブジェクト以外にもstringやnumberなども投げられる。

Summary

new Object()コンストラクタを使わないでリテラルを使う理由がかなり詳しく書かれていてすばらしい。
この著者もthat派だった。(エディタではselfだけじゃなくてthatも色をつけよう)

名前:  非公開コメント   

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