MVC是一種設計模式,它將應用程式劃分為3個部分:資料(模型)、展示層(視圖)和使用者互動層。結合下圖,更能理解三者之間的關係。
換句話說,一個事件的發生是這樣的過程
使用者和應用程式互動
#控制器的事件處理器被觸發
控制器從模型中請求數據,並將其交給視圖
視圖將資料呈現給使用者
模型:用來存放應用程式的所有資料物件。模型不必知道視圖和控制器的細節,模型只需包含資料及直接和這些資料相關的邏輯。任何事件處理程式碼、視圖模版,以及那些和模型無關的邏輯都應隔離在模型之外。
視圖:視圖層是呈現給使用者的,使用者會與之產生互動。在javaScript應用程式中,視圖大都是由html、css和JavaScript模版組成的。除了模版中簡單的條件語句之外,視圖不應包含任何其他邏輯。事實上和模型類似,視圖也應該從應用的其他部分解耦出來
控制器:控制器是模型和視圖的紐帶。控制器從視圖獲得事件和輸入,對它們進行處理,並相應地更新視圖。當頁面載入時,控制器會為視圖新增事件監聽,例如監聽表單提交和按鈕點擊。然後當使用者和應用程式產生互動時,控制器中的事件觸發器就會開始工作。
例如JavaScript框架早期框架backbone就是採用的MVC模式。
上面的例子似乎太過空洞,下面講一個生活中的例子進行講解:
1、用戶提交一個新的聊天資訊
2、控制器的事件處理器被觸發
3、控制器創建了一個新的聊天模型
4、然後控制器更新視圖
5、用戶在聊天視窗看到新的聊天資訊
講了一個生活的例子,我們用程式碼的方式更加深入了解MVC。
Model
MVC中M表示model,與資料操作和行為相關的邏輯都應放入模型中。例如我們建立一個Model對象,所有資料的操作都應該都放在這個命名空間中。以下是一些簡化的程式碼,首先創建新模型和實例
var Model = { create: function() { this.records = {} var object = Object.create(this) object.prototype = Object.create(this.prototype) return object } }
create用於創建一個以Model為原型的對象,然後就是一些包括資料操作的一些函數包括查找,存儲
var Model = { /*---代码片段--*/ find: function () { return this.records[this.id] }, save: function () { this.records[this.id] = this } }
下面我們就可以使用這個Model了:
user = Model.create() user.id = 1 user.save() asset = Model.create() asset.id = 2 asset.save() Model.find(1) => {id:1}
可以看到我們就已經查找到這個物件了。模型也就是資料的部分我們也就完成了。
Control
下面來講關於mvc中的控制器。當載入頁面的時候,控制器將事件處理程序綁定在視圖中,並適時地處理回調,以及和模型必要的對接。以下是控制器的簡單範例:
var ToggleView = { init: function (view) { this.view = $(view) this.view.mouseover(this.toggleClass, true) this.view.mouseout(this.toggleClass, false) }, this.toggleClass: function () { this.view.toggleClass('over', e.data) } }
這樣我們就實作了對一個視圖的簡單控制,滑鼠移入元素新增over class,移除就移除over class。然後在加入一些簡單的樣式例如
ex: .over {color: red} p{color: black} 这样控制器就和视图建立起了连接。在MVC中有一个特性就是一个控制器控制一个视图,随着项目体积的增大,就需要一个状态机用于管理这些控制器。先来创建一个状态机 var StateMachine = function() {} SateMachine.add = function (controller) { this.bind('change', function (e, current) { if (controller == current) { controller.activate() } else { controller.deactivate() } }) controller.active = function () { this.trigger('change', controller) } } // 创建两个控制器 var con1 = { activate: funtion() { $('#con1').addClass('active') }, deactivate: function () { $('#con1').removeClass('active') } } var con2 = { activate: funtion() { $('#con2').addClass('active') }, deactivate: function () { $('#con2').removeClass('active') } } // 创建状态机,添加状态 var sm = new StateMachine sm.add(con1) sm.add(con2) // 激活第一个状态 con1.active()
這樣就實作了簡單的控制器管理,最後我們在加入一些css樣式。
#con1, #con2 { display: none } #con2.active, #con2.active { display: block }
當con1啟動的時候樣式就發生了變化,也就是視圖發生了變化。
控制器也就講到了這裡,下面來看看MVC中的View部分,也就是視圖
View
視圖是應用的接口,它為用戶提供視覺呈現並與使用者產生互動。在javaScript種,視圖是無邏輯的HTML片段,又套用的控制器來管理,視圖處理事件回呼以及內嵌資料。簡單來說就是在javaScript中寫HTML程式碼,然後將HTML片段插入到HTML頁面中,這裡講兩種方法:
動態渲染視圖
使用document.createElement建立DOM元素,設定他們的內容然後追加到頁面中,例如
var views = documents.getElementById('views')
views.innerHTML = '' // 元素清空
#var wapper = document.createElement('p ')
wrapper.innerText = 'add to views'
views.appendChild(wrapper)
這樣就完成了用createElement建立元素,然後再加入HTML頁面。
範本
如果以前有後端開發經驗,那麼對模版應該比較熟悉。例如在nodejs中常用的就是ejs,下面是ejs的小例子,可以看到的是ejs將javascript直接渲染為HTML
str = '
h1>'
ejs.render(str, {title: 'ejs'
});
那么这个渲染后的结果就是
ejs
当然实际中ejs的功能更强大,我们甚至可以在其中加入函数,模板语言是不是觉得跟vue,React的书写方式特别像,我也觉得像。那么view的作用就显而易见了,就是将HTML和javaScript连接起来。剩下一个问题就是在mvc原理图我们看到了视图和模型之间的关系,当模型更改的时候,视图也会跟着更新。那么视图和模型就需要进行绑定,它意味着当记录发生改变时,你的控制器不需要处理视图的更新,因为这些更新是在后台自动完成的。为了将javaScript对象和视图绑定在一起,我们需要设置一个回调函数,当对象的属性发生改变时发送一个更新视图的通知。下面是值发生变化的时候调用的回调函数,当然现在我们可以使用更简单的set,get进行数据的监听,这在我们后面的MVVM将会讲到。
var addChange = function (ob) { ob.change = function (callback) { if (callback) { if (!this._change) this._change = {} this._change.push(callback) } else { if (!this._change) return for (var i = this._change.length - 1; i >= 0; i--) { this._change[i].apply(this) } } } }
我们来看看一个实际的例子
var addChange = function (ob) { ob.change = function (callback) { if (callback) { if (!this._change) this._change = {} this._change.push(callback) } else { if (!this._change) return for (var i = this._change.length - 1; i >= 0; i--) { this._change[i].apply(this) } } } } var object = {} object.name = 'Foo' addChange(object) object.change(function () { console.log('Changed!', this) // 更新视图的代码 }) obejct.change() object.name = 'Bar' object.change()
这样就实现了执行和触发change事件了。
我相信大家对MVC有了比较深刻的理解,下面来学习MVVM模式。
MVVM
如今主流的web框架基本都采用的是MVVM模式,为什么放弃了MVC模式,转而投向了MVVM模式呢。在之前的MVC中我们提到一个控制器对应一个视图,控制器用状态机进行管理,这里就存在一个问题,如果项目足够大的时候,状态机的代码量就变得非常臃肿,难以维护。还有一个就是性能问题,在MVC中我们大量的操作了DOM,而大量操作DOM会让页面渲染性能降低,加载速度变慢,影响用户体验。最后就是当Model频繁变化的时候,开发者就主动更新View,那么数据的维护就变得困难。世界是懒人创造的,为了减小工作量,节约时间,一个更适合前端开发的架构模式就显得非常重要。这时候MVVM模式在前端中的应用就应运而生。
MVVM让用户界面和逻辑分离更加清晰。下面是MVVM的示意图,可以看到它由Model、ViewModel、View这三个部分组成。
下面分别来讲讲他们的作用
View
View是作为视图模板,用于定义结构、布局。它自己不处理数据,只是将ViewModel中的数据展现出来。此外为了和ViewModel产生关联,那么还需要做的就是数据绑定的声明,指令的声明,事件绑定的声明。这在当今流行的MVVM开发框架中体现的淋淋尽致。在示例图中,我们可以看到ViewModel和View之间是双向绑定,意思就是说ViewModel的变化能够反映到View中,View的变化也能够改变ViewModel的数据值。那如何实现双向绑定呢,例如有这个input元素:
<input>
随着用户在Input中输入值的变化,在ViewModel中的message也会发生改变,这样就实现了View到ViewModel的单向数据绑定。下面是一些思路:
扫描看哪些节点有yg-xxx属性
自动给这些节点加上onchange这种事件
更新ViewModel中的数据,例如ViewModel.message = xx.innerText
那么ViewModel到View的绑定可以是下面例子:
<p></p>
渲染后p中显示的值就是ViewModel中的message变量值。下面是一些思路:
首先注册ViewModel
扫描整个DOM Tree 看哪些节点有yg-xxx这中属性
记录这些被单向绑定的DOM节点和ViewModel之间的隐射关系
使用innerText,innerHTML = ViewModel.message进行赋值
ViewModel
ViewModel起着连接View和Model的作用,同时用于处理View中的逻辑。在MVC框架中,视图模型通过调用模型中的方法与模型进行交互,然而在MVVM中View和Model并没有直接的关系,在MVVM中,ViewModel从Model获取数据,然后应用到View中。相对MVC的众多的控制器,很明显这种模式更能够轻松管理数据,不至于这么混乱。还有的就是处理View中的事件,例如用户在点击某个按钮的时候,这个行动就会触发ViewModel的行为,进行相应的操作。行为就可能包括更改Model,重新渲染View。
Model
Model 层,对应数据层的域模型,它主要做域模型的同步。通过 Ajax/fetch 等 API 完成客户端和服务端业务 Model 的同步。在层间关系里,它主要用于抽象出 ViewModel 中视图的 Model。
MVVM简单实现
实现效果:
<input>
{{message}}
<script> const vm = new MVVM({ el: '#mvvm', methods: { changeMessage: function () { this.message = 'message has change' } }, data: { message: 'this is old message' } }) </script>
这里为了简单,借鉴了Vue的一些方法
Observer
MVVM为我们省去了手动更新视图的步骤,一旦值发生变化,视图就重新渲染,那么就需要对数据的改变就行检测。例如有这么一个例子:
hero = { name: 'A' }
这时候但我们访问hero.name 的时候,就会打印出一些信息:
hero.name // I'm A
当我们对hero.name 进行更改的时候,也会打印出一些信息:
hero.name = 'B' // the name has change
这样我们是不是就实现了数据的观测了呢。
在Angular中实现数据的观测使用的是脏检查,就是在用户进行可能改变ViewModel的操作的时候,对比以前老的ViewModel然后做出改变。
而在Vue中,采取的是数据劫持,就是当数据获取或者设置的时候,会触发Object.defineProperty()。
这里我们采取的是Vue数据观测的方法,简单一些。下面是具体的代码
function observer (obj) { let keys = Object.keys(obj) if (typeof obj === 'object' && !Array.isArray(obj)) { keys.forEach(key => { defineReactive(obj, key, obj[key]) }) } } function defineReactive (obj, key, val) { observer(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function () { console.log('I am A') return val }, set: function (newval) { console.log('the name has change') observer(val) val = newval } }) }
把hero带入observe方法中,结果正如先前预料的一样的结果。这样数据的检测也就实现了,然后在通知订阅者。如何通知订阅者呢,我们需要实现一个消息订阅器,维护一个数组用来收集订阅者,数据变动触发notify(),然后订阅者触发update()方法,改善后的代码长这样:
function defineReactive (obj) { dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function () { console.log('I am A') Dep.target || dep.depend() return val }, set: function (newval) { console.log('the name has change') dep.notify() observer(val) val = newval } }) } var Dep = function Dep () { this.subs = [] } Dep.prototype.notify = function(){ var subs = this.subs.slice() for (var i = 0, l = subs.length; i <p>这跟Vue源码差不多,就完成了往订阅器里边添加订阅者,和通知订阅者。这里以前我看Vue源码的时候,困扰了很久的问题,就是在get方法中Dep是哪儿来的。这里说一下他是一个全局变量,添加target变量是用于向订阅器中添加订阅者。这里的订阅者是Wacther,Watcher就可以连接视图更新视图。下面是Watcher的一部分代码</p><pre class="brush:php;toolbar:false">Watcher.prototype.get = function(key){ Dep.target = this this.value = obj[key] // 触发get从而向订阅器中添加订阅者 Dep.target = null // 重置 };
Compile
在讲MVVM概念的时候,在View -> ViewModel的过程中有一个步骤就是在DOM tree中寻找哪个具有yg-xx的元素。这一节就是讲解析模板,让View和ViewModel连接起来。遍历DOM tree是非常消耗性能的,所以会先把节点el转换为文档碎片fragment进行解析编译操作。操作完成后,在将fragment添加到原来的真实DOM节点中。下面是它的代码
function Compile (el) { this.el = document.querySelector(el) this.fragment = this.init() this.compileElement() } Compile.prototype.init = function(){ var fragment = document.createDocumentFragment(), chid while (child.el.firstChild) { fragment.appendChild(child) } return fragment }; Compile.prototype.compileElement = function(){ fragment = this.fragment me = this var childNodes = el.childNodes [].slice.call(childNodes).forEach(function (node) { var text = node.textContent var reg = /\{\{(.*)\}\}/ // 获取{{}}中的值 if (reg.test(text)) { me.compileText(node, RegExp.$1) } if (node.childNodes && node.childNodes.length) { me.compileElement(node) } }) } Compile.prototype.compileText = function (node, vm, exp) { updateFn && updateFn(node, vm[exp]) new Watcher(vm, exp, function (value, oldValue) { // 一旦属性值有变化,就会收到通知执行此更新函数,更新视图 updateFn() && updateFn(node, val) }) } // 更新视图 function updateFn (node, value) { node.textContent = value }
这样编译fragment就成功了,并且ViewModel中值的改变就能够引起View层的改变。接下来是Watcher的实现,get方法已经讲了,我们来看看其他的方法。
Watcher
Watcher是连接Observer和Compile之间的桥梁。可以看到在Observer中,往订阅器中添加了自己。dep.notice()发生的时候,调用了sub.update(),所以需要一个update()方法,值发生变化后,就能够触发Compile中的回调更新视图。下面是Watcher的具体实现
var Watcher = function Watcher (vm, exp, cb) { this.vm = vm this.cb = cb this.exp = exp // 触发getter,向订阅器中添加自己 this.value = this.get() } Watcher.prototype = { update: function () { this.run() }, addDep: function (dep) { dep.addSub(this) }, run: function () { var value = this.get() var oldVal = this.value if (value !== oldValue) { this.value = value this.cb.call(this.vm, value, oldValue) // 执行Compile中的回调 } }, get: function () { Dep.target = this value = this.vm[exp] // 触发getter Dep.target = null return value } }
在上面的代码中Watcher就起到了连接Observer和Compile的作用,值发生改变的时候通知Watcher,然后Watcher调用update方法,因为在Compile中定义的Watcher,所以值发生改变的时候,就会调用Watcher()中的回调,从而更新视图。最重要的部分也就完成了。在加一个MVVM的构造器就ok了。推荐一篇文章自己实现MVVM,这里边讲的更加详细。
相关推荐:
以上是MVC、MVVM簡單實作方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

PHP仍然流行的原因是其易用性、靈活性和強大的生態系統。 1)易用性和簡單語法使其成為初學者的首選。 2)與web開發緊密結合,處理HTTP請求和數據庫交互出色。 3)龐大的生態系統提供了豐富的工具和庫。 4)活躍的社區和開源性質使其適應新需求和技術趨勢。

PHP和Python都是高層次的編程語言,廣泛應用於Web開發、數據處理和自動化任務。 1.PHP常用於構建動態網站和內容管理系統,而Python常用於構建Web框架和數據科學。 2.PHP使用echo輸出內容,Python使用print。 3.兩者都支持面向對象編程,但語法和關鍵字不同。 4.PHP支持弱類型轉換,Python則更嚴格。 5.PHP性能優化包括使用OPcache和異步編程,Python則使用cProfile和異步編程。

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

PHP在現代化進程中仍然重要,因為它支持大量網站和應用,並通過框架適應開發需求。 1.PHP7提升了性能並引入了新功能。 2.現代框架如Laravel、Symfony和CodeIgniter簡化開發,提高代碼質量。 3.性能優化和最佳實踐進一步提升應用效率。

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP類型提示提升代碼質量和可讀性。 1)標量類型提示:自PHP7.0起,允許在函數參數中指定基本數據類型,如int、float等。 2)返回類型提示:確保函數返回值類型的一致性。 3)聯合類型提示:自PHP8.0起,允許在函數參數或返回值中指定多個類型。 4)可空類型提示:允許包含null值,處理可能返回空值的函數。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SublimeText3漢化版
中文版,非常好用

Dreamweaver Mac版
視覺化網頁開發工具

Atom編輯器mac版下載
最受歡迎的的開源編輯器

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。