質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.51%

  • JavaScript

    16491questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • ESLint

    26questions

    ESLintは、JavaScriptのための構文チェックツール。全検証ルールを自由に on/offでき、独自のプロジェクトに合わせたカスタムルールを容易に設定することが可能。公開されている様々なプラグインを組み込んで使用することもできます。

【JavaScript】no-extend-native に対処したい【ESLint】(質問タイトル修正)

解決済

回答 5

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 487

himejiy3

score 66

(2017-11-04 8:50頃 追記)
旧タイトル「プロトタイプをよく分かっていなかったようです・・」を変更しました。
当初、「自分が上手くコードを書き替えられないのはプロトタイプの仕組みを理解していないからだ」と考え旧タイトルにしていたのですが、問題は別のところにあったようなので、ベストアンサーを済ませた後ではありますが、タイトルを修正させて頂くことにしました。
何卒、ご了承ください。


以下はカードゲームのシャッフル部分を抜粋したコードで、一応は意図した通りに動いたのですが

Array.prototype.shuffle = function() {
  let i = this.length;
  while (i > 0) {
    const j = parseInt(Math.random() * i, 10);
    i--;
    [this[i], this[j]] = [this[j], this[i]];
  }
  return this;
};

const cards = [];
for (let i = 1; i <= 8; i++) {
  cards.push(i);
  cards.push(i);
}
cards.shuffle();

console.log(cards);  // [5, 3, 1, 8, 4, 6, 6, 7, 2, 1, 3, 4, 2, 5, 7, 8]


これですとESLintに
Array prototype is read only, properties should not be added. (no-extend-native)
(配列プロトタイプは読み取り専用であり、プロパティを追加しないでください。)
と怒られてしまいました。
で、初心者の私としてはここらへんの標準は分かりませんが、取りあえず
「なるほど、これは好ましい手法ではないのかもしれない」
と思いまして、
怒られない書き方に修正しようとしました。
しかし上手くいかず、ハマってしまいました。

const MyArray = [];
MyArray.prototype.shuffle = function() {


とすると
Uncaught TypeError: Cannot set property 'shuffle' of undefined
と言われ、
そりゃそうだと思いながらいろいろ試すのですが上手く動いてくれず、
結局
こんな書き方↓に落ち着いたわけですが

const cards = [];

cards.shuffle = function() {
  let i = this.length;
  while (i > 0) {
    const j = parseInt(Math.random() * i, 10);
    i--;
    [this[i], this[j]] = [this[j], this[i]];
  }
  return this;
};

for (let i = 1; i <= 8; i++) {
  cards.push(i);
  cards.push(i);
}
cards.shuffle();

console.log(cards);
// [4, 8, 6, 7, 1, 5, 1, 7, 6, 4, 2, 8, 3, 5, 2, 3, shuffle: ƒ]


確かにこれなら思惑通りに動くのですけど、
これでは、将来的にコードの再利用も考慮したつもりであった、当初の思想から思いっきり外れてしまい、不本意な書き方になってしまいました。
(まぁ、理解不足・知識不足が判明したため、再利用も何もあったものではないのですが。)
何か大きな勘違いがあるかもしれませんし。

ESLintの「no-extend-native」をoffにしても良いのですけど、何だかモヤモヤします。

そこで質問です。
今回の場合ですと、「配列名.shuffle();」と書けば配列の中身がシャッフルされる汎用コードを残しておきたいです。
勉強不足で理解が追い付かないかもしれませんが、当面の模範解答をご教授願えますでしょうか。
よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • defghi1977

    2017/11/03 19:23

    プロトタイプを理解しているからこそArray.shuffleを実装できたのであって, Arrayを拡張するのが目的であればESLint側をoffにするのは別におかしくないんじゃないかな?

    キャンセル

  • himejiy3

    2017/11/03 20:02

    そういうものなのでしょうか。でも「no-extend-native」を回避するコードを書けないままなのは残念です。最後のコードは回避したように感じられませんでしたし。Arrayにくっつける以外のオーソドックスな手法も知っておきたいと思っています。

    キャンセル

  • defghi1977

    2017/11/03 20:15

    であれば質問の内容と題名が合っていません. 「Array.prototypeに関数を登録せずに配列名.関数名()と記述できるようにするには」とすべきです. 少なくともあなたはプロトタイプを理解しているように見えます.

    キャンセル

  • himejiy3

    2017/11/03 20:25

    んー、そうですか。正直、Array.prototype~の部分は「書いてみたらなんか出来た」といった程度で、理解して書いた感覚があまり無いんですよね…。その後の修正を試みている間も、理由が分からないエラーも多くて。何だかスポンジの上を歩いている気分です。ふわふわした知識で進んでいる感じで。

    キャンセル

回答 5

checkベストアンサー

+4

正直なところ, どう汎用的に作りたいかが見えないため答えようがなく, 再利用だけが目的であれば「どうとでもなる」というのが個人的な意見です. 

ただ, 開発の規模によって扱いやすい形が変化するので, その傾向は掴んでおくと良いでしょう. (Array.prototypeの拡張は大規模開発において名前の競合を引き起こすリスクが高まるため, Lintで明示的に禁止できる等)


ここではArrayオブジェクトを継承する方法を示します. 

以下はプロトタイプチェーンでArrayオブジェクトを継承したCardsオブジェクトを生成しています.

function Cards() {
    this.push(...arguments);
}
Cards.prototype = new Array();
Cards.prototype.shuffle = function () {
    let i = this.length;
    while (i > 0) {
        const j = parseInt(Math.random() * i, 10);
        i--;
        [this[i], this[j]] = [this[j], this[i]];
    }
    return this;
};
let cards = new Cards();
for (let i = 1; i <= 8; i++) {
    cards.push(i);
    cards.push(i);
}
cards.shuffle();
console.log(cards);

ECMAScript2015が利用できる環境であれば次のようなClass構文を使ってArrayオブジェクトを継承することも可能です.

class Cards extends Array {
    constructor() {
        super(...arguments);
    }
    shuffle() {
        let i = this.length;
        while (i > 0) {
            const j = parseInt(Math.random() * i, 10);
            i--;
            [this[i], this[j]] = [this[j], this[i]];
        }
        return this;
    }
}
let cards = new Cards();
for (let i = 1; i <= 8; i++) {
    cards.push(i);
    cards.push(i);
}
cards.shuffle();
console.log(cards);

後はこのCardsオブジェクトを使い回せば良いのです.

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/03 22:23

    すみません、我ながら「落としどころの見えない質問になってしまっているなぁ」と思います。
    確かに再利用だけが目的なら、自力解決が出来なくもなく、自分でも理解できている関数化による使い回しで済ませれば事足りました。
    回答感謝です。
    1番目は自分なりに試行錯誤していた時、かなり近付いていましたが、
    「this.push(...arguments);」と書く発想に到達できませんでした・・。
    2番目のclass命令による構文は、手持ちの参考書には記述があったものの、頭にはまるで入っていませんでした。他言語の経験者は理解しやすいそうですが・・。今一度、本を読み返しておくことにします。こうして回答コードを見ると、この解決策は面白いと感じました。

    キャンセル

  • 2017/11/04 07:58

    飽くまで「今回のプログラムでは」という条件付きではありますが、
    こちらの回答コードの利用で、当初の自分が「こうしたかった」という形に出来ました。

    でも次回は、単純な関数化でまとめようと思います。
    おかげで勉強になりましたけど、そこまでこだわる部分でなかったのは明白です。
    趣味で楽しむプログラミングならではの足踏みでした。

    ちなみに、こちらの回答2番目のclass命令での対処法は、上級者ですと
    constructor() {
    super(...arguments);
    }
    の部分は省略するところのようですが、わざわざ書いてくださっていたことで
    参考書の記述に辿り着きやすかったです。

    皆さん、ありがとうございました。
    非常に、大変迷いましたが、今回はこちらの回答をベストアンサーとさせていただきます。

    キャンセル

+4

既存クラス(JavaScriptの場合はコンストラクタ)に後から変更を加える行為は他の言語でモンキーパッチ等と言われています(モンキーパッチという言葉自体は言語のコミュニティーによって少し異なるので注意が必要です)。このモンキーパッチは一時的な対応やテスト等において有効な手段の一つではありますが、際限なく使うことは危険とされており、特に組み込みオブジェクトに対して行うことは、余程の理由が無い限り使うべきでは無いとされています。(絶対に使うなと言うのでは無く、Polyfillの実現などの妥当な理由があれば、問題ありません)

なぜ、やってはいけないのか?

まず、ESLintでエラーがでるというとよりも、なぜESLintではデフォルトでエラーとしているのかを理解すべきです。なぜ悪いかがわからないとどう直すべきかもわかりませんので。

色々あって、ファイルを分割することにしました。

main.mjs

import cards from './cards';
import './shuffle';
cards.shuffle();

console.log(cards);

shuffle.mjs

Array.prototype.shuffle = function() {
  let i = this.length;
  while (i > 0) {
    const j = parseInt(Math.random() * i, 10);
    i--;
    [this[i], this[j]] = [this[j], this[i]];
  }
  return this;
};

cards.mjs

const cards = [];
for (let i = 1; i <= 8; i++) {
  cards.push(i);
  cards.push(i);
}
Array.prototype.shuffle = function() {};
cards.shuffle();

export default cards;

node.jsでnode --experimental-modules main.mjsで実行すれば、同じように動くことを確認できるでしょう。なお、cards.mjsでshuffleを定義しているのは、モジュールでの分割を頼んだテラ君(仮名)によると、どうやらそのあとのcards.shuffle();がエラーになったので、エラーにならないようにダミーで追加したそうです。テラ君曰く、モジュールはそれぞれ独立した環境だから、ダミーが置いてあっても問題ないとのことです。

さらにプロジェクトが進んで、fromが無いimport文は最初に書くと決まったので、main.mjsを次のように書き直しました。

main.mjs (修正版)

import './shuffle';
import cards from './cards';
cards.shuffle();

console.log(cards);

動くは動きますが、まったくシャッフルされなくなってしまいました。一体何が問題なのでしょうか?

テラ君はモジュールで副作用があるのはfromが無いimport文だけだと勘違いしていたようですが、実際はどのような呼び出し方であれ、副作用はあり得ます。今回のようにグローバルオブジェクトであるArrayの一部を変更するような場合、全ての環境でのArrayに影響を及ぼします。shuffle.mjsとcards.mjsの両方で同じ所(Array.prototype.shuffle)を変更しようとすると、後からの変更が前の変更を上書きしますので、import文が入れ替わっただけで、動作が変わるという現象が起きてしまいました。

このように複数のファイルで構成するようになったときに問題が生じます。上の話は短いコードなのですぐにわかりますが、もっと長いコード、より複雑な入れ子になった呼び出しがある場合、すぐに見つけることはできず、予期せぬ不具合として紛れ込んでしまう可能性があります。これを防ぐには、理由なき安易な組み込みへの拡張は行わない、という規約を設ける以外に方法はありません。

どう修正すればいいのか?

三つほど書きましたが、他にもあると思います。

エラーを無視する。

組み込みオブジェクトへの変更はバグが起きやすいと言うだけで、バグが必ず起きるという物ではありません。使ってはいけないのでは無く、使うことにリスクがあるということです。そのリスクよりもメリットの方が大きいというのであれば、エラーは無視してそのまま使うという選択も十分あり得ます。

関数にする。

対象となる配列を引数とする単なる関数にします。何が何でもオブジェクト指向っぽくしたいというのが無ければこれが良いでしょう。

function shuffle(list) {
  let i = list.length;
  while (i > 0) {
    const j = parseInt(Math.random() * i, 10);
    i--;
    [list[i], list[j]] = [list[j], list[i]];
  }
  return list;
};

const cards = [];
for (let i = 1; i <= 8; i++) {
  cards.push(i);
  cards.push(i);
}
shuffle(cards);

console.log(cards);

Cardsクラス(コンストラクタ)を作り、そこに実装する。

オブジェクト指向っぽくしたいのであればクラスを作ってしまいます。

class Cards {
  constructor() {
    this.list = [];
    for (let i = 1; i <= 8; i++) {
      this.list.push(i);
      this.list.push(i);
    }
  }
  shuffle() {
    let i = this.list.length;
    while (i > 0) {
      const j = parseInt(Math.random() * i, 10);
      i--;
      [this.list[i], this.list[j]] = [this.list[j], this.list[i]];
    }
    return this.list;
  }
}

const cards = new Cards;
cards.shuffle();

console.log(cards);

ESLintは危ないコードを書いていませんかというただのお知らせです。それに必ず従わなければならないというものではありません。しかし、なぜ、エラーや警告になるのかを理解せずに無視するのもいけません。エラーなどが出ないことだけを目的にすると、ちぐはぐな対応になり、せっかくと指摘が無駄になってしまいます。「とりあえず出ないようにしよう」とはしないでください。まずは、なぜ駄目なのかということを調べて、理解する所から始めてください。何が問題なのかが調べてもわからなければ、teratailで聞けば良いだけなんですから。きっと皆さんが親切に答えてくれますよ。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/04 08:35

    回答ありがとうございます。
    既にタッチの差でベストアンサーで閉じてしまっていたことをご容赦ください。

    『ESLintでエラーがでるというとよりも、なぜESLintではデフォルトでエラーとしているのかを理解すべきです。』
    そう、そこです。この警告はどこまで従うべきなのか、その判断が、手持ちの書籍と自分なりのネット検索ではできませんでした。

    完全な個人の趣味といえども、独自の道を歩むつもりは毛頭無く、長いものに巻かれる書き方をしたいので。

    『なぜ駄目なのかということを調べて、理解する所から始めてください。』
    はい、今回の件は「このエラーの詳細を知りたい」という質問でいったん留めておくべきでした。
    結論を急ぎ過ぎて、漠然とした問いになってしまったことを申し訳なく思います。

    キャンセル

+3

今回の場合ですと、「配列名.shuffle();」と書けば配列の中身がシャッフルされる汎用コードを残しておきたいです。

ESLintの「no-extend-native」

は、仕組み上両立できません(何か技巧的なことをすれば回避できなくはないかもしれませんが、それはno-extend-nativeをごまかしているのに過ぎず、本質的にはどうしようもありません)。

標準で使われているプロトタイプにメソッドを追加するのは「プロトタイプ汚染」と言われるように、忌避されることも多い手法です。再利用を考えれば、shuffle(配列)という形の単なる関数としておいたほうがいいと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/03 20:15

    ほうほうほうほう、そうなのですか。そういう部分も知りたかったのです。自分は何が分かっていないのかも分からない段階なので。
    単なる関数で書いて再利用に備えるのは出来ると思います。それで良いとされるなら気が楽になるところです。

    キャンセル

  • 2017/11/03 20:27

    require/exportsやimport/exportを使う現代のJavaScript環境では、ライブラリから供給されるものも、基本的に変数でやり取りされます。

    「prototypeの書き換え」は、「新しい標準で登場したメソッドをJavaScriptコードで補う」時以外は、ほぼ使われません。

    キャンセル

  • 2017/11/03 21:05

    回答に加えて「変数でやり取り」「prototype書き換えはほぼ使われない」といったお話で、本当に知りたかった部分に近付いた感じがします。
    今回のつまづきは正解が見えてこなかったもので、ESLintの警告に従うべきか、offで対処すべきか、はたまた、踏ん張ってプロトタイプの知識を深めるべきか、関数でサッと済ませて次に進むべきか、判断できず、取りあえず足踏みしてしまっていました。
    方向性が見えてきたように思います。

    キャンセル

+2

基本的な配列に対して自分で定義したメソッドをポン付けするのはラッパーオブジェクトを使えば実現できると思います。
でもけっこう面倒なので、別のやり方を提案します。
シャッフルする配列をプロパティに持ったクラス(jsなのでfunctionだけど)を用意するのはどうでしょうか。

function Cards(){
    let numbers = [];
};

Cards.prototype.set = function(c){
    this.numbers = c;
}

Cards.prototype.shuffle = function(){
    let arr = this.numbers;
    for(let i = arr.length - 1; i > 0; i--){
        const r = Math.floor(Math.random() * (i + 1));
        const tmp = arr[i];
        arr[i] = arr[r];
        arr[r] = tmp;
    }
    this.numbers = arr;
}

var case1 = new Cards();
case1.set([1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8]);
case1.shuffle();
console.log('case1.numbers', case1.numbers);

でもここまで書いて気づいたんですが、そもそもprototype使う必要はないですよね。
普通に配列をシャッフルする関数を書けばいいだけ。

function shuffle(arr){
    for(let i = arr.length - 1; i > 0; i--){
        const r = Math.floor(Math.random() * (i + 1));
        const tmp = arr[i];
        arr[i] = arr[r];
        arr[r] = tmp;
    }
    return arr;
};

var cards = [1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8];
cards = shuffle(cards);

結論:シャッフル処理を外出ししたいなら関数化すればok。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/03 20:41

    「prototypeを使う必要はない」・・はい、こだわりすぎましたかね・・。
    「関数化すればok。」・・結局、そこに行き着くものでしょうか。世の先輩方もそういう風に組んでいるもんだ、となれば安心感はあります。
    自分的に、ちょっと背伸びしすぎていた部分もあったかもしれません。

    キャンセル

+2

  //名前の衝突防止
  const shuffle = Symbol('shuffle'); 
  //shuffleの実装と仮定
  _shuffle(){ return [] };

  Object.defineProperty(Array.prototype, shuffle, {
    value: _shuffle,
    writable: true,
    configurable: true,
    enumerable: false
  });

  const numbers = [1, 2, 3, 4, 5];

  console.log(numbers[shuffle]()); // []

numbers.shuffle()ではなく、numbers[shuffle]()になるのがイケテナイ。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/04 06:47

    これはなるほどと思いました。
    先のdefghi1977さんの回答でも「(...arguments)」とされていて、「あっ」と思った次第です。
    自分が迷走していた当時、その点には全く考えが及んでいませんでした。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.51%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • JavaScript

    16491questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • ESLint

    26questions

    ESLintは、JavaScriptのための構文チェックツール。全検証ルールを自由に on/offでき、独自のプロジェクトに合わせたカスタムルールを容易に設定することが可能。公開されている様々なプラグインを組み込んで使用することもできます。