すでにインスタンス化されているJavaScriptオブジェクトのプロトタイプを設定する方法は?


106

fooJavaScriptコードにオブジェクトがあるとします。 foo複雑なオブジェクトであり、別の場所で生成されます。fooオブジェクトのプロトタイプを変更するにはどうすればよいですか?

私の動機は、.NETからJavaScriptリテラルにシリアル化されたオブジェクトに適切なプロトタイプを設定することです。

ASP.NETページ内に次のJavaScriptコードを記述したとします。

var foo = <%=MyData %>;

それMyDataが.NET JavaScriptSerializerDictionary<string,string>オブジェクト。

実行時には、これは次のようになります。

var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}];

ご覧のとおりfoo、オブジェクトの配列になります。foo適切なプロトタイプで初期化できるようにしたいと思います。私はないではない変更するObject.prototypeArray.prototype。これどうやってするの?


既存のプロトタイプに追加しますか、それとも新しいプロトタイプに切り替えますか?
SLaks、2011

プロトタイプに変更を加えるということですか、または実際にプロトタイプを変更して、あるプロトタイプを切り替えて別のプロトタイプに置き換えます。後者のケースが可能かどうかさえわかりません。
James Gaunt、2011

2
明示的なプロトタイププロパティまたは暗黙的なプロトタイプリンクを意味しますか?(これら二つは2つの非常に異なるものです)
サイムVIDAS

:私はもともとこれで心配していたstackoverflow.com/questions/7013545/...
ビビアン・リバー

1
あなたはバックボーンextendやグーグルのことを知っていますgoog.inheritか?多くの開発者は、古いnewコンストラクターを呼び出す前に継承を構築する方法を提供します。これは、私たちが提供される前でありObject.create、オーバーライドを心配する必要はありませんでしたObject.prototype
ライアン、

回答:


114

2012年2月を編集:以下の答えはもはや正確ではありません。__proto__は「標準のオプション」としてECMAScript 6に追加されています。つまり、実装する必要はありませんが、実装する場合は、所定のルールセットに従う必要があります。これは現在未解決ですが、少なくともJavaScriptの仕様の一部として正式に組み込まれる予定です。

この質問は、表面上見えるよりもはるかに複雑であり、Javascriptの内部に関する知識に関しては、ほとんどの人の給与水準を超えています。

prototypeオブジェクトのプロパティは、そのオブジェクトの新しい子オブジェクトを作成するときに使用されます。変更してもオブジェクト自体には反映されず、そのオブジェクトが他のオブジェクトのコンストラクターとして使用された場合に反映され、既存のオブジェクトのプロトタイプを変更しても意味がありません。

function myFactory(){};
myFactory.prototype = someOtherObject;

var newChild = new myFactory;
newChild.__proto__ === myFactory.prototype === someOtherObject; //true

オブジェクトには、現在のプロトタイプを指す内部[[prototype]]プロパティがあります。それが機能する方法は、オブジェクトのプロパティが呼び出されるたびに、オブジェクトで始まり、ルートオブジェクトプロトタイプの後に一致が見つかるか、失敗するまで[[プロトタイプ]]チェーンを上に向かっていきます。これは、JavaScriptがオブジェクトの実行時の構築と変更を可能にする方法です。必要なものを探す計画があります。

__proto__プロパティには、いくつかの実装(今ロット)に存在する:任意のMozillaの実装で、私の知っているすべてのWebKitのものを、いくつか他の人を。このプロパティは内部の[[prototype]]プロパティを指し、オブジェクトの作成後の変更を可能にします。この連鎖ルックアップにより、プロパティと関数は即座にプロトタイプに一致するように切り替わります。

この機能は現在標準化されていますが、JavaScriptの必須部分ではありません。サポートする言語では、コードが「最適化されていない」カテゴリに分類される可能性が高くなります。JSエンジンは、コード、特に頻繁にアクセスされる「ホット」コードを分類するために最善を尽くす必要があり、変更__proto__などの凝った操作を行っている場合、コードはまったく最適化されません。

この投稿https://bugzilla.mozilla.org/show_bug.cgi?id=607863では、現在の実装__proto__とそれらの違いについて具体的に説明しています。それは困難で未解決の問題であるため、すべての実装は異なる方法で実行します。a。)構文b。)ホストオブジェクト(技術的にはDOMはJavascriptの外部に存在します)およびc。)を除き、JavaScriptのすべてが変更可能__proto__です。残りは完全にあなたと他のすべての開発者の手に委ねられているので、なぜあなたは__proto__痛い親指のように突き出るのかを見ることができます。

それを__proto__可能にしないと、他の方法では不可能であることが1つあります。実行時のコンストラクターとは別のオブジェクトプロトタイプの指定です。これは重要な使用例であり、__proto__まだ死んでいない主な理由の1つです。Harmonyの策定において真剣な議論のポイントとなったこと、またはまもなくECMAScript 6として知られるようになることは非常に重要です。作成中にオブジェクトのプロトタイプを指定する機能は、Javascriptの次のバージョンの一部となり、これは__proto__の日を示すベルは正式に番号が付けられています。

短期的には、__proto__それをサポートするブラウザーをターゲットにしている場合に使用できます(IEはサポートしていません。IEはサポートしません)。ES6は2013年まで確定されないため、今後10年間は​​webkitとmozで動作する可能性があります。

Brendan Eich -re:ES5の新しいObjectメソッドのアプローチ

申し訳ありませんが... __proto__オブジェクトの初期化子の使用例(つまり、ES5のObject.createに類似した、まだ到達できない新しいオブジェクト)を除いて、設定可能というのはひどい考えです。私はこれを__proto__12年以上前に設定可能に設計および実装して書いています。

...層別化の欠如が問題です(キーを持つJSONデータを検討してください"__proto__")。さらに悪いことに、可変性は、実装がiloopingを回避するために循環プロトタイプチェーンをチェックする必要があることを意味します。[無限再帰の定数チェックが必要です]

最後__proto__に、既存のオブジェクトを変更すると、新しいプロトタイプオブジェクトの非ジェネリックメソッドが壊れる可能性があります。これ__proto__は、設定されているレシーバー(直接)オブジェクトでは機能しない可能性があります。これは単に悪い習慣であり、一般的に意図的なタイプの混乱の一種です。


優れた内訳!可変プロトタイプとまったく同じではありませんが、ECMA Harmonyはおそらくプロキシを実装するため、キャッチオールパターンを使用して特定のオブジェクトに追加機能をシムできます。
Nick Husher、2011

2
Brendan Eichの問題は、プロトタイピング言語全体のネイティブです。おそらく__proto__を作成non-writable, configurableすると、ユーザーにプロパティを明示的に再構成するように強制することで、これらの懸念が解消されます。結局のところ、悪い習慣は言語自体の機能ではなく、言語の機能の乱用に関連しています。書き込み可能な__proto__は前代未聞ではありません。列挙できない書き込み可能なプロパティは他にも多数あり、危険はありますが、ベストプラクティスもあります。ハンマーの頭は、誰かを傷つけるために不適切に使用できるという理由だけで取り外されるべきではありません。
スイベル2013年

__proto__Object.createはオブジェクトのみを生成し、たとえば関数、シンボル、正規表現、DOM要素、またはその他のホストオブジェクトを生成しないため、変更が必要です。オブジェクトを呼び出し可能にする、または他の方法で特別なものにしたいが、プロトタイプチェーンを変更する場合、設定可能__proto__またはプロキシなしで動かなくなる
user2451227

2
ただし、魔法のプロパティではなくObject.setPrototypeを使用して「__proto__JSON内」の懸念を排除した方がよいでしょう。ブレンダンアイヒの他の懸念は笑える。再帰的なプロトタイプチェーンや、指定されたオブジェクトに対して不適切なメソッドを含むプロトタイプを使用することは、プログラマーエラーであり、言語の誤りではなく、要因であってはなりません__proto__
user2451227 14

1
IE 10はをサポートしています__proto__
kzh 2014


14

constructorオブジェクトのインスタンスでを使用して、オブジェクトのプロトタイプをインプレースで変更できます。これはあなたが求めていることだと思います。

これは、fooどれがのインスタンスであるかを意味しますFoo

function Foo() {}

var foo = new Foo();

次のようbarFooして、のすべてのインスタンスにプロパティを追加できます。

foo.constructor.prototype.bar = "bar";

ここに概念実証を示すフィドルがあります:http : //jsfiddle.net/C2cpw/。古いブラウザがこのアプローチをどのように処理するかははっきりとはわかりませんが、これでかなりうまくいくはずです。

あなたの意図が機能をオブジェクトにミックスインすることであるなら、このスニペットは仕事をするべきです:

function mix() {
  var mixins = arguments,
      i = 0, len = mixins.length;

  return {
    into: function (target) {
      var mixin, key;

      if (target == null) {
        throw new TypeError("Cannot mix into null or undefined values.");
      }

      for (; i < len; i += 1) {
        mixin = mixins[i];
        for (key in mixin) {
          target[key] = mixin[key];
        }

        // Take care of IE clobbering `toString` and `valueOf`
        if (mixin && mixin.toString !== Object.prototype.toString) {
          target.toString = mixin.toString;
        } else if (mixin && mixin.valueOf !== Object.prototype.valueOf) {
          target.valueOf = mixin.valueOf;
        }
      }
      return target;
    }
  };
};

1
+1:これは有益で興味深いものですが、本当に欲しいものを手に入れるのに役立ちません。より具体的になるように質問を更新しています。
ビビアン川

次も使用できますfoo.__proto__.bar = 'bar';
Jam Risser

9

foo.__proto__ = FooClass.prototypeFirefox、Chrome、SafariでサポートされているAFAIKを実行できます。ことに注意してください__proto__プロパティは非標準であり、いくつかの点で離れて行くかもしれません。

ドキュメント:https : //developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto。また、http://www.mail-archive.com/jsmentors@googlegroups.com/msg00392.htmlに、なぜ存在しないのObject.setPrototypeOf()か、なぜ__proto__廃止されているのかについての説明もあります。


3

プロキシコンストラクター関数を定義してから、新しいインスタンスを作成し、すべてのプロパティを元のオブジェクトからそれにコピーできます。

// your original object
var obj = { 'foo': true };

// your constructor - "the new prototype"
function Custom(obj) {
    for ( prop in obj ) {
        if ( obj.hasOwnProperty(prop) ) {
            this[prop] = obj[prop];
        }
    }
}

// the properties of the new prototype
Custom.prototype.bar = true;

// pass your original object into the constructor
var obj2 = new Custom(obj);

// the constructor instance contains all properties from the original 
// object and also all properties inherited by the new prototype
obj2.foo; // true
obj2.bar; // true

ライブデモ: http : //jsfiddle.net/6Xq3P/

Customコンストラクタは、新しいプロトタイプ、エルゴを表し、そのCustom.prototypeオブジェクトは、あなたがあなたの元のオブジェクトで使用したいのですが、すべての新しいプロパティが含まれています。

Customコンストラクター内では、すべてのプロパティを元のオブジェクトから新しいインスタンスオブジェクトにコピーするだけです。

この新しいインスタンスオブジェクトには、元のオブジェクトのすべてのプロパティ(コンストラクター内でオブジェクトにコピーされたもの)と、内部で定義されたすべての新しいプロパティが含まれますCustom.prototype(新しいオブジェクトはCustomインスタンスであるため)。


3

クロスブラウザーの方法で既にインスタンス化されているJavaScriptオブジェクトのプロトタイプを変更することはできません。他の人が述べたように、あなたのオプションは次のとおりです:

  1. 非標準/クロスブラウザー__proto__プロパティの変更
  2. オブジェクトプロパティを新しいオブジェクトにコピーする

プロトタイプ全体の要素を効果的に変更するためにオブジェクトを再帰的にループして内部オブジェクトにする必要がある場合は特に、どちらも特に優れたものではありません。

質問の代替ソリューション

私はあなたが望むように見える機能をもっと抽象的なものに見ていきます。

基本的にプロトタイプ/メソッドは、オブジェクトに基づいて関数をグループ化する方法を可能にします。
書く代わりに

function trim(x){ /* implementation */ }
trim('   test   ');

あなたが書く

'   test  '.trim();

上記の構文は、object.method()構文のため、OOPという用語が作成されました。従来の関数型プログラミングに対するOOPの主な利点には、次のものがあります。

  1. メソッド名が短く、変数が少ないobj.replace('needle','replaced')vs str_replace ( 'foo' , 'bar' , 'subject')異なる変数のような名前と場所を覚える必要がある
  2. メソッドchaining(string.trim().split().join())は、ネストされた関数を変更および記述してから潜在的に簡単にすることができますjoin(split(trim(string))

残念ながら、JavaScriptでは(上記のように)既存のプロトタイプを変更することはできません。理想的には、上記Object.prototypeの特定のオブジェクトのみを変更できますが、残念ながら変更するObject.prototypeと、スクリプトが破損する可能性があります(プロパティの衝突とオーバーライドが発生します)。

これらの2つのプログラミングスタイルの間に一般的に使用される中間点はなく、カスタム関数を編成するOOP方法もありません。

UnlimitJSは、カスタムメソッドを定義できる中間基盤を提供します。それは避けます:

  1. オブジェクトのプロトタイプを拡張しないため、プロパティの衝突
  2. それでもOOPチェーン構文が可能です
  3. UnlimitedがJavaScriptのプロトタイププロパティの衝突の問題の多くを解決するのは、450バイトのクロスブラウザー(IE6 +、Firefox 3.0 +、Chrome、Opera、Safari 3.0+)スクリプトです。

上記のコードを使用して、オブジェクトに対して呼び出す関数の名前空間を作成します。

次に例を示します。

var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}];

// define namespace with methods
var $ = {
  log:function(){
    console.log(this);
    return this;
  }[Unlimit](),
  alert:function(){
    alert(''+this);
  }[Unlimit]()
}


foo[$.log]()
   [$.log]()
   [$.alert]();

例の詳細については、こちらのUnlimitJSを参照してください。基本的に[Unlimit]()、関数を呼び出すと、その関数をオブジェクトのメソッドとして呼び出すことができます。これは、OOPと機能的な道路の中間点のようなものです。


2

[[prototype]]私が知る限り、すでに構築されたオブジェクトの参照を変更することはできません。元のコンストラクター関数のプロトタイププロパティを変更することもできますが、既にコメントしたように、そのコンストラクターはObjectであり、コアJSコンストラクトの変更は悪いことです。

ただし、必要な追加機能を実装する構築オブジェクトのプロキシオブジェクトを作成することもできます。問題のオブジェクトに直接割り当てることで、追加のメソッドと動作をモンキーパッチすることもできます。

おそらく、別の角度からアプローチしたい場合は、他の方法で必要なものを手に入れることができます。プロトタイプをいじるなど、何をする必要がありますか?


他の誰かがこれの正確さについてコメントできますか?
ビビアン川

この恐ろしいjsfiddleに基づいて、__proto__プロパティを変更することで既存のオブジェクトのプロトタイプを変更できるようです。これは、__proto__表記法をサポートするブラウザー(ChromeおよびFirefox)でのみ機能し、非推奨になりました。つまり、簡単に言えば[[prototype]]、オブジェクトのを変更できますが、おそらく変更すべきではありません。
Nick Husher

1

プロトタイプがわかっている場合は、それをコードに挿入してみませんか?

var foo = new MyPrototype(<%= MyData %>);

したがって、データがシリアル化されると、

var foo = new MyPrototype([{"A":"1","B":"2"},{"X":"7","Y":"8"}]);

ここで必要なのは、引数として配列を取るコンストラクタのみです。


0

継承しArrayたり、「サブクラス化」する方法はありません。

あなたができることはこれです(警告:FESTERING CODE AHEAD):

function Foo(arr){
  [].push.apply(this, arr)
}
Foo.prototype = []
Foo.prototype.something = 123

var foo = new Foo(<%=MyData %>)

foo.length // => 2
foo[0] // => {"A":"1","B":"2"}
foo.something // => 123

これは機能しますが、パスを横切る人には特定の問題を引き起こします(配列のように見えますが、操作しようとすると問題が発生します)。

正気な方法でメソッド/プロパティをに直接追加しfooたり、コンストラクタを使用して配列をプロパティとして保存してみませんか?

function Foo(arr){
  this.items = arr
}
Foo.prototype = {
  someMethod : function(){ ... }
  //...
}

var foo = new Foo(<%=MyData %>)
foo.items // => [{"A":"1","B":"2"},{"X":"7","Y":"8"}]

0

プロトタイプをその場で作成したい場合、これは方法の1つです

function OntheFlyProto (info){
    this.items = info;
    this.y =-1;
    for(var i = 0; i < this.items.length ; i++){
        OntheFlyProto.prototype["get"+this.items[i].name] = function (){
            this.y++;
            return this.items[this.y].value;
        }
    }
}

var foo = [{name:"one", value:1},{name:"two", value:2}];
v = new OntheFlyProto(foo);

-1
foo.prototype.myFunction = function(){alert("me");}

いいえ、できません。これは静的プロパティです。
11

@SLaks prototypeは静的ですか?どういう意味かわかりません。
–ŠimeVidas、2011

1
prototypeオブジェクトではなく、関数のプロパティです。 Object.prototype存在する; {}.prototypeしません。
11

ブラウザの互換性を気にしない場合は、を使用Object.getPrototypeOf(foo)してコンストラクタプロトタイプオブジェクトを取得できます。そのプロパティを変更して、オブジェクトのプロトタイプを変更できます。それは最近のブラウザでのみ機能しますが、どのブラウザかはわかりませんでした。
Nick Husher、2011

@TeslaNick:戻ってくるObject.prototype可能性Object.getPrototypeOf(foo)が最も高いため、直接変更して直接変更することもできます。あまり役に立たない。
Wladimir Palant、2011
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.