記事

javascript - からnewを取り除いてみる

1/2
giveするイベントも終わったところで、takeしたいと思います。
JavaScriptから、newを。
だぐらす「すべてのnewを、生まれる前に消し去りたい」

newはなぜ危険か



"JavaScript: the Good Parts"も言葉では指摘していますが、実演まではしていないので以下で改めて。

(function(){

var o,
    /* なんの変哲もないコンストラクター */
    Point = function Point(x, y){
        this.x = x;
        this.y = y;
    };
try{
    o = new Point(3, 4);
    p(JSON.stringify(o));   /* {"x":3,"y":4} */
    p(x); p(y);             /* exception thrown */
}catch(e){
    p(e);                   /* ReferenceError: Can't find variable: x */
}
try{
    o =  Point(3, 4);       /* new を忘れると… */
    p(JSON.stringify(o));   /* undefined    // なんで? */
    p(x); p(y);             /* 3; 4;        // なんでなんで? */
}catch(e){
    p(e);                   /* ここにはこない */
}

try{                        /* clean it up */
    delete this.x;
    delete this.y;
    p(x); p(y);
}catch(e){
    p(e);                   /* ReferenceError: Can't find variable: x */
}

})();


要するに、newをつけ忘れてコンストラクターを呼び出すと、thisがグローバルネームスペースを指しているので知らないうちにグローバル変数を定義したり上書きしたりしちゃうということなのですね。
Pythonistas にしたら new なんてつける方がおかしいですし、Perl Mongers や Rubyists もConstructor.new() と書けない点がひっかかります。

安全なコンストラクターの書き方



このthisが何を指し示すかを調べれば、newをつけようがつけまいがnewしてくれるコンストラクターを作れます。

(function(){

var o,
    Point = function Point(x, y){
        if (this instanceof Point){ /* new されたらこっち */
            this.x = x;
            this.y = y;
        }else{
            return new Point(x, y); /* されなかったら改めてnew */
        }
    };
try{
    o = new Point(3, 4);
    p(JSON.stringify(o));   /* {"x":3,"y":4} */
    p(x); p(y);             /* exception thrown */
}catch(e){
    p(e);                   /* ReferenceError: Can't find variable: x */
}
try{
    o =  Point(3, 4);       /* 今度はつけ忘れても… */
    p(JSON.stringify(o));   /* {"x":3,"y":4} */
    p(x); p(y);             /* exception thrown */
}catch(e){
    p(e);                   /* ReferenceError: Can't find variable: x */
}

})();



でもこんなひちめんどくさいこといちいちやらなければならないのでしょうか?

newを自作してみる


それにあたっては、newが何をしているかを改めて確認しておきましょう。
new - MDN

When the code new foo(...) is executed, the following things happen:

  1. A new object is created, inheriting from foo.prototype.
  2. The constructor function foo is called with the specified arguments and this bound to the newly created object. new foo is equivalent to new foo(), i.e. if no argument list is specified, foo is called without arguments.
  3. The object returned by the constructor function becomes the result of the whole new expression. If the constructor function doesn't explicitly return an object, the object created in step 1 is used instead. (Normally constructors don't return a value, but they can choose to do so if they want to override the normal object creation process.)
このとおりのことをする関数は自作できるでしょうか?
これだと、うまく行きません。

var newer = function(cf, args){
  var seed = {};              /* A new object is created */
  seed.constructor = cf;      /* inheriting from foo.prototype */
  return cf.apply(seed, args) /* The constructor function foo is called... */ 
      || seed;                /* If the constructor function doesn't 
                               * explicitly return an object, 
                               * the object created in step 1 is used 
                               * instead.
                               */ 
};

var o,
    Point = function Point(x, y){
        this.x = x;
        this.y = y;
    };
Point.prototype.fromOrigin = function(){
    return Math.sqrt(this.x*this.x + this.y*this.y)
};
try{
    o = newer(Point, [3, 4]);
    p( JSON.stringify(o) );     /* 一見うまく行っているんだけど… */
    p( o instanceof Point );    /* うーん、だめだ */
    p( o.fromOrigin() );        /* 爆発しろ */
}catch(e){
    p(e);
}


トピックス

  1. 一覧を見る

ランキング

  1. 1

    BLOGOSサービス終了のお知らせ

    BLOGOS編集部

    03月31日 16:00

  2. 2

    なぜ日本からは韓国の姿が理解しにくいのか 識者が語る日韓関係の行方

    島村優

    03月31日 15:41

  3. 3

    「いまの正義」だけが語られるネット社会とウェブ言論の未来

    御田寺圭

    03月31日 10:09

  4. 4

    カーオーディオの文化史 〜ドライブミュージックを支えた、技術の結晶たち〜

    速水健朗

    03月30日 16:30

  5. 5

    BLOGOS執筆を通じて垣間見たリーマンショック後10年の企業経営

    大関暁夫

    03月31日 08:27

ログイン

ログインするアカウントをお選びください。
以下のいずれかのアカウントでBLOGOSにログインすることができます。

コメントを書き込むには FacebookID、TwitterID のいずれかで認証を行う必要があります。

※livedoorIDでログインした場合、ご利用できるのはフォロー機能、マイページ機能、支持するボタンのみとなります。