18

プロトタイプを変更せずに、コアJavaScriptタイプ(文字列、日付など)をどのように拡張しますか?たとえば、いくつかの便利なメソッドを使用して派生文字列クラスを作成したいとします。

function MyString() { }
MyString.prototype = new String();
MyString.prototype.reverse = function() {
  return this.split('').reverse().join('');
};
var s = new MyString("Foobar"); // Hmm, where do we use the argument?
s.reverse();
// Chrome - TypeError: String.prototype.toString is not generic
// Firefox - TypeError: String.prototype.toString called on incompatible Object

このエラーは、文字列ベースのメソッド(この場合は「分割」)に起因しているようです。これは、そのメソッドが文字列以外のオブジェクトに適用されているためです。しかし、文字列以外のオブジェクトに適用できない場合、実際にそれらを自動的に再利用できますか?

[編集]

明らかに私の試みには多くの点で欠陥がありますが、それは私の意図を示していると思います。少し考えてみると、Stringで明示的に呼び出すことなく、Stringプロトタイプオブジェクトの関数を再利用することはできないようです。

コアタイプをそのまま拡張することは可能ですか?

4

5 に答える 5

18

2年後:グローバルスコープで何かを変更することはひどい考えです

オリジナル:

ネイティブプロトタイプの拡張に「間違った」ものがあるのは、ES5ブラウザのFUDです。

Object.defineProperty(String.prototype, "my_method", {
  value: function _my_method() { ... },
  configurable: true,
  enumerable: false,
  writeable: true
});

ただし、ES3ブラウザをサポートする必要がある場合はfor ... in、文字列でループを使用する人に問題があります。

私の意見では、ネイティブプロトタイプを変更することができ、壊れたコードの使用をやめる必要があります。

于 2011-08-21T23:48:48.387 に答える
3

更新:このコードでさえ、ネイティブStringタイプを完全に拡張していません(lengthプロパティは機能しません)。

Imoこのアプローチに従うことはおそらく価値がありません。考慮すべきことが多すぎて、完全に機能することを保証するために多くの時間を費やす必要があります(機能する場合)。@Raynosは別の興味深いアプローチを提供します

それにもかかわらず、ここにアイデアがあります:


String.prototype.toString実際の文字列以外は呼び出せないようです。このメソッドをオーバーライドできます。

// constructor
function MyString(s) {
    String.call(this, s); // call the "parent" constructor
    this.s_ = s;
}

// create a new empty prototype to *not* override the original one
tmp = function(){};
tmp.prototype = String.prototype;
MyString.prototype = new tmp();
MyString.prototype.constructor = MyString;

// new method
MyString.prototype.reverse = function() {
  return this.split('').reverse().join('');
};

// override 
MyString.prototype.toString = function() {
  return this.s_;
};

MyString.prototype.valueOf = function() {
  return this.s_;
};


var s = new MyString("Foobar");
alert(s.reverse());

ご覧のとおり、それを機能させるにはオーバーライドも必要でしvalueOfた。

しかし:これらがオーバーライドする必要がある唯一のメソッドであるかどうかはわかりません。他の組み込みタイプの場合は、他のメソッドをオーバーライドする必要があるかもしれません。良いスタートは、ECMAScript仕様を取得し、メソッドの仕様を確認することです。

たとえば、String.prototype.splitアルゴリズムの2番目のステップは次のとおりです。

SをToStringを呼び出した結果とし、引数としてこの値を指定します。

オブジェクトがに渡されるとToString、基本的toStringにこのオブジェクトのメソッドが呼び出されます。そして、それがオーバーライドするときに機能する理由toStringです。

更新:機能しないのはですs.lengthしたがって、メソッドを機能させることはできるかもしれませんが、他のプロパティはもっと注意が必要なようです。

于 2011-08-21T23:34:40.370 に答える
2

まず第一に、このコードでは:

MyString.prototype = String.prototype;   
MyString.prototype.reverse = function() {
    this.split('').reverse().join('');
};

変数MyString.prototypeString.prototypeは両方とも同じオブジェクトを参照しています!一方に割り当てることは、もう一方に割り当てることです。reverseメソッドをドロップしたとき、MyString.prototypeそれもに書き込んでいましたString.prototype。だからこれを試してみてください:

MyString.prototype = String.prototype;   
MyString.prototype.charAt = function () {alert("Haha");}
var s = new MyString();
s.charAt(4);
"dog".charAt(3);

最後の2行は、プロトタイプが同じオブジェクトであるため、両方とも警告を発します。あなたは本当に拡張しString.prototypeました。

今あなたのエラーについて。あなたはreverse自分のMyString物を呼びました。このメソッドはどこで定義されていますか?プロトタイプでは、これはと同じString.prototypeです。あなたは上書きしreverseました。それが最初にすることは何ですか?splitターゲットオブジェクトを呼び出します。さて、String.prototype.split動作するためには、を呼び出す必要がありますString.prototype.toString。例えば:

var s = new MyString();
if (s.split("")) {alert("Hi");}

このコードはエラーを生成します:

TypeError: String.prototype.toString is not generic

これが意味するのはString.prototype.toString、文字列の内部表現を使用してその処理を実行し(つまり、内部プリミティブ文字列を返す)、文字列プロトタイプを共有する任意のターゲットオブジェクトに適用できないということです。したがって、を呼び出すとsplit、splitの実装は「ああ、私のターゲットは文字列ではないので、呼び出させてください」と言いましtoStringたが、toString「私のターゲットは文字列ではなく、ジェネリックではありません」と言ったので、をスローしましたTypeError

JavaScriptのジェネリックスについて詳しく知りたい場合は、配列と文字列のジェネリックスに関するこのMDNセクションを参照してください。

これをエラーなしで機能させる方法については、Alxandrの回答を参照してください。

プロトタイプを変更せずに、などの正確な組み込み型を拡張することに関しては、ラッパー、デリゲート、またはサブクラスを作成せずに、実際には拡張しません。しかし、これでは次のような構文は許可されませんStringDate

d1.itervalTo(d2)

ここでd1、およびは、プロトタイプを拡張しなかっd2た組み込みクラスのインスタンスです。:-) JavaScriptは、この種のメソッド呼び出し構文にプロトタイプチェーンを使用します。ただそうです。素晴らしい質問ですが...しかし、これはあなたが考えていたものですか?Date

于 2011-08-21T23:27:44.507 に答える
0

ここで間違っているのは1つだけです。MyString.prototypeはString.prototypeであってはならず、次のようになります。

function MyString(s) { }
MyString.prototype = new String();
MyString.prototype.reverse = function() {
  this.split('').reverse().join('');
};
var s = new MyString("Foobar");
s.reverse();

[編集]

より良い方法であなたの質問に答えるために、それは不可能であるべきではありません。これを見ると、https : //developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/constructorで、bool、int、stringのタイプを変更できないため、変更できないことが説明されています。 「サブクラス化」。

于 2011-08-21T23:14:02.950 に答える
0

基本的な答えはおそらくできないということだと思います。あなたができることはSugar.jsがすることです-オブジェクトのようなオブジェクトを作成し、それから拡張します:

http://sugarjs.com/

Sugar.jsはすべてネイティブオブジェクト拡張に関するものであり、Object.prototypeを拡張するものではありませ

于 2013-07-12T16:51:44.540 に答える