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

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

ただいまの
回答率

88.77%

JavaScriptのコーディング方法について幾つか教えてください。

解決済

回答 2

投稿

  • 評価
  • クリップ 2
  • VIEW 866

fukumi822

score 200

趣味でプログラミングをしているのですが、以下の点が分からないので教えてください。

  • JavaScriptの書き始め方
  • 大量の要素管理方法

JavaScriptの書き始め方

JavaScriptでは基本的にDOM操作をする為、ウィンドウのイベントリスナーを設定します。

window.addEventListener('load', ()=>{
 //!script
});

ただ、ここ(!script)にやたい事をズラズラ書いてしまうと後で読み返す時に苦労するので、私はクラスを使って、主な処理をクラス内で記述しています。

クラス内であれば、インスタンス時にDOMが取れたら順序関係なくプロパティ・メソッドの定義が出来る為です。
これはあくまでも私の考えで一般的にはどの様な方法で書き始めているのかが知りたいです。

大量の要素の管理方法

大量の要素を取得する際、の管理方法が知りたいです。
クラスコンストラクタ内で以下の様にしていました。

class MyClass{
  constructor(){
    this.element = document.getElementById("postText");
    //以下十個以上似た記述
  }
}

個人的な感想これだと、何がなんだよく分からなくなって来ます。
そこで、全てを一纏めにしようと感がえ次の様に構造化しました。

class MyClass{
  constructor(){
    this._publicElements = {
      editorInputInterface : {
        textArea : document.getElementById("postText"),
        input : {
          postTitle : document.getElementById("postTitle"),
          postLabel : document.querySelector("input[name='post_label']"),
          postSeo   : document.querySelector("input[name='post_seo_about']")
        }
      },
      editorControlInterface : {
        ControlButton : {
          addImage : document.querySelector("#Toolbar .left button[value='image']"),
          addLink  : document.querySelector("#Toolbar .left button[value='link']"),
          addTable : document.querySelector("#Toolbar .left button[value='table']"),
          addTableWidget : {
            widgetBody  : document.querySelector("div.tableInWidget"),
            input       : document.querySelectorAll("div.tableInWidget input"),
            button      : document.querySelector("div.tableInWidget button")
          },
          addQuote : document.querySelector("#Toolbar .left button[value='quote']")
        },

        RequestButton : {
          public    : document.querySelector("#Toolbar div.right button.article"),
          preview   : document.querySelector("#Toolbar div.right button.preview"),
          save      : document.querySelector("#Toolbar div.right button.save")
        }
      }
    };
  }
}

確かに構造化されて分かりやすい??? な状況で、デメリットも見えて来ました。

  • 参照する時軽く数十文字行くthis._publicElements.editorControlInterface.ControlButton.addLink
  • 確かに読めば分かるけど、視認性が悪すぎる

この様な問題に直面しました。皆さんは大小の規模に限らずこの様な場合どの様に管理をしますか?

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • 退会済みユーザー

    退会済みユーザー

    2019/04/30 01:45

    MyClass の中で決め打ちしているのだから、それを再利用できないクラスになっていますね。
    querySelector で指定する要素群を constructor への引数にしてはどうですか?
    細分化して構造を構築する、その構造を再利用できるようにするのがクラスの便利なところではないですか?

    あと短く一度に指定するなら
    let [a,b,c] = document.querySelectorAll ("..., ...., ...");

    キャンセル

  • miyabi_takatsuk

    2019/04/30 01:47

    hai_haiさん>そこですよね。
    せっかくクラス構文使ってオブジェクト指向で書いてるのに、
    意味がなくなってる気がします 汗

    キャンセル

  • 退会済みユーザー

    退会済みユーザー

    2019/04/30 02:26 編集

    おそらくね、必要な要素を取り込む必要はないのですよ。イベントハンドラの引数に渡される event から必要なものを探せばいい。event.target からクリックされた要素、そこから id なり className なり、親要素を辿っても十数段上を探すだけで必要な要素を見つけられるはずです。document.addEvenentListener で始めると dom 構造を読み終わるのを待たなくていい。document はすでに存在するのだから。
    ここで見ていると document にイベントを張り付けている人は少ないよね。最善だと思うのに。

    キャンセル

回答 2

checkベストアンサー

+2

まず、windowオブジェクトを使用しているので、
ブラウザ上で動くスクリプトという認識でお答えしていきます。
また、アロー関数やclass構文は、Internet Explorerでは動きません。
javascriptは、プロトベースのオブジェクト指向言語のため、
javascriptのclass構文は、プロトベース構文の糖衣構文になります。
とまぁ、class構文、アロー関数を使う事を前提とされているようなので、IEは対象ブラウザとして考えないでいい状況との認識も併せてお答えしていきます。

JavaScriptの書き始め方

これに関して。
DOMを読み込んでから実行するなら、window.addEventListener('load', ()=>{});
は発動が遅いですよ。
これは、HTML上のすべての要素(画像やらJSやら)全ファイルを読み込み完了してから実行するものです。つまり、初動が遅くなります。
DOMを読み込んでからなら、

document.addEventListener('DOMContentLoaded', ()=> {
  // 処理を記述
});


で充分ですし、適当かと思います。
そして、考えとおっしゃっていますが、
class構文を使用したとしても、
インスタンスを生成する際には、DOMが読み込まれている状態でないと、 当然エラーが起きます。
クラスの記述は、ロード系イベントの外側でけっこうですが、
インスタンスを生成するnew構文は、ロード系イベントの中で書かないと、DOMが取得できずエラーが出ます。
(body要素終了タグ直前に書くなら、あるいはエラーにならないかもしれない)
つまりは、下記のようにしなくてはならないということです。

class myClass {
  constructor(){
    this.hoge = document.getElementById('hoge');
  }
}
// ここでDOM読み込み前にインスタンスを生成するとエラーになる。
const myInstans = new myClass();

document.addEventListener('DOMContentLoaded', ()=> {
  // このイベント内であれば、DOMが読み込まれているので、絶対にエラーは起きない
  window.myInstans002 = new myClass();
});

つまり一般的とか、考えとかではなく、
そもそも、DOMの取得なりのコントロールは、DOMを読み込んでからでないと動かないんです。

また、主な処理を、クラスの中に~とおっしゃっておりますが、
もしかして、
クラスのメソッドの中でいちいち、
window.addEventListnerなどを実行しているということでしょうか?
だとしたら、かなりよろしくなく、メモリーリークを誘発させる書き方だと思います。
addEventListnerは、その名の通り、イベントを追加するメソッドです。
つまり、そんなクラスを作った日には、インスタンスを生成するないし、該当メソッドを実行する度に、イベントを追加されてしまいます。
一、二個ならまだしも、一万個とか生成した時は、
管理不能、メモリ解放不能な変数や関数を、大量にメモリ上に残すことになります。
addEventListnerは、同スクリプト内で大量に使うなら、必要ないものは削除できるようにしておくか、
そもそも、同じオブジェクトにはあまり何個も重ねて処理を追加しないように書く工夫が必要かと思います。

大量の要素の管理方法

constructorに直接オブジェクト処理実行してる時点で、メモリの無駄遣いというかすでに分かりやすさや、柔軟性が失われているような気がしますね。
せっかくclass構文を使ってオブジェクト指向で書いているのにもったいないような気がします。

私なら、構造だけを定義したクラスを先に用意し、
実際に使用するクラスの中でインスタンスを生成するようにして、
オブジェクトを管理しますね。
そちらの方が、何のクラスで何をしているかがわかりやすくなり、
また、各構造別にメソッドを定義もできますので、非常に管理しやすくなるかと。
そうすれば、プロパティ名を短くしても、どこで何をやってるかのわかりずらさも軽減されるかと思います。

class inputInterface {
  constructor(titleID, labelSelector, seoSelector){
    this.title = document.getElementById(titleID);
    this.label = document.querySelector(labelSelector);
    this.seo = document.querySelector(seoSelector);
  }
}

class editorInputInterface {
  constructor(textAreaName, titleID, labelSelector, seoSelector){
    this.textArea = document.getElementById(textAreaName);
    this.input = new inputInterface(titleID, labelSelector, seoSelector);
  }
}

class MyClass {
  constructor(){
    this._publicElements = {
      editorInput: new editorInputInterface("postText", "postTitle", "input[name='post_label']", "input[name='post_seo_about']"),
      // .
      // .
      // .
      // 以下にも同様に、先に構造体を定義して、こちらで使用していく
    };


  }
}

つまり、質問者さんの書き方だと、せっかくクラス構文で書いているのに、
結局ハードコーディングになってしまっていて、
クラス構文を生かし切れていない形になってます。
ハードコーディングは、パッと見でわかりやすい反面、
柔軟性もなければ、メンテナンス性もかなり悪くなります。

先に構造体を作って、インスタンスを生成して・・・という作り方は、
中~大規模のオブジェクト指向での開発において、とても重要になります。

以上、長文失礼しました。
また、後半に関しては、正直私の考え方なので、違うやり方の方がいい場合もあるかもしれません。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/04/30 02:36

    回答ありがとうございます。
    Internet Explorerに関して、対応の意思はありませんので問題はありません。

    クラスの考え方についてもありがとうございます。
    確かにクラスを使っているので、JavaのMainクラスみたいな書き方(自分だけかも)になっていて無駄が多いですね。この辺りもう少し設計を練り直したいと思います。

    キャンセル

+2

まず、MyClassのままなのが相当ダメです。イケてません。
貴方は玩具を整理しようとおもちゃ箱を買ってきて、
それにMyClassというラベルを貼って管理しようと思いますか?

普通の人間は入れるものを抽象化してラベル名を決定します。
例えばゲーム機でまとめるならゲーム機というラベルを貼りますよね。

やってないでしょ?すぐやりましょ。
もしこれが例題であってもやってください。
コードを書くというのはやることに名前を付けるという行為なので、練習であっても、いやだからこそ命名の練習が必須です。

私はクラスを使って、主な処理をクラス内で記述しています。

オブジェクト指向でよくあるクラスを使うとコード量が増えますが
この増えたコード量をペイ出来るか否かがオブジェクト指向のとてもむずかしい所になります。

そこで、全てを一纏めにしようと感がえ次の様に構造化しました。

オブジェクト指向初心者が陥るやつですね。
ポリモーフィズムとDIを覚えない限り、オブジェクト指向の良さは1ミリも引き出せませんので、
この両方は確実に学習してください。

私の書いたオブジェクト指向の記事が多少は参考になるでしょう。
分かりそうで分からない少し分かるオブジェクト指向プログラミング - Qiita

軽く解説しておくと、インスタンスは情報とメソッドをカラクリ人形です。


今回の例だとこのクラスはインスタンス化して使うことを想定していませんので、
プレーンなオブジェクトを生成したほうが良いです。

そしてオブジェクトとは微塵も関係ない所で解決できそうですね。
ちょっと作ってみましょうか。

var nodeList = {
  posts: {
    title: "#postText",
    text: "#postText",
    label: "input[name='post_label']",
    seo: "input[name='post_seo_about']"
  },
  controller: {
    image: "#Toolbar .left button[value='image']",
    link: "#Toolbar .left button[value='link']",
    table: "#Toolbar .left button[value='table']",
    widget: {
      body: "div.tableInWidget",
      input: "div.tableInWidget input",
      button: "div.tableInWidget button"
    },
    quote: "#Toolbar .left button[value='quote']"
  },
  requests: {
    public: "#Toolbar div.right button.article",
    preview: "#Toolbar div.right button.preview",
    save: "#Toolbar div.right button.save"
  }
};
var node = function (path) {
  var name = path.split(".").reduce((obj, n) => obj != null ? obj[n] : "", nodeList);
  return name;
  // 本当はquerySelectorで包んで返したいのでこっちに変更してね
  // return document.querySelector(name);
}

console.log(node("controller.image"));
// "#Toolbar .left button[value='image']"
console.log(node("requests.public"));
// "#Toolbar div.right button.article"
console.log(node("controller.widget.input"));
// "div.tableInWidget input"
console.log(node("hoge.piko"));
// ""

window.addEventListener('load', ()=>{
  // あとはやりたいことをこの中で記述する
  // 必要になったらこんな風に実行する
  // var input = node("controller.widget.input");
});

ちょっとnode関数の中身でイディオム使ってますが、
JavaScriptはnullやundefined値はプロパティを所持してないのでエラー回避の為に三項演算子使ってます。
また、文字列の.を元に再帰的にプロパティを読みに行く仕組みを作る為にreduce使ってます。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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

関連した質問

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