web-dev-qa-db-ja.com

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

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

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

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

var foo = <%=MyData %>;

MyDataは、Dictionary<string,string>オブジェクトで.NET JavaScriptSerializerを呼び出した結果であるとします。

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

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

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

101

2012年2月編集:以下の回答は正確ではありません。 __proto__は「規範的オプション」としてECMAScript 6に追加されています。つまり、実装する必要はありませんが、実装されている場合は、所定の規則に従う必要があります。これは現在未解決ですが、少なくとも公式にはJavaScriptの仕様の一部になります。

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

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

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

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

オブジェクトには、現在のプロトタイプを指す内部[[prototype]]プロパティがあります。動作方法は、オブジェクトのプロパティが呼び出されるたびに、オブジェクトで開始し、ルートObjectプロトタイプの後に一致するか失敗するまで[[prototype]]チェーンをたどります。これにより、Javascriptがオブジェクトのランタイム構築と変更を可能にします。必要なものを検索する計画があります。

__proto__プロパティは、いくつかの実装(多くの場合)に存在します。Mozillaの実装、私が知っているすべてのWebkit実装、その他の実装です。このプロパティは内部[[prototype]]プロパティを指し、オブジェクトの作成後の変更を許可します。この連鎖ルックアップにより、プロパティと関数はすぐにプロトタイプに一致するように切り替わります。

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

これは投稿します https://bugzilla.mozilla.org/show_bug.cgi?id=60786 は、__proto__の現在の実装とそれらの違いについて具体的に説明しています。それは難しい未解決の問題だからです。 a。)構文b。)ホストオブジェクト(DOMは技術的にJavascriptの外部に存在する)およびc。)__proto__を除く、Javascriptのすべては変更可能です。残りは完全にあなたと他のすべての開発者の手に委ねられているので、__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に類似した、まだ到達できない新しいオブジェクト)を除いて、ひどい考えです。これは、12年以上前に設定可能な__proto__を設計および実装したものです。

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

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

111
user748221

ES6は最終的に Object.setPrototypeOf(object、prototype) を指定します。これはChromeおよびFirefoxで既に実装されています。

31
Joel Richard

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

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

function Foo() {}

var foo = new Foo();

以下を実行することにより、barのすべてのインスタンスにプロパティFooを追加できます。

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;
    }
  };
};
14
Tim

foo.__proto__ = FooClass.prototype、Firefoxでサポートされている限り、ChromeおよびSafariを実行できます。 __proto__プロパティは非標準であり、ある時点でなくなる可能性があることに注意してください。

ドキュメント: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/protoObject.setPrototypeOf()がない理由の説明については、 http://www.mail-archive.com/[email protected]/msg00392.html も参照してください。なぜ__proto__は非推奨になりました。

9
Wladimir Palant

クロスブラウザの方法で既にインスタンス化されている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方法もありません。

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

  1. オブジェクトのプロトタイプを拡張しないため、プロパティの衝突
  2. OOPチェーン構文を引き続き許可
  3. JavaScriptのプロトタイププロパティの衝突の問題のUnlimitの多くは、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]();

より多くの例をここで読むことができます nlimitJS 。基本的に、関数で[Unlimit]()を呼び出すと、その関数をオブジェクトのメソッドとして呼び出すことができます。 OOPと機能的な道路の中間地点のようなものです。

3
Lime

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

// 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コンストラクタは、新しいプロトタイプergoを表し、そのCustom.prototypeオブジェクトには、元のオブジェクトで使用するすべての新しいプロパティが含まれます。

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

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

3
Šime Vidas

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

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

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

2
Nick Husher

プロトタイプを知っているなら、なぜそれをコードに挿入しないのですか?

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

したがって、データがシリアル化されると、次のようになります

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

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

1
rewritten

その場でプロトタイプを作成したい場合、これは方法の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);
0
Yene Mulatu
foo.prototype.myFunction = function(){alert("me");}
0
PhD

Arrayまたは「サブクラス」から実際に継承する方法はありません。

あなたができることはこれです(警告:コードを先に進める):

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
Ricardo Tomasi