久久久久在线观看_又色又爽又黄的免费视频播放_一区中文字幕_日韩电影在线播放

今日頭條 焦點資訊 營銷之道 企業報道 淘寶運營 網站建設 軟件開發 400電話
  當前位置: 首頁 » 資訊 » 軟件開發 » 正文

MVC、MVVM簡單實現方法

放大字體  縮小字體 發布日期:2018-02-28  來源:企業800網  作者:新格網  瀏覽次數:157  【去百度看看】
核心提示:MVC是一種設計模式,它將應用劃分為3個部分:數據(模型)、展示層(視圖)和用戶交互層。結合一下下圖,更能理解三者之間的關系。
MVC是一種設計模式,它將應用劃分為3個部分:數據(模型)、展示層(視圖)和用戶交互層。結合一下下圖,更能理解三者之間的關系。

換句話說,一個事件的發生是這樣的過程

  1. 用戶和應用交互

  2. 控制器的事件處理器被觸發

  3. 控制器從模型中請求數據,并將其交給視圖

  4. 視圖將數據呈現給用戶

模型:用來存放應用的所有數據對象。模型不必知曉視圖和控制器的細節,模型只需包含數據及直接和這些數據相關的邏輯。任何事件處理代碼、視圖模版,以及那些和模型無關的邏輯都應當隔離在模型之外。
視圖:視圖層是呈現給用戶的,用戶與之產生交互。在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><%= title %></h1>'
ejs.render(str, {

title: 'ejs'

});
那么這個渲染后的結果就是
<h1>ejs</h1>
當然實際中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的單向數據綁定。下面是一些思路:

  1. 掃描看哪些節點有yg-xxx屬性

  2. 自動給這些節點加上onchange這種事件

  3. 更新ViewModel中的數據,例如ViewModel.message = xx.innerText

那么ViewModel到View的綁定可以是下面例子:

<p yg-text='message'></p>

渲染后p中顯示的值就是ViewModel中的message變量值。下面是一些思路:

  1. 首先注冊ViewModel

  2. 掃描整個DOM Tree 看哪些節點有yg-xxx這中屬性

  3. 記錄這些被單向綁定的DOM節點和ViewModel之間的隱射關系

  4. 使用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,這里邊講的更加詳細。

相關推薦:

PHP學習MVC框架之路由

TP框架多層MVC用法分析

MVC框架是什么 這里為你解答_實用技巧

以上就是MVC、MVVM簡單實現方法的詳細內容,更多請關注php中文網其它相關文章!

 
關鍵詞: MVVM,方法,簡單
 
[ 資訊搜索 ]  [ 加入收藏 ]  [ 告訴好友 ]  [ 打印本文 ]  [ 違規舉報 ]  [ 關閉窗口 ]

 
0條 [查看全部]  相關評論

 
網站首頁 | 關于我們 | 聯系方式 | 使用協議 | 版權隱私 | 網站地圖 | 排名推廣 | 廣告服務 | 積分換禮 | 網站留言 | RSS訂閱 | 吉ICP備11001726號-6
企業800網 · 提供技術支持