Web上のJavaScriptのMVC実装は非常に少ないため、JavaScript実装におけるこのアプローチの利点を明らかにしたいと思います。
MVCを使用することは、ビジネスアプリケーション、ゲーム、管理者タイプのインターフェース、またはGmail / Googleドキュメントのようなものにより適していると事前に言います。 1000個のバナーと飛ぶ雪片が画面に表示されるプロモーションサイトがある場合、MVCの使用は無意味で有害ですらあります。
ちょっとした歴史:
MVC(Model View Controller)は、1979年にSmallTalk言語についても説明されました。
それ以来、インターフェースでは新しいものは何も発明されていません。 テンプレートが改善され、補足され、拡張されました。 プラットフォームの機能と
HMVC (階層モデルビューコントローラー)、
MVP (モデルビュープレゼンター)、
MVVM (モデルビューViewModel)など、アプリケーションの記述言語に応じてフォロワーが登場しましたが、本質は変わりません-データソースの変更とコマンドからの分離関数とその弱い結合。
1979年にテンプレートが表示されて以来、最初のレビューは次のようなものであったことを事前に言います。「
なぜそれが必要なのでしょうか。それですべてがうまく機能します。 」、2番目のレビュー: 「そして最後に、3番目:」
モデルが変更されるたびに、ビューが更新されます。すべてが遅くなります! 」 したがって、この記事を読むときに同様の質問があることに驚かないでください。 すべてにかかわらず、MVCとそのフォロワーは、かなりの数のタスクについて非常に成功し、実績のあるソリューションになりました。 これもご覧ください。
タスク :値を追加および削除するための、単純なインタラクティブページ(値のリスト、2つのボタン)を作成します。 従来のアプローチは非常に単純です-HTMLファイルを作成します。 選択はリストに使用され、ボタンは操作に使用されます。 ボタンのonClick属性では、JavaScript関数呼び出しを規定しています。 selectから値を削除するには、DOM selectオブジェクトを使用し、selectedIndexプロパティを要求して、対応するオブジェクトを削除します。
<html><head><script> // <![CDATA[ function addItem (value) { if (!value) { return; } var myselect = document.getElementById('myselect'); var newoption = myselect.appendChild(document.createElement('option')); newoption.value = value; newoption.innerHTML=value; } function removeCurrentItem () { var myselect = document.getElementById('myselect'); if (myselect.selectedIndex === -1) { return; } var selectedOption = myselect.options[myselect.selectedIndex]; selectedOption.parentNode.removeChild(selectedOption); } // ]]> </script> </head> <body> <select id="myselect" size="4"></select> <button onClick="addItem(prompt('enter value'))">+</button> <button onClick="removeCurrentItem()" />-</button> </body> </html>
すべてがシンプルで問題なく動作します。 なぜ他の何かが必要なのですか? このアプリケーションの場合、問題は予想されませんが、統計によると、実際にコードを記述する時間は約25%で、残りの75%のプログラマーは開発、プロジェクトのサポート、新しい機能の追加を行っています。 また、アプリケーションがより複雑になり、数が増えると、コードの維持がますます難しくなり、バグの数が増えます。
「Jquery / Dojo / MooTools / My_Love_Ajax_Libraryを使用して実行します」とあなたは言います。 たとえば、次のように:
<html><head> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script> <script> // <![CDATA[ $(document).ready(function(){ $('#plus').bind('click', function(){ var value = prompt('add value'); if (!value) { return; } $('<option>').html(value).appendTo($('#myselect')); }); $('#minus').bind('click', function(){ var myselect = $('#myselect'); if (myselect.attr('selectedIndex') === -1) { return; } myselect.children().remove(':selected'); }); }); // ]]> </script> </head> <body> <select id="myselect" size="4"></select> <button id="plus">+</button> <button id="minus"/>-</button> </body> </html>
それはさらに良くなり、コードはよりコンパクトになり、とてもクールに見えます。 ただし、これは主な問題を解決しませんでした。データ(リスト)はユーザーインターフェイス内、select要素のhtml内に保存されます。 2番目の欠点は、htmlおよびjavascriptコードのハッシュがあることです。 そのシンプルさにもかかわらず、開発は不十分です。 たとえば、上司が私たちのところに来て、「リストで何かが強調表示されている場合にのみマイナスボタンを表示したい」という最も単純な要求を言います。 すぐに、プログラマーとして、問題が発生します。この変更を行う場所-HTMLまたはJavaScript。 「うーん、htmlでボタンをオフにします」とあなたは言います。 また、休暇中に同僚はJavaScript内で同様の操作を行います。
一見単純な変更がコードを非常に混乱させ始めることが判明したため、これを避けたいと思います。
従来のMVC実装では、基本的な原則は次のとおりです。
- 弱い結合
- モデルは誰についても何も知りません。 モデルは、必要に応じて、たとえば、Viewをリッスンできるアラートを送信します。
- ビューはモデルについては知っていますが、変更することはできません。ビューはコントローラーを操作できます
- コントローラーはモデルについて知っており、それを変更できます。また、ビューについても知っており、それを変更できます(それら)。
この場合、違いは次のとおりです。
- モデルは誰についても何も知りません。 アラートを送信できます
- ビューはモデルについてのみ知っている
- コントローラーは、ビューとモデルについて認識しています。
ご覧のとおり、従来のテンプレートとは異なり、このテンプレートはより厳格です。 ビューはコントローラーについて何も知らないので、オブジェクト(ViewとController)をクロスリンクするという考えは好きではありません。今のところ、そのようなリンクなしでやろうとすることができます。 たとえば、Orthodox MVC(Orthodox MVC)と呼びましょう
モデル
この場合のモデルは、入力したオブジェクト(文字列)の配列を単に格納します。 「現在の」オブジェクトのシリアル番号も保存されます。 オブジェクトを追加および削除するための標準メソッドもあります:addItem、removeCurrentItem、これらなしでは実行できません。 モデルには、その変更を通知する必要があります。
アラートを送信するには、サブジェクトタイプのオブジェクト(modelChangedSubject)を使用します。これらのオブジェクトをいくつか作成し、「モデルが変更されました」だけでなく、「モデルXプロパティが変更されました」通知をサブスクライバーに送信できます。 これにより、ビューが完全に更新されるのではなく、モデル内で実際に変更された領域のみが更新されます。
jqueryでサブジェクトを作成するための標準関数が見つからなかったため、完成したmakeObservableSubjectを
カナダのプログラマーによる優れたmvc記事から取りました。
... OMVC.Model = function () { var that = this; var items = []; this.modelChangedSubject = OMVC.makeObservableSubject(); this.addItem = function (value) { if (!value) { return; } items.push(value); that.modelChangedSubject.notifyObservers(); }; this.removeCurrentItem = function () { if (that.selectedIndex === -1) { return; } items.splice(that.selectedIndex, 1); that.modelChangedSubject.notifyObservers(); }; this.getItems = function () { return items; }; this.selectedIndex = -1; this.getSelectedIndex = function () { return that.selectedIndex; } this.setSelectedIndex = function (value) { that.selectedIndex = value; that.modelChangedSubject.notifyObservers(); } }; ...
表示する
ご覧のとおり、htmlには「空白」の本文があり、すべての要素は「手動」で描画されます。 JavaScriptを使用します。 Gmailを見てください-それも同じです。 すべての要素:ボタン、リストはjavascriptで描画されます。
ビューはモデルについて知っていますが、理想的には、ビューは読み取り専用モードでモデルにアクセスできる必要があります。 本当にしたい場合でも、心がremoveCurrentItemを呼び出すことを許可することはできません。
残念ながら、JavaScriptで、ある関数が別のオブジェクトへの変更を許可し、他の関数に読み取り専用アクセスのみを許可する方法をまだ理解していません。 これを達成する方法についてアイデアがあれば、共有してください。
... OMVC.View = function (model, rootObject) { var that = this; that.select = $('<select/>').appendTo(rootObject); that.select.attr('size', '4'); that.buttonAdd = $('<button>+</button>').appendTo(rootObject).height(20); that.buttonRemove = $('<button>-</button>').appendTo(rootObject).height(20); model.modelChangedSubject.addObserver(function () { var items = model.getItems(); var innerHTML = ''; for (var i = 0; i<items.length; i += 1) { innerHTML += "<option>"+items[i]+"</option>"; } that.select.html(innerHTML); }); }; ...
コントローラー
このコントローラーでは、モデルとビューをリンクするだけで、ビューオブジェクトにイベントを掛けるだけです。
... OMVC.Controller = function (model, view) { view.buttonAdd.bind('click', function () { model.addItem(prompt('addvalue')); }); view.buttonRemove.bind('click', function () { model.removeCurrentItem(); }); view.select.bind('click', function () { model.setSelectedIndex(view.select[0].selectedIndex); }); }; ...
全文テキスト:
<!doctype html> <html> <body> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script> <script> // <![CDATA[ var OMVC = {}; OMVC.makeObservableSubject = function () { var observers = []; var addObserver = function (o) { if (typeof o !== 'function') { throw new Error('observer must be a function'); } for (var i = 0, ilen = observers.length; i < ilen; i += 1) { var observer = observers[i]; if (observer === o) { throw new Error('observer already in the list'); } } observers.push(o); }; var removeObserver = function (o) { for (var i = 0, ilen = observers.length; i < ilen; i += 1) { var observer = observers[i]; if (observer === o) { observers.splice(i, 1); return; } } throw new Error('could not find observer in list of observers'); }; var notifyObservers = function (data) { // Make a copy of observer list in case the list // is mutated during the notifications. var observersSnapshot = observers.slice(0); for (var i = 0, ilen = observersSnapshot.length; i < ilen; i += 1) { observersSnapshot[i](data); } }; return { addObserver: addObserver, removeObserver: removeObserver, notifyObservers: notifyObservers, notify: notifyObservers }; }; OMVC.Model = function () { var that = this; var items = []; this.modelChangedSubject = OMVC.makeObservableSubject(); this.addItem = function (value) { if (!value) { return; } items.push(value); that.modelChangedSubject.notifyObservers(); }; this.removeCurrentItem = function () { if (that.selectedIndex === -1) { return; } items.splice(that.selectedIndex, 1); that.modelChangedSubject.notifyObservers(); }; this.getItems = function () { return items; }; this.selectedIndex = -1; this.getSelectedIndex = function () { return that.selectedIndex; } this.setSelectedIndex = function (value) { that.selectedIndex = value; that.modelChangedSubject.notifyObservers(); } }; OMVC.View = function (model, rootObject) { var that = this; that.select = $('<select/>').appendTo(rootObject); that.select.attr('size', '4'); that.buttonAdd = $('<button>+</button>').appendTo(rootObject).height(20); that.buttonRemove = $('<button>-</button>').appendTo(rootObject).height(20); model.modelChangedSubject.addObserver(function () { var items = model.getItems(); var innerHTML = ''; for (var i = 0; i<items.length; i += 1) { innerHTML += "<option>"+items[i]+"</option>"; } that.select.html(innerHTML); }); }; OMVC.Controller = function (model, view) { view.buttonAdd.bind('click', function () { model.addItem(prompt('addvalue')); }); view.buttonRemove.bind('click', function () { model.removeCurrentItem(); }); view.select.bind('click', function () { model.setSelectedIndex(view.select[0].selectedIndex); }); }; $(document).ready(function () { var model = new OMVC.Model(); var view = new OMVC.View(model, $('<div/>').appendTo($("body"))); var controller = new OMVC.Controller(model, view); }); // ]]> </script> </body> </html>
おわりに
最後に、
問題は 、MVC実装が最初の標準または改良されたjQuery-evよりも優れている点は何ですか? 私たちは3倍のコードを書きましたが、何のためですか?
回答 :このような単純な例でも、MVCテンプレートの利点は徐々に顕著になり始めています。
- すべての要素へのリンクはすでにビューに保存されているため、後でdocument.getElementByIdおよび同様の関数によってそれらを受信するためにid要素をインターフェイスに割り当てる必要はありません。htmlドキュメントでそれらを検索する必要はありません。
- すべてのオブジェクトはrootObjectエレメント内に描画され、ビューはrootObjectに対して何度でも簡単に使用できます。
- html本文には何も保存されません。 もちろん、そこには何かがありますが、それにはまったく興味がありません。変更前は、htmlとJavaScript関数の2つの場所を探して実行する必要がありました。 これですべてがJavaScriptでのみ行われ、さらにデザインはViewオブジェクトにのみ保存されます。
- 変更は非常に簡単です。 たとえば、上記の「ボス」リクエストは次のように実装されます。
- selectedIndexを変更するサブジェクトを作成します
this.selectedIndexChangedSubject = OMVC.makeObservableSubject(); - 変更する場合は、そのことを通知します。selectedIndexChangedSubject.notifyObservers();
- そして、フォームでselectedIndexChangedSubjectアラートをサブスクライブします
バックライト付きの機能を追加した完全な最終バージョン:
<!doctype html> <html> <body> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script> <script> // <![CDATA[ var OMVC = {}; OMVC.makeObservableSubject = function () { var observers = []; var addObserver = function (o) { if (typeof o !== 'function') { throw new Error('observer must be a function'); } for (var i = 0, ilen = observers.length; i < ilen; i += 1) { var observer = observers[i]; if (observer === o) { throw new Error('observer already in the list'); } } observers.push(o); }; var removeObserver = function (o) { for (var i = 0, ilen = observers.length; i < ilen; i += 1) { var observer = observers[i]; if (observer === o) { observers.splice(i, 1); return; } } throw new Error('could not find observer in list of observers'); }; var notifyObservers = function (data) { // Make a copy of observer list in case the list // is mutated during the notifications. var observersSnapshot = observers.slice(0); for (var i = 0, ilen = observersSnapshot.length; i < ilen; i += 1) { observersSnapshot[i](data); } }; return { addObserver: addObserver, removeObserver: removeObserver, notifyObservers: notifyObservers, notify: notifyObservers }; }; OMVC.Model = function () { var that = this; var items = []; this.modelChangedSubject = OMVC.makeObservableSubject(); this.addItem = function (value) { if (!value) { return; } items.push(value); that.modelChangedSubject.notifyObservers(); }; this.removeCurrentItem = function () { if (that.selectedIndex === -1) { return; } items.splice(that.selectedIndex, 1); if (items.length === 0) { that.setSelectedIndex(-1); } that.modelChangedSubject.notifyObservers(); }; this.getItems = function () { return items; }; this.selectedIndex = -1; this.getSelectedIndex = function () { return that.selectedIndex; } this.selectedIndexChangedSubject = OMVC.makeObservableSubject(); this.setSelectedIndex = function (value) { that.selectedIndex = value; that.selectedIndexChangedSubject.notifyObservers(); } }; OMVC.View = function (model, rootObject) { var that = this; that.select = $('<select/>').appendTo(rootObject); that.select.attr('size', '4'); that.buttonAdd = $('<button>+</button>').appendTo(rootObject).height(20); that.buttonRemove = $('<button>-</button>').appendTo(rootObject).height(20).fadeOut(); model.modelChangedSubject.addObserver(function () { var items = model.getItems(); var innerHTML = ''; for (var i = 0; i<items.length; i += 1) { innerHTML += "<option>"+items[i]+"</option>"; } that.select.html(innerHTML); }); model.selectedIndexChangedSubject.addObserver(function () { if(model.getSelectedIndex() === -1) { that.buttonRemove.fadeOut(); } else { that.buttonRemove.fadeIn(); } }); }; OMVC.Controller = function (model, view) { view.buttonAdd.bind('click', function () { model.addItem(prompt('addvalue')); }); view.buttonRemove.bind('click', function () { model.removeCurrentItem(); }); view.select.bind('click', function () { model.setSelectedIndex(view.select[0].selectedIndex); }); }; $(document).ready(function () { var model = new OMVC.Model(); var view = new OMVC.View(model, $('<div/>').appendTo($("body"))); var controller = new OMVC.Controller(model, view); }); // ]]> </script> </body> </html>
以前はインタラクティブなHTMLページがありましたが、今ではHTMLのグラフィカルシェルを備えたjavascriptアプリケーションがあります。 違いを感じてください。