MVC、MVVM簡單實作方法
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 = '
<%= title %> h1>'
ejs.render(str, {
title: 'ejs'
登入後複製
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 type='text' yg-model='message'>
随着用户在Input中输入值的变化,在ViewModel中的message也会发生改变,这样就实现了View到ViewModel的单向数据绑定。下面是一些思路:
扫描看哪些节点有yg-xxx属性
自动给这些节点加上onchange这种事件
更新ViewModel中的数据,例如ViewModel.message = xx.innerText
那么ViewModel到View的绑定可以是下面例子:
<p yg-text='message'></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简单实现
实现效果:
<p id="mvvm"> <input type="text" v-model="message"> <p>{{message}}</p> <button v-click='changeMessage'></button> </p> <script type=""> 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 < l; i++) { subs[i].update() } } Dep.prototype.addSub = function(sub){ this.subs.push(sub) } Dep.prototype.depend = function(){ if (Dep.target) { Dep.target.addDep(this) } }
这跟Vue源码差不多,就完成了往订阅器里边添加订阅者,和通知订阅者。这里以前我看Vue源码的时候,困扰了很久的问题,就是在get方法中Dep是哪儿来的。这里说一下他是一个全局变量,添加target变量是用于向订阅器中添加订阅者。这里的订阅者是Wacther,Watcher就可以连接视图更新视图。下面是Watcher的一部分代码
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中文網其他相關文章!

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

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

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

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

微信是主流的聊天工具之一,我們可以透過微信認識新的朋友,聯絡老的朋友,維繫朋友之間的友誼。正如天下沒有不散的宴席,人與人之間的相處難免會發生意見不合的時候。當一個人極度影響你的情緒,或是在相處的時候發現三觀不合,沒辦法再繼續溝通,那麼我們可能需要刪除微信好友的方法。怎麼刪除微信好友?刪除微信好友的方法第一步:在微信主介面輕觸【通訊錄】;第二步:點選對應要刪除的好友,進入【詳細資料】;第三步:點選右上角【...】;第四步:點選下方【刪除】即可;第五步:了解後頁面提示後,點選【刪除聯絡人】即可;溫馨

而後悔莫及、人們常常會因為一些原因不小心刪除某些聯絡人、微信作為一款廣泛使用的社群軟體。幫助用戶解決這個問題,本文將介紹如何透過簡單的方法找回被刪除的聯絡人。 1.了解微信聯絡人刪除機制這為我們找回被刪除的聯絡人提供了可能性、微信中的聯絡人刪除機制是將其從通訊錄中移除,但並未完全刪除。 2.使用微信內建「通訊錄恢復」功能微信提供了「通訊錄恢復」節省時間和精力,使用者可以透過此功能快速找回先前刪除的聯絡人,功能。 3.進入微信設定頁面點選右下角,開啟微信應用程式「我」再點選右上角設定圖示、進入設定頁面,,

番茄小說是一款非常熱門的小說閱讀軟體,我們在番茄小說中經常會有新的小說和漫畫可以去閱讀,每一本小說和漫畫都很有意思,很多小伙伴也想著要去寫小說來賺取賺取零用錢,在把自己想要寫的小說內容編輯成文字,那麼我們要怎麼樣在這裡面去寫小說呢?小伙伴們都不知道,那就讓我們一起到本站本站中花點時間來看寫小說的方法介紹。分享番茄小說寫小說方法教學 1、先在手機上打開番茄免費小說app,點擊個人中心——作家中心 2、跳到番茄作家助手頁面——點擊創建新書在小說的結

七彩虹主機板在中國國內市場享有較高的知名度和市場佔有率,但是有些七彩虹主機板的用戶還不清楚怎麼進入bios進行設定呢?針對這一情況,小編專門為大家帶來了兩種進入七彩虹主機板bios的方法,快來試試吧!方法一:使用u盤啟動快捷鍵直接進入u盤裝系統七彩虹主機板一鍵啟動u盤的快捷鍵是ESC或F11,首先使用黑鯊裝機大師製作一個黑鯊U盤啟動盤,然後開啟電腦,當看到開機畫面的時候,連續按下鍵盤上的ESC或F11鍵以後將會進入到一個啟動項順序選擇的窗口,將遊標移到顯示“USB”的地方,然

手機遊戲成為了人們生活中不可或缺的一部分,隨著科技的發展。它以其可愛的龍蛋形象和有趣的孵化過程吸引了眾多玩家的關注,而其中一款備受矚目的遊戲就是手機版龍蛋。幫助玩家們在遊戲中更好地培養和成長自己的小龍,本文將向大家介紹手機版龍蛋的孵化方法。 1.選擇合適的龍蛋種類玩家需要仔細選擇自己喜歡並且適合自己的龍蛋種類,根據遊戲中提供的不同種類的龍蛋屬性和能力。 2.提升孵化機的等級玩家需要透過完成任務和收集道具來提升孵化機的等級,孵化機的等級決定了孵化速度和孵化成功率。 3.收集孵化所需的資源玩家需要在遊戲中

Win11管理員權限取得方法匯總在Windows11作業系統中,管理員權限是非常重要的權限之一,可以讓使用者對系統進行各種操作。有時候,我們可能需要取得管理員權限來完成一些操作,例如安裝軟體、修改系統設定等。下面就為大家總結了一些取得Win11管理員權限的方法,希望能幫助大家。 1.使用快捷鍵在Windows11系統中,可以透過快捷鍵的方式快速開啟命令提

Oracle版本查詢方法詳解Oracle是目前世界上最受歡迎的關聯式資料庫管理系統之一,它提供了豐富的功能和強大的效能,廣泛應用於企業。在進行資料庫管理和開發過程中,了解Oracle資料庫的版本是非常重要的。本文將詳細介紹如何查詢Oracle資料庫的版本信息,並給出具體的程式碼範例。查詢資料庫版本的SQL語句在Oracle資料庫中,可以透過執行簡單的SQL語句

字體大小的設定成為了重要的個人化需求,隨著手機成為人們日常生活的重要工具。以滿足不同使用者的需求、本文將介紹如何透過簡單的操作,提升手機使用體驗,調整手機字體大小。為什麼需要調整手機字體大小-調整字體大小可以使文字更清晰易讀-適合不同年齡段用戶的閱讀需求-方便視力不佳的用戶使用手機系統自帶字體大小設置功能-如何進入系統設置界面-在在設定介面中找到並進入"顯示"選項-找到"字體大小"選項並進行調整第三方應用調整字體大小-下載並安裝支援字體大小調整的應用程式-開啟應用程式並進入相關設定介面-根據個人
