そしお再びMVCに぀いお

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実装では、基本的な原則は次のずおりです。

  1. 匱い結合
  2. モデルは誰に぀いおも䜕も知りたせん。 モデルは、必芁に応じお、たずえば、Viewをリッスンできるアラヌトを送信したす。
  3. ビュヌはモデルに぀いおは知っおいたすが、倉曎するこずはできたせん。ビュヌはコントロヌラヌを操䜜できたす
  4. コントロヌラヌはモデルに぀いお知っおおり、それを倉曎できたす。たた、ビュヌに぀いおも知っおおり、それを倉曎できたすそれら。


この堎合、違いは次のずおりです。

  1. モデルは誰に぀いおも䜕も知りたせん。 アラヌトを送信できたす
  2. ビュヌはモデルに぀いおのみ知っおいる
  3. コントロヌラヌは、ビュヌずモデルに぀いお認識しおいたす。


ご芧のずおり、埓来のテンプレヌトずは異なり、このテンプレヌトはより厳栌です。 ビュヌはコントロヌラヌに぀いお䜕も知らないので、オブゞェクト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テンプレヌトの利点は埐々に顕著になり始めおいたす。

  1. すべおの芁玠ぞのリンクはすでにビュヌに保存されおいるため、埌でdocument.getElementByIdおよび同様の関数によっおそれらを受信するためにid芁玠をむンタヌフェむスに割り圓おる必芁はありたせん。htmlドキュメントでそれらを怜玢する必芁はありたせん。
  2. すべおのオブゞェクトはrootObject゚レメント内に描画され、ビュヌはrootObjectに察しお䜕床でも簡単に䜿甚できたす。
  3. html本文には䜕も保存されたせん。 もちろん、そこには䜕かがありたすが、それにはたったく興味がありたせん。倉曎前は、htmlずJavaScript関数の2぀の堎所を探しお実行する必芁がありたした。 これですべおがJavaScriptでのみ行われ、さらにデザむンはViewオブゞェクトにのみ保存されたす。
  4. 倉曎は非垞に簡単です。 たずえば、䞊蚘の「ボス」リク゚ストは次のように実装されたす。

    • 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アプリケヌションがありたす。 違いを感じおください。




All Articles