jQuery / JavaScriptコードを整理する最良の方法(2013)[終了]


104

問題

この回答は以前に回答済みですが、古くて最新ではありません。1つのファイルに2000行を超えるコードが含まれていますが、誰もが知っているように、特にコードを調べたり、新しい機能を追加したりする場合、これは悪い習慣です。現在と将来のために、コードをよりよく整理したいと考えています。

多数のボタン、UI要素、ドラッグ、ドロップ、アクションリスナー/ハンドラー、および複数のリスナーが同じ関数を使用する可能性のあるグローバルスコープ内の関数を備えたツール(単純なWebサイトではない)を構築していることを述べておく必要があります。

コード例

$('#button1').on('click', function(e){
    // Determined action.
    update_html();
});

... // Around 75 more of this

function update_html(){ .... }

...

その他のサンプルコード

結論

私は本当にこのコードを整理して、繰り返し使用しないで、新しい機能を追加したり、古い機能を更新したりできるようにする必要があります。一人でこれに取り組みます。一部のセレクターは100行のコードになる場合があります。その他のセレクターは1です。少し調べてみると、require.js繰り返しが多く、実際には必要以上のコードを記述していることがわかりました。私はこの基準に当てはまる可能性のある解決策を受け入れ、リソース/例へのリンクは常にプラスです。

ありがとう。


backbone.jsとrequire.jsを追加したい場合は、大変な作業になります。
ジャンティモン2013年

1
これを書くとき、あなたは自分が何を何度も繰り返していると思いますか?
マイクサミュエル


4
Angularを学ぼう!それは未来です。
OnurYıldırım2013年

2
コードは外部リンクではなく、ここにある必要があります。また、@ codereviewは、これらのタイプの質問に適した場所です。
ジョージストッカー2013年

回答:


98

あなたを助けるかもしれないし、そうでないかもしれないいくつかの簡単なことを説明します。明白なものもあれば、非常に難解なものもあります。

ステップ1:コードを区分する

コードを複数のモジュール式ユニットに分離することは、非常に優れた最初のステップです。「一緒に」機能するものを切り上げ、それらを小さな専用の収納ユニットに入れます。現時点ではフォーマットについて心配する必要はありません。インラインにしてください。構造は後のポイントです。

したがって、次のようなページがあるとします。

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

メンテナンスを容易にするために、ヘッダー関連のすべてのイベントハンドラー/バインダーがそこにあるように区分けすることは理にかなっています(そして1000行をふるいにかける必要はありません)。

その後、Gruntなどのツールを使用して、JSを1つのユニットに再構築できます。

ステップ1a:依存関係の管理

AMDと呼ばれるものを実装するには、RequireJSやCommonJSなどのライブラリを使用します。非同期モジュールロードを使用すると、コードが依存しているものを明示的に示すことができます。これにより、ライブラリ呼び出しをコードにオフロードできます。文字通り「これにはjQueryが必要です」と言うだけで、AMDがそれをロードし、jQueryが利用可能になったときにコードを実行します。

これには隠されたgemもあります。ライブラリのロードは、DOMが準備できた2番目に行われます。これにより、ページのロードが停止することはありません。

ステップ2:モジュール化

ワイヤーフレームを見ますか?2つの広告ユニットがあります。ほとんどの場合、イベントリスナーを共有します。

このステップでのタスクは、コード内の繰り返しのポイントを特定し、これをすべてモジュールに統合することです。現在、モジュールはすべてを網羅します。途中で分割します。

このステップの全体的なアイデアは、ステップ1から進み、すべてのコピーパスタを削除して、疎結合のユニットに置き換えることです。だから、代わりに:

ad_unit1.js

 $("#au1").click(function() { ... });

ad_unit2.js

 $("#au2").click(function() { ... });

私は:

ad_unit.js

 var AdUnit = function(elem) {
     this.element = elem || new jQuery();
 }
 AdUnit.prototype.bindEvents = function() {
     ... Events go here
 }

page.js

 var AUs = new AdUnit($("#au1,#au2"));
 AUs.bindEvents();

これにより、繰り返しを取り除くことに加えて、イベントマークアップを区分することができます。これはかなり適切な手順であり、これは後でさらに拡張します。

ステップ3:フレームワークを選択してください!

繰り返しをさらにモジュール化して削減したい場合は、MVC(モデル-ビュー-コントローラー)アプローチを実装する素晴らしいフレームワークがたくさんあります。私のお気に入りはBackbone / Spineですが、Angular、Yiiなどもあります...リストは続きます。

A モデルは、あなたのデータを表します。

A Viewは、あなたのマークアップとそれに関連するすべてのイベントを表し

A コントローラは、ビジネスロジックを表します-言い換えれば、コントローラは、使用する負荷に何を望むと何モデルのページに指示します。

これは重要な学習ステップになりますが、賞はそれだけの価値があります。スパゲッティよりもクリーンでモジュール化されたコードが優先されます。

あなたができることは他にもたくさんありますが、それらは単なるガイドラインとアイデアです。

コード固有の変更

ここにあなたのコードへのいくつかの特定の改善があります:

 $('.new_layer').click(function(){

    dialog("Create new layer","Enter your layer name","_input", {

            'OK' : function(){

                    var reply = $('.dialog_input').val();

                    if( reply != null && reply != "" ){

                            var name = "ln_"+reply.split(' ').join('_');
                            var parent = "";

                            if(selected_folder != "" ){
                            parent = selected_folder+" .content";
                            }

                            $R.find(".layer").clone()
                            .addClass(name).html(reply)
                            .appendTo("#layer_groups "+parent);

                            $R.find(".layers_group").clone()
                            .addClass(name).appendTo('#canvas '+selected_folder);

            }

        }

    });
 });

これは次のように書くのがよいでしょう。

$("body").on("click",".new_layer", function() {
    dialog("Create new layer", "Enter your layer name", "_input", {
         OK: function() {
             // There must be a way to get the input from here using this, if it is a standard library. If you wrote your own, make the value retrievable using something other than a class selector (horrible performance + scoping +multiple instance issues)

             // This is where the view comes into play. Instead of cloning, bind the rendering into a JS prototype, and instantiate it. It means that you only have to modify stuff in one place, you don't risk cloning events with it, and you can test your Layer stand-alone
             var newLayer = new Layer();
             newLayer
               .setName(name)
               .bindToGroup(parent);
          }
     });
});

コードの前半:

window.Layer = function() {
    this.instance = $("<div>");
    // Markup generated here
};
window.Layer.prototype = {
   setName: function(newName) {
   },
   bindToGroup: function(parentNode) {
   }
}

突然、コピー貼り付けを行わずに、コードのどこからでも標準レイヤーを作成できるようになりました。5つの異なる場所でこれを行っています。コピーと貼り付けを5つ保存しました。

もう1つ:

//アクションのルールセットラッパー

var PageElements = function(ruleSet) {
ruleSet = ruleSet || [];
this.rules = [];
for (var i = 0; i < ruleSet.length; i++) {
    if (ruleSet[i].target && ruleSet[i].action) {
        this.rules.push(ruleSet[i]);
    }
}
}
PageElements.prototype.run = function(elem) {
for (var i = 0; i < this.rules.length; i++) {
    this.rules[i].action.apply(elem.find(this.rules.target));
}
}

var GlobalRules = new PageElements([
{
    "target": ".draggable",
    "action": function() { this.draggable({
        cancel: "div#scrolling, .content",
        containment: "document"
        });
    }
},
{
    "target" :".resizable",
    "action": function() {
        this.resizable({
            handles: "all",
            zIndex: 0,
            containment: "document"
        });
    }
}

]);

GlobalRules.run($("body"));

// If you need to add elements later on, you can just call GlobalRules.run(yourNewElement);

これは、標準ではないイベントや作成イベントがある場合に、ルールを登録する非常に強力な方法です。これは、pub / sub通知システムと組み合わせた場合や、要素を作成するたびに発生するイベントにバインドされた場合にも非常に重要です。Fire'n'forgetモジュールイベントバインディング!


2
@ジェシカ:なぜオンラインツールは何か違うのですか?アプローチは今でも同じです。コンパートメント化/モジュール化し、フレームワークを使用してコンポーネント間の疎結合を促進します(最近はすべてイベント委任が付属しています)、コードを分割します。そこであなたのツールに当てはまらないものは何ですか?ボタンがたくさんあるということですか?
セバスチャンルノー

2
@ジェシカ:更新されました。と同様の概念を使用して、レイヤーの作成を簡略化および合理化しましたView。そう。これはあなたのコードにどのように適用されませんか?
セバスチャンルノー

10
@ジェシカ:最適化せずにファイルに分割することは、ジャンクを保管するための引き出しをさらに購入するようなものです。ある日、あなたは片付けなければなりません、そして引き出しがいっぱいになる前に片付けが簡単です。なぜ両方しないのですか?今、あなたのようなルックスがよいでしょうlayers.jssidebar.jsglobal_events.jsresources.jsfiles.jsdialog.jsあなたは自分のコードを分割するつもりなら。使用grunt1にそれらを再構築すると、Google Closure Compilerコンパイルして最小限に抑えます。
セバスチャンルノー

3
require.jsを使用する場合は、r.jsオプティマイザーも確認する必要があります。これが、require.jsを使用する価値がある理由です。すべてのファイルを組み合わせて最適化します:requirejs.org/docs/optimization.html
Willem D'Haeseleer

2
@SébastienRenauldあなたの回答とコメントは他のユーザーから非常に高く評価されています。
それで

13

require.jsを使用して、現在のコードベースを複数のファイルに分割する簡単な方法を次に示します。コードを2つのファイルに分割する方法を示します。その後、ファイルを追加するのは簡単です。

ステップ1)コードの上部で、Appオブジェクト(またはMyGameのような任意の名前)を作成します。

var App = {}

ステップ2)トップレベルのすべての変数と関数を変換して、Appオブジェクトに属させます。

の代わりに:

var selected_layer = "";

あなたが欲しい:

App.selected_layer = "";

の代わりに:

function getModified(){
...
}

あなたが欲しい:

App.getModified = function() {

}

この時点では、次の手順を完了するまでコードは機能しません

ステップ3)すべてのグローバル変数と関数参照を変換して、アプリを通過します。

次のようなものを変更します:

selected_layer = "."+classes[1];

に:

App.selected_layer = "."+classes[1];

そして:

getModified()

に:

App.GetModified()

ステップ4)この段階でコードをテストします-すべて機能するはずです。何かを見逃したため、最初はいくつかのエラーが表示される可能性があるため、先に進む前にそれらを修正してください。

ステップ5) requirejsを設定します。私はあなたがウェブサーバーから提供されたウェブページを持っていると仮定します、そのコードは以下にあります:

www/page.html

とjquery in

www/js/jquery.js

これらのパスがこれとまったく同じでない場合、以下は機能せず、パスを変更する必要があります。

requirejsをダウンロードし、require.jswww/jsディレクトリに配置します。

で、page.htmlすべてのスクリプトタグを削除し、次のようなスクリプトタグを挿入します。

<script data-main="js/main" src="js/require.js"></script>

www/js/main.jsコンテンツで作成:

require.config({
 "shim": {
   'jquery': { exports: '$' }
 }
})

require(['jquery', 'app']);

次に、手順1〜3で修正したすべてのコード(グローバル変数のみがAppである必要があります)を次の場所に配置します。

www/js/app.js

そのファイルの最上部に、次のように記述します。

require(['jquery'], function($) {

下に置く:

})

次に、ブラウザにpage.htmlをロードします。アプリは動作するはずです!

手順6)別のファイルを作成する

これがあなたの仕事が報われるところです、あなたはこれを何度も行うことができます。

www/js/app.js$とAppを参照するコードをいくつか引き出します。

例えば

$('a').click(function() { App.foo() }

中に入れて www/js/foo.js

そのファイルの最上部に、次のように記述します。

require(['jquery', 'app'], function($, App) {

下に置く:

})

次に、www / js / main.jsの最後の行を次のように変更します。

require(['jquery', 'app', 'foo']);

それでおしまい!コードを独自のファイルに配置するたびに、これを実行してください。


これには複数の問題があります-明らかな問題は、最後にすべてのファイルを断片化し、プリプロセッサを使用しないことにより、ページの読み込みごとにスクリプトごとに 400バイトの無駄なデータをすべてのユーザーに強制することr.jsです。さらに、OPの問題に実際に対処していない-単に一般的なrequire.jsハウツーを提供しただけである。
セバスチャンルノー

7
えっ?私の答えはこの質問に固有のものです。そして明らかにr.jsが次のステップですが、ここでの問題は最適化ではなく組織化です。
リンヘッドリー2013年

私はこの回答が好きです。require.jsを使用したことがないので、それを使用してメリットを得ることができるかどうかを確認する必要があります。私は重くモジュールパターンを使用しますが、おそらくこれは外に抽象化するいくつかのことを私を許可し、その後でそれらを必要とします。
トニー・

1
@SébastienRenauld:この答えは、require.jsだけではありません。主に、作成しているコードの名前空間を持つことについて話します。私はあなたが良い部分に感謝し、それで何か問題を見つけたら編集をするべきだと思います。:)
mithunsatheesh

10

あなたの質問とコメントのために、あなたはあなたのコードをBackboneのようなフレームワークに移植したり、Requireのようなローダーライブラリを使用したりしないことを想定しています。できるだけ簡単な方法で、すでに持っているコードを取り戻すためのより良い方法が欲しいだけです。

対象のセクションを見つけるために2000行以上のコードをスクロールするのは面倒だと私は理解しています。解決策は、コードを機能ごとに1つずつ、異なるファイルに分割することです。たとえばsidebar.jscanvas.jsGruntを使用して本番環境に結合し、Useminと一緒に次のようなものを作成できます。

あなたのhtmlで:

<!-- build:js scripts/app.js -->
<script src="scripts/sidebar.js"></script>
<script src="scripts/canvas.js"></script>
<!-- endbuild -->

あなたのGruntfileで:

useminPrepare: {
  html: 'app/index.html',
  options: {
    dest: 'dist'
  }
},
usemin: {
  html: ['dist/{,*/}*.html'],
  css: ['dist/styles/{,*/}*.css'],
  options: {
    dirs: ['dist']
  }
}

Yeomanを使用する場合は、このすべての定型コードが提供されます。

次に、各ファイル自体について、ベストプラクティスに従い、すべてのコードと変数がすべてそのファイル内にあり、他のファイルに依存していないことを確認する必要があります。これは、あるファイルの関数を他のファイルから呼び出せないという意味ではありません。ポイントは、変数と関数をカプセル化することです。名前空間に似たもの。すべてのコードをオブジェクト指向に移植したくないと仮定しますが、少しリファクタリングしてもかまわない場合は、モジュールパターンと呼ばれるものと同等のものを追加することをお勧めします。次のようになります。

sidebar.js

var Sidebar = (function(){
// functions and vars here are private
var init = function(){
  $("#sidebar #sortable").sortable({
            forceHelperSize: true,
            forcePlaceholderSize: true,
            revert: true,
            revert: 150,
            placeholder: "highlight panel",
            axis: "y",
            tolerance: "pointer",
            cancel: ".content"
       }).disableSelection();
  } 
  return {
   // here your can put your "public" functions
   init : init
  }
})();

次に、このコードを次のようにロードできます。

$(document).ready(function(){
   Sidebar.init();
   ...

これにより、コードを書き直す必要がなく、保守しやすいコードを作成できます。


1
最後から2番目のスニペットを真剣に再検討することをお勧めします。これは、コードをインラインで記述することと同じです。モジュールにはが必要#sidebar #sortableです。コードをインライン化して2つのIETFを保存するだけで、メモリを節約することもできます。
セバスチャンルノールト2013年

重要なのは、必要なコードは何でも使用できるということです。私は元のコードからの例を使用しています
ヘスス・カレラ

私はイエスに同意します。これは単なる例です。OPは、要素のセレクターをハードコーディングする代わりに指定できるようにするオプション「オブジェクト」を簡単に追加できますが、これは簡単な例にすぎません。私はモジュールパターンが大好きだと言いたいのですが、これは私が使用する主要なパターンですが、それでも、コードをよりよく整理しようとしていると言っています。私は通常C#を使用しているので、関数の命名と作成は非常に一般的に感じられます。アンダースコアがローカルでプライベートであるように「パターン」を維持しようとします。変数は単なる「オブジェクト」であり、パブリックであるリターンで関数を参照します。
トニー

しかし、このアプローチにはまだ課題があり、これを行うためのより良い方法を望んでいます。しかし、他のjsとの競合を引き起こすためにグローバルスペースで変数と関数を宣言するよりもはるかにうまく機能します。笑
トニー

6

JavaScript MVCフレームワークを使用して、JavaScriptコードを標準的な方法で整理します。

利用可能な最良のJavaScript MVCフレームワークは次のとおりです。

JavaScript MVCフレームワークを選択するには、考慮すべき多くの要素が必要でした。プロジェクトに重要な要素に基づいて最適なフレームワークを選択するのに役立つ次の比較記事を読んでください。http//sporto.github.io/blog/2013/04/12/comparison-angular-backbone-can-ember/

フレームワークでRequireJSを使用して、非同期jsファイルとモジュールのロードをサポートすることもできます。
JSモジュールのロードを開始するには、以下を参照してください。http
//www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/


4

コードを分類します。この方法は私に大いに役立ち、どんなjsフレームワークでも動作します:

(function(){//HEADER: menu
    //your code for your header
})();
(function(){//HEADER: location bar
    //your code for your location
})();
(function(){//FOOTER
    //your code for your footer
})();
(function(){//PANEL: interactive links. e.g:
    var crr = null;
    $('::section.panel a').addEvent('click', function(E){
        if ( crr) {
            crr.hide();
        }
        crr = this.show();
    });
})();

お好みのエディター(最高はKomodo Edit)で、すべてのエントリを折りたたむことで折りたたむことができ、タイトルのみが表示されます。

(function(){//HEADER: menu_____________________________________
(function(){//HEADER: location bar_____________________________
(function(){//FOOTER___________________________________________
(function(){//PANEL: interactive links. e.g:___________________

2
ライブラリに依存しない標準JSソリューションの+1。
ホバーウィッキー2013年

複数の理由により-1。同等のコードは、OPのコードとまったく同じです... +「セクション」ごとに1つのIETF。さらに、モジュール開発者がそれらの作成/削除をオーバーライドしたり、動作を拡張したりすることを許可せずに、過度に広範なセレクターを使用しています。最後に、IETFは無料ではありません。
セバスチャン

@hobberwickey:あなたのことは知りませんが、コミュニティ主導で、できればバグがすぐに見つかるようなものに頼りたいと思います。特にそうでなければ、私は車輪を再発明することを非難します。
セバスチャンルノー

2
これは、コードを個別のセクションに編成するだけです。前回確認したのは、A:良い習慣であり、B:コミュニティがサポートするライブラリが本当に必要なものではありません。すべてのプロジェクトがBackboneやAngularなどに適合するわけではありません。関数にラップしてコードをモジュール化することは、優れた一般的なソリューションです。
ホバーウィッキー2013年

お気に入りのライブラリに依存してこのアプローチを使用することはいつでも可能です。しかし、上記のソリューションは、純粋なjavascript、カスタムライブラリ、または有名なjsフレームワークで機能します

3

私はお勧めします:

  1. イベント管理用のパブリッシャー/サブスクライバーパターン。
  2. オブジェクト指向
  3. 名前空間

あなたの場合ジェシカでは、インターフェースをページまたは画面に分割します。ページまたは画面はオブジェクトであり、一部の親クラスから拡張できます。PageManagerクラスを使用して、ページ間の相互作用を管理します。


例/リソースでこれを拡張できますか?
キビリアス2013年

1
「オブジェクト指向」とはどういう意味ですか?JSでのほとんどすべてのものがあるオブジェクト。そしてJSにはクラスがありません。
Bergi

2

バックボーンのようなものを使用することをお勧めします。バックボーンはRESTFULでサポートされているJavaScriptライブラリです。Ikはコードをよりクリーンで読みやすくし、requirejsと一緒に使用すると強力です。

http://backbonejs.org/

http://requirejs.org/

バックボーンは実際のライブラリではありません。それはあなたのJavaScriptコードに構造を与えることを意味します。jquery、jquery-ui、google-mapsなどの他のライブラリを含めることができます。私の考えでは、バックボーンは、オブジェクト指向およびモデルビューコントローラの構造に最も近いjavascriptアプローチです。

また、ワークフローについて.. PHPでアプリケーションを構築する場合は、Laravelライブラリを使用してください。RESTfullの原則とともに使用すると、Backboneで問題なく動作します。LaravelフレームワークへのリンクとRESTfull APIの構築に関するチュートリアルの下:

http://maxoffsky.com/code-blog/building-restful-api-in-laravel-start-here/

http://laravel.com/

以下はnettutsのチュートリアルです。Nettutsには、高品質のチュートリアルがたくさんあります。

http://net.tutsplus.com/tutorials/javascript-ajax/understanding-backbone-js-and-the-server/


0

yeoman http://yeoman.io/などのツールを使用して、開発ワークフロー全体の実装を開始するときかもしれません。これは、すべての依存関係、ビルドプロセス、および必要に応じて自動テストを制御するのに役立ちます。最初は多くの作業が必要ですが、一度実装すると、将来の変更が非常に簡単になります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.