155

すべてのJSオピニオンリーダーは、ネイティブオブジェクトを拡張することは悪い習慣であると言います。しかし、なぜ?パフォーマンスヒットはありますか?Object彼らは、誰かがそれを「間違った方法」で行い、列挙可能なタイプをに追加して、任意のオブジェクトのすべてのループを実質的に破壊することを恐れていますか?

TJHolowaychukのshould.jsを例にとってみましょう。はに簡単なゲッターを追加し、すべてが正常に機能します(ソース)。Object

Object.defineProperty(Object.prototype, 'should', {
  set: function(){},
  get: function(){
    return new Assertion(Object(this).valueOf());
  },
  configurable: true
});

これは本当に理にかなっています。たとえば、を拡張できますArray

Array.defineProperty(Array.prototype, "remove", {
  set: function(){},
  get: function(){
    return removeArrayElement.bind(this);
  }
});
var arr = [0, 1, 2, 3, 4];
arr.remove(3);

ネイティブタイプの拡張に反対する議論はありますか?

4

9 に答える 9

145

オブジェクトを拡張すると、その動作が変更されます。

独自のコードでのみ使用されるオブジェクトの動作を変更することは問題ありません。ただし、他のコードでも使用されているものの動作を変更すると、他のコードが破損するリスクがあります。

javascriptのオブジェクトクラスと配列クラスにメソッドを追加する場合、javascriptの動作により、何かが壊れるリスクが非常に高くなります。長年の経験から、この種のものはjavascriptにあらゆる種類のひどいバグを引き起こすことがわかりました。

カスタム動作が必要な場合は、ネイティブクラスを変更するのではなく、独自のクラス(おそらくサブクラス)を定義する方がはるかに優れています。そうすれば、何も壊すことはありません。

サブクラス化せずにクラスの動作を変更する機能は、優れたプログラミング言語の重要な機能ですが、めったに使用せず、注意して使用する必要があります。

于 2012-12-25T21:51:19.463 に答える
37

パフォーマンスの低下など、測定可能な欠点はありません。少なくとも誰も言及していません。したがって、これは個人的な好みと経験の問題です。

主な賛成論:見た目が良く、より直感的です:シンタックスシュガー。これはタイプ/インスタンス固有の関数であるため、そのタイプ/インスタンスに具体的にバインドする必要があります。

主な反対の議論:コードは干渉する可能性があります。lib Aが関数を追加すると、libBの関数が上書きされる可能性があります。これはコードを非常に簡単に壊す可能性があります。

どちらにもポイントがあります。タイプを直接変更する2つのライブラリに依存している場合、期待される機能はおそらく同じではないため、コードが壊れてしまう可能性があります。私はそれに完全に同意します。マクロライブラリは、ネイティブタイプを操作してはなりません。そうでなければ、開発者としてのあなたは、舞台裏で実際に何が起こっているのかを知ることができません。

そして、それが私がjQueryやアンダースコアなどのライブラリを嫌う理由です。誤解しないでください。それらは完全にうまくプログラムされており、魅力のように機能しますが、大きいです。あなたはそれらの10%だけを使用し、約1%を理解しています。

だから私は、本当に必要なものだけを必要とする原子論的アプローチを好みます。このように、あなたは常に何が起こるかを知っています。マイクロライブラリは、あなたが望んでいることだけを実行するので、干渉することはありません。どの機能が追加されているかをエンドユーザーに知らせるという状況では、ネイティブタイプの拡張は安全であると見なすことができます。

TL; DR疑わしい場合は、ネイティブタイプを拡張しないでください。エンドユーザーがその動作を認識し、必要とすることが100%確実である場合にのみ、ネイティブ型を拡張します。ネイティブタイプの既存の関数を操作しないでください。既存のインターフェイスが破損する可能性があります。

タイプを拡張する場合は、Object.defineProperty(obj, prop, desc);を使用します。できない場合は、タイプのを使用してくださいprototype


ErrorsをJSON経由で送信できるようにしたかったので、最初にこの質問を思いつきました。だから、私はそれらを文字列化する方法が必要でした。error.stringify()よりもずっと気分が良かったerrorlib.stringify(error); 2番目の構成が示すように、私はそれ自体errorlibではなく操作していerrorます。

于 2012-12-26T04:24:10.690 に答える
20

私の意見では、それは悪い習慣です。主な理由は統合です。should.jsドキュメントの引用:

OMG IT EXTENDS OBJECT ???!?!@はい、そうです。単一のゲッターを使用する必要があります。いいえ、コードを壊すことはありません。

さて、作者はどうやって知ることができますか?私のモックフレームワークが同じことをした場合はどうなりますか?私のpromiseslibが同じことをした場合はどうなりますか?

あなたがあなた自身のプロジェクトでそれをしているなら、それは大丈夫です。しかし、図書館にとって、それは悪いデザインです。Underscore.jsは、正しい方法で行われたことの例です。

var arr = [];
_(arr).flatten()
// or: _.flatten(arr)
// NOT: arr.flatten()
于 2012-12-25T22:26:09.653 に答える
18

ケースバイケースでそれを見ると、おそらくいくつかの実装が受け入れられます。

String.prototype.slice = function slice( me ){
  return me;
}; // Definite risk.

すでに作成されたメソッドを上書きすると、解決するよりも多くの問題が発生します。そのため、多くのプログラミング言語では、この慣習を回避することが一般的に述べられています。Devsは、関数が変更されたことをどのように知るのですか?

String.prototype.capitalize = function capitalize(){
  return this.charAt(0).toUpperCase() + this.slice(1);
}; // A little less risk.

この場合、既知のコアJSメソッドを上書きしていませんが、Stringを拡張しています。この投稿の1つの議論は、このメソッドがコアJSの一部であるかどうか、またはドキュメントをどこで見つけるかを新しい開発者がどのように知るかについて言及しました。コアJSStringオブジェクトがcapitalizeという名前のメソッドを取得するとどうなりますか?

他のライブラリと衝突する可能性のある名前を追加する代わりに、すべての開発者が理解できる会社/アプリ固有の修飾子を使用した場合はどうなりますか?

String.prototype.weCapitalize = function weCapitalize(){
  return this.charAt(0).toUpperCase() + this.slice(1);
}; // marginal risk.

var myString = "hello to you.";
myString.weCapitalize();
// => Hello to you.

他のオブジェクトを拡張し続けると、すべての開発者が(この場合は)weを使用して実際にそれらに遭遇し、会社/アプリ固有の拡張であることが通知されます。

これは名前の衝突を排除するものではありませんが、可能性を減らします。コアJSオブジェクトの拡張があなたやあなたのチームのためであると判断した場合、おそらくこれはあなたのためです。

于 2015-07-16T17:57:23.580 に答える
9

ビルトインのプロトタイプを拡張することは確かに悪い考えです。ただし、ES2015では、目的の動作を取得するために利用できる新しい手法が導入されました。

sを利用WeakMapしてタイプを組み込みのプロトタイプに関連付ける

次の実装では、プロトタイプNumberArrayプロトタイプをまったく触れずに拡張します。

// new types

const AddMonoid = {
  empty: () => 0,
  concat: (x, y) => x + y,
};

const ArrayMonoid = {
  empty: () => [],
  concat: (acc, x) => acc.concat(x),
};

const ArrayFold = {
  reduce: xs => xs.reduce(
   type(xs[0]).monoid.concat,
   type(xs[0]).monoid.empty()
)};


// the WeakMap that associates types to prototpyes

types = new WeakMap();

types.set(Number.prototype, {
  monoid: AddMonoid
});

types.set(Array.prototype, {
  monoid: ArrayMonoid,
  fold: ArrayFold
});


// auxiliary helpers to apply functions of the extended prototypes

const genericType = map => o => map.get(o.constructor.prototype);
const type = genericType(types);


// mock data

xs = [1,2,3,4,5];
ys = [[1],[2],[3],[4],[5]];


// and run

console.log("reducing an Array of Numbers:", ArrayFold.reduce(xs) );
console.log("reducing an Array of Arrays:", ArrayFold.reduce(ys) );
console.log("built-ins are unmodified:", Array.prototype.empty);

ご覧のとおり、この手法でプリミティブプロトタイプを拡張することもできます。マップ構造とObjectIDを使用して、タイプを組み込みのプロトタイプに関連付けます。

私の例では、配列自体の要素から空のアキュムレータを作成する方法と、このアキュムレータと要素を連結する方法に関する情報を抽出できるため、単一の引数としてreduceのみを期待する関数を有効にします。Array

Mapガベージコレクションされない組み込みのプロトタイプを表すだけの場合、弱参照は意味をなさないため、通常のタイプを使用できた可能性があることに注意してください。ただし、aWeakMapは反復可能ではなく、適切なキーがない限り検査できません。どんな形のタイプリフレクションも避けたいので、これは望ましい機能です。

于 2016-10-01T16:03:42.393 に答える
8

ネイティブオブジェクトを拡張すべきでないもう1つの理由:

私たちはprototype.jsを使用し、ネイティブオブジェクトで多くのものを拡張するMagentoを使用しています。これは、新しい機能を導入することを決定するまでは正常に機能し、そこから大きな問題が発生します。

ページの1つにWebcomponentsを導入したため、webcomponents-lite.jsは、IEの(ネイティブ)イベントオブジェクト全体を置き換えることにしました(なぜですか?)。もちろん、これはprototype.jsを壊し、次にMagentoを壊します。(問題が見つかるまで、問題をさかのぼって多くの時間を費やす可能性があります)

あなたがトラブルが好きなら、それを続けてください!

于 2018-05-02T11:51:27.810 に答える
5

私はこれを行わない3つの理由を見ることができます(少なくともアプリケーション内から)、そのうちの2つだけがここの既存の回答で扱われています:

  1. これを間違えると、拡張型のすべてのオブジェクトに列挙可能なプロパティが誤って追加されてしまいます。を使用すると簡単に回避できObject.definePropertyます。これにより、デフォルトで列挙できないプロパティが作成されます。
  2. 使用しているライブラリと競合する可能性があります。勤勉に避けることができます。プロトタイプに何かを追加する前に、使用するライブラリが定義するメソッドを確認し、アップグレード時にリリースノートを確認して、アプリケーションをテストするだけです。
  3. ネイティブJavaScript環境の将来のバージョンと競合する可能性があります。

ポイント3は間違いなく最も重要なものです。使用するライブラリを決定するため、テストを通じて、プロトタイプ拡張によって使用するライブラリとの競合が発生しないことを確認できます。コードがブラウザで実行されていると仮定すると、ネイティブオブジェクトについても同じことが言えません。今日定義Array.prototype.swizzle(foo, bar)し、明日GoogleがChromeに追加Array.prototype.swizzle(bar, foo)すると、なぜ.swizzleの動作がMDNに記載されているものと一致しないように見えるのか疑問に思う、混乱した同僚になってしまう可能性があります。

mootoolsが所有していないプロトタイプをいじったことで、Webの破損を避けるためにES6メソッドの名前が変更されたという話も参照してください。)

これは、ネイティブオブジェクトに追加されたメソッドにアプリケーション固有のプレフィックスを使用することで回避できます(たとえば、Array.prototype.myappSwizzleの代わりにdefineを使用しますArray.prototype.swizzle)が、それはちょっと醜いです。プロトタイプを拡張する代わりにスタンドアロンのユーティリティ関数を使用することで、同様に解決できます。

于 2017-12-29T18:59:46.593 に答える
3

パフォーマンスも理由です。キーをループする必要がある場合があります。これを行うにはいくつかの方法があります

for (let key in object) { ... }
for (let key in object) { if (object.hasOwnProperty(key) { ... } }
for (let key of Object.keys(object)) { ... }

私は通常for of Object.keys()、それが正しいことをし、比較的簡潔で、チェックを追加する必要がないので使用します。

しかし、それははるかに遅いです。

for-ofvsfor-inパフォーマンスの結果

理由を推測するだけObject.keysで遅いのは明らかでObject.keys()、割り当てを行う必要があります。実際、AFAIKは、それ以降、すべてのキーのコピーを割り当てる必要があります。

  const before = Object.keys(object);
  object.newProp = true;
  const after = Object.keys(object);

  before.join('') !== after.join('')

JSエンジンが何らかの不変のキー構造を使用して、不変のObject.keys(object)キーを反復処理し、まったく新しい不変のキーオブジェクトを作成する参照を返す可能性がありobject.newPropますが、それでも明らかに最大15倍遅くなります。

チェックでさえhasOwnProperty最大2倍遅くなります。

そのすべてのポイントは、パフォーマンスに敏感なコードがあり、キーをループする必要がある場合は、for inを呼び出さなくても使用できるようにする必要があるということですhasOwnProperty。変更していない場合にのみこれを行うことができますObject.prototype

Object.defineProperty追加するものが列挙できない場合にプロトタイプを変更するために使用する場合、上記の場合、JavaScriptの動作に影響を与えないことに注意してください。残念ながら、少なくともChrome 83では、パフォーマンスに影響します。

ここに画像の説明を入力してください

パフォーマンスの問題を強制的に表示するために、3000個の列挙不可能なプロパティを追加しました。30のプロパティしかないため、テストが近すぎて、パフォーマンスに影響があったかどうかを判断できませんでした。

https://jsperf.com/does-adding-non-enumerable-properties-affect-perf

Firefox77とSafari13.1は、拡張クラスと非拡張クラスのパフォーマンスに違いがないことを示しました。おそらくv8がこの領域で修正され、パフォーマンスの問題は無視できます。

しかし、私も付け加えておきますが、の話がありArray.prototype.smooshます。短いバージョンは、人気のあるライブラリであるMootoolsで、独自に作成されていArray.prototype.flattenます。標準化委員会がネイティブを追加しようとしたとき、Array.prototype.flatten彼らは多くのサイトを壊さずにはいられないことに気づきました。休憩について知った開発者は、es5メソッドsmooshをジョークと名付けることを提案しましたが、人々はそれがジョークであると理解せずにびっくりしました。flat彼らは代わりに落ち着きましたflatten

話の教訓は、ネイティブオブジェクトを拡張するべきではないということです。そうした場合、同じ問題が発生する可能性があり、特定のライブラリがMooToolsと同じくらい人気がない限り、ブラウザベンダーはあなたが引き起こした問題を回避する可能性はほとんどありません。あなたの図書館がそれほど人気を博しているのなら、あなたが引き起こした問題を他の人たちに強制的に回避させるのは一種の意味があるでしょう。したがって、ネイティブオブジェクトを拡張しないでください

于 2019-11-03T03:38:36.573 に答える
0

編集:

しばらくして、私は考えを変えました-プロトタイプの汚染は悪いです(しかし、私は投稿の最後に例を残しました)。

上記および下記の投稿に記載されているよりもはるかに多くの問題を引き起こす可能性があります。

JS / TSユニバース全体で単一の標準を持つことが重要です(一貫したnpmjsがあると便利です)。

以前、私はブル**イットを書き、人々に彼らの図書館でそれをするように勧めました-これについては申し訳ありません:

Jeff Clayton の提案も良いようです-メソッド名のプレフィックスは、パッケージ名の後にアンダースコアを付けることができます。例:( Array.prototype.<custom>_Flatten既存のパッケージプレフィックスが将来的に既存のパッケージになる可能性があります)


元の回答の一部:

私は個人的にネイティブメソッドを拡張xしていました。ライブラリでプレフィックスを使用しています(サードパーティのライブラリを拡張するときにも使用しています)。

TSのみ:

declare global {
  interface Array<T> {
    xFlatten(): ExtractArrayItemsRecursive<T>;
  }
}

JS + TS:

Array.prototype.xFlatten = function() { /*...*/ }
于 2021-07-02T12:17:13.883 に答える