[JavaScript] newは大事だよ!

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[JavaScript] newは大事だよ!

JavaScriptのnewって何?一体何なの?という話 - 愛と勇気と缶ビール

クラスってものはJavaScriptにはないはずなのに、 new ClassName();と書くとあたかもClassNameクラスのオブジェクトのインスタンスが生成され、それが返ってくるかのような挙動をしている。

これは気持ち悪い。言語仕様としてはクラスは本来存在しないのに、クラスのようなものが導入されている。まともな神経を持った人間なら、一体 new演算子って何なの?という疑問を持つのが当たり前である。

こんな扱いを受けているnewがあまりにも不憫で涙した><

newはprototype-basedなJavaScriptを書くための唯一の手段[1]で、真にJavaScriptらしいコードを書くためには欠かすことができないものなのに><

ということで、newを擁護するためのエントリーをポスト。

そもそもnewとは

new演算子の動作については引用元のエントリーでも紹介されてて、thisがどうのとかコンストラクターがどうのとかは言われているけど、newの本質はそこじゃないよ><

newの本質はprototypeを受け継いだ新しいオブジェクトを作ること。

var myPrototype = {
  name: "myPrototype",
  sayHello: function() {
    return "Hello. This is " + this.name + ".sayHello().";
  }
};

function Foo() {}
Foo.prototype = myPrototype;

var newed = new Foo();
console.log(newed.sayHello());  // "Hello. This is myPrototype.sayHello()."

var called = Foo();
console.log(called.sayHello()); // TypeError: called is undefined

↑のコードで、newして生成したオブジェクトはFoo.prototype (== myPrototypeオブジェクト) のプロパティを受け継いでいるのがわかる。

JavaScriptのnew演算子は、既存のオブジェクト (プロトタイプ) をベースに、新しいオブジェクトを生成するための演算子なんだよ! (そのついでにコンストラクターを使って新しいオブジェクトを初期化してるだけ!)

newがやってること

知っての通り、JavaScriptのオブジェクトはPrototype Chainと呼ばれる階層構造になっている。

例えば↑のnew Foo()であれば、↓のような2段重ね[2]のオブジェクトになっていて、上から順番にプロパティを探索していって見つかった値を返している。

name sayHello
new Foo() - -
Foo.prototype "myPrototype" function

new演算子はこのPrototype Chainの先頭にオブジェクトを追加するための唯一の手段[3]で、モダンなオブジェクト指向JavaScriptプログラムではPrototype Chainを使ってオブジェクト指向的な機能を実装するから、new演算子に対する理解は必須。

「JavaScriptにクラスはないのにnewがあるのはおかしい」

そんなふうに考えていた時期が俺にもありました (遠い目)。

クラスベースのオブジェクト指向言語ではクラスを雛形にして新しいオブジェクトを生成するのに対して、プロトタイプベースのオブジェクト指向言語では既存のオブジェクト (プロトタイプ) を雛形にして新しいオブジェクトを生成する。

確かにJavaScriptはクラスベース言語の皮をかぶってるところがあるけど、new演算子は雛形から新しいオブジェクトを生成する演算子なんだと考えれば、JavaScriptにnew演算子があったとしても全然不思議じゃないんだよ!

まとめ

newかわいいよnew

蛇足

newしないとinstanceofが使えなくなるのが地味に痛いよね。

function Bar() {}
console.log(new Bar() instanceof Bar);  // true

function Baz() { return {}; }
console.log(Baz() instanceof Baz);      // false

追記

ピンと来ない人のために、newを使ったprototype的な継承とnewを使わないコンストラクタ的な継承のサンプルコードとPrototype Chain表を下記。

↓newを使った場合

var Person = function(name) {
  this._name = name;
};

Person.prototype = {
  getName: function() { return this._name; },
  toString: function() { return this.getName(); }
};

var AgedPerson = function(name, age) {
  Person.call(this, name);
  this._age = age;
};

AgedPerson.prototype = new Person();
AgedPerson.prototype.getAge = function() { return this._age; };
AgedPerson.prototype.toString = function() {
  return this.getName() + " (" + this.getAge() + ")";
};

var a = new Person("Alice"), b = new AgedPerson("Bob", 78);
console.log(a.toString(), b.toString());  // "Alice" "Bob (78)"
_name _age getName getAge toString
new AgedPerson("Bob", 78) "Bob" 78 - - -
AgedPerson.prototype - - - function function
Person.prototype - - function - function
Object.prototype - - - - function

↓newを使わない場合

var createPerson = function(name) {
  return {
    _name: name,
    getName: function() { return this._name; },
    toString: function() { return this.getName(); }
  };
};


var createAgedPerson = function(name, age) {
  var obj = createPerson(name);
  obj._age = age;
  obj.getAge = function() { return this._age; };
  obj.toString = function() {
    return this.getName() + " (" + this.getAge() + ")";
  };
  return obj;
};

var a = createPerson("Alice"), b = createAgedPerson("Bob", 78);
console.log(a.toString(), b.toString());  // "Alice" "Bob (78)"
_name _age getName getAge toString
createAgedPerson("Bob", 78) "Bob" 78 function function function
Object.prototype - - - - function

↑の例ではアウトプットは同じだけど、Prototype Chainの状態が全然違う。一度生成したオブジェクトをプロトタイプとして再利用している分、前者の例のほうが速かったり効率がよかったりするんだけど、それ以外にも下のような例では動作が全然違う。

console.log(new AgedPerson("Bob", 78) instanceof AgedPerson);   // true
console.log(new AgedPerson("Carol", 75) instanceof Person);     // true

console.log(createAgedPerson("Bob", 78) instanceof AgedPerson); // false
console.log(createAgedPerson("Carol", 75) instanceof Person);   // false
console.log(new Person("Alice").getName === new Person("Bob").getName);             // true
console.log(new Person("Alice").getName === new AgedPerson("Bob", 78).getName);     // true

console.log(createPerson("Alice").getName === createPerson("Bob").getName);         // false
console.log(createPerson("Alice").getName === createAgedPerson("Bob", 78).getName); // false

前者の例ならではの↓のような芸当も。

var debug = true;
if (debug) {
  var tmp = Person.prototype.getName;
  Person.prototype.getName = function() {
    console.log("Begin: Person.prototype.getName()");
    var result = tmp.call(this);
    console.log("End: Person.prototype.getName() returning '" + result + "'");
    return result;
  }
}

(new AgedPerson("Bob", 78)).toString();
// Begin: Person.prototype.getName()
// End: Person.prototype.getName() returning 'Bob'

JavaScriptを書くならnewとPrototypeは積極的に使っていくべきだよねー

註釈

  1. ^ 今日現在普及しているECMAScript 3rd Editionでの話。最新のECMAScript 5th Editionではnew以外にもprototypeを使う方法がいくつか用意されているけど、library writerじゃなければ普通は使わない。

  2. ^ より正確には↓のような3段重ね。{ }new Object()のシンタックスシュガーだから、↑のサンプルコードでnewは二回呼ばれてるんだよね。

    name sayHello
    new Foo() - -
    Foo.prototype "myPrototype" function
    Object.prototype - -
  3. ^ しつこいようだけどECMAScript 3rd Editionでの話。

スポンサーサイト

関連記事

トラックバック URL

http://liosk.blog103.fc2.com/tb.php/202-d68bcb13

トラックバック

コメント

JavaScriptを勉強し始めたばかりの者ですが、
newの存在に対しての疑問が解決し、
とても勉強になりました。
ありがとうございます!
  • 2013-06-13
  • by 通りすがり
  • id:EvmDRqhQ
  • 編集
new に違和感があるのは、関数型言語の側面が強いJavascriptでFunctionを取り回す際に、構文(ステートメント)が紛れ込んでいるからだからだと思います。

現に、この記事で解説されているように、関数のみで実現できることが、newという構文では動作が全然違うという事が起きるわけで、統一性がなく混乱を招く要素であるのはそのとおりだと思います。
  • 2013-07-30
  • by ken
  • id:/aF5ZJNE
  • 編集
「newうぜー」「newって何だよ」と思っていたけど、今後は可愛がります。
  • 2013-08-17
  • by 寄り道
  • id:-
僕は、newが気になったのでなんとか実装してみようとしてみて、
次の動作をnewがすることを知りました。
①[[Prototype]]にコンストラクタのprototypeを持つオブジェクトを作る
②もしコンストラクタがオブジェクトを返せば①の代わりにそれを返す

そして実装してみようとしました。
「Function.prototype.new=function(){
var obj=Object.create(this.prototype),
rtn=this.apply(obj,arguments);
return rtn instanceof Object?rtn:obj
}」
結果、ほとんどの場合は成功しましたが、ネイティブ(Dateなど)では失敗しました。
どうやらネイティブのコンストラクタはnewされているか知ることができるようです。
  • 2013-12-21
  • by 名無しのにゅうす
  • id:5bwaSGmE
  • 編集

コメントの投稿

お名前
コメント
編集キー
 
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。