MVVM框架理解及其原理實現

小白一枚,一直使用的是React,想要多瞭解一些其它的框架,正好最近Vue愈來愈火熱,Github上的Star數已經超過了React。而其背後蘊含的MVVM框架思想也一直跟React的組件化開發思想並駕齊驅,在這裏也是本着兼收幷蓄的思想,多瞭解一種開發模式。所以經過一些學習資料,寫一些本身對MVVM開發思想的理解。html

廢話很少說,我們進入正題。前端

MVVM框架理解

提及這個MVVM模型,就不得不說MVC框架。vue

bg2015020105.png

將整個前端頁面分紅View,Controller,Modal,視圖上發生變化,經過Controller(控件)將響應傳入到Model(數據源),由數據源改變View上面的數據。node

整個過程看起來是行雲流水,業務邏輯放在Model當中,頁面渲染邏輯放在View當中,但在實際運用上卻存在一個問題:那就是MVC框架容許View和Model直接進行通訊!!算法

換句話說,View和Model之間隨着業務量的不斷龐大,會出現蜘蛛網同樣難以處理的依賴關係,徹底背離了開發所應該遵循的「開放封閉原則」express

面對這個問題,MVVM框架就出現了,它與MVC框架的主要區別有兩點:
一、實現數據與視圖的分離
二、經過數據來驅動視圖,開發者只須要關心數據變化,DOM操做被封裝了。數組

hEQAEJACAgBISAEhIAQEAJCQAgIASHgigT+Dy+Ux4EFPXIkAAAAAElFTkSuQmCC

能夠看到MVVM分別指View,Model,View-Model,View經過View-Model的DOM Listeners將事件綁定到Model上,而Model則經過Data Bindings來管理View中的數據,View-Model從中起到一個鏈接橋的做用。app

MVVM的實現原理:

MVVM的實現主要是三個核心點:框架

  1. 響應式:vue如何監聽data的屬性變化
  2. 模板解析:vue的模板是如何被解析的
  3. 渲染:vue模板是如何被渲染成HTML的

響應式:

對於MVVM來講,data通常是放在一個對象當中,就好比這樣:dom

var obj = {
             name: 'zhangsan',
             age: 25
         }

當咱們訪問或修改obj的屬性的時候,好比:

console.log(obj.name)  //訪問
         obj.age = 22            //修改

可是這樣的操做vue自己是沒有辦法感知到的,那麼應該如何讓vue知道咱們進行了訪問或是修改的操做呢?
那就要使用Object.defineProperty

var vm = {}
        var data = {
            name: 'zhangsan',
            age: 20
        }

        var key, value
        for (key in data) {
            (function (key) {
                Object.defineProperty(vm, key, {
                    get: function () {
                        console.log('get', data[key]) // 監聽
                        return data[key]
                    },
                    set: function (newVal) {
                        console.log('set', newVal) // 監聽
                        data[key] = newVal
                    }
                })
            })(key)
        }

經過Object.defineProperty將data裏的每個屬性的訪問與修改都變成了一個函數,在函數get和set中咱們便可監聽到data的屬性發生了改變。

模板解析:

首先模板是什麼?

模板本質上是一串字符串,它看起來和html的格式很相像,實際上有很大的區別,由於模板自己還帶有邏輯運算,好比v-if,v-for等等,但它最後仍是要轉換爲html來顯示。

<div id="app">
        <div>
            <input v-model="title">
            <button v-on:click="add">submit</button>
        </div>
        <div>
            <ul>
                <li v-for="item in list">{{item}}</li>
            </ul>
        </div>
    </div>

模板在vue中必須轉換爲JS代碼,緣由在於:在前端環境下,只有JS纔是一個圖靈完備語言,才能實現邏輯運算,以及渲染爲html頁面。

這裏就引出了vue中一個特別重要的函數——render

render函數中的核心就是with函數。

with函數將某個對象添加到做用域鏈的頂部,若是在 statement中有某個未使用命名空間的變量,跟做用域鏈中的某個屬性同名,則這個變量將指向這個屬性值。

舉個例子:

var obj = {
            name: 'zhangsan',
            age: 20,
            getAddress: function () {
                alert('beijing')
            }
        }
        function fn1() {
            with(obj) {
                alert(age)
                alert(name)
                getAddress()
            }
        }
        fn1()

with將obj這個對象放在了本身函數的做用域鏈的頂部,當執行下列函數時,就會自動到obj這個對象去尋找同名的屬性。

而在render函數中,with的用法是這樣:

<div id="app">
        <div>
            <input v-model="title">
            <button v-on:click="add">submit</button>
        </div>
        <div>
            <ul>
                <li v-for="item in list">{{item}}</li>
            </ul>
        </div>
    </div>
// 對應的js文件
        var data = {
            title: '',
            list: []
        }
        // 初始化 Vue 實例
        var vm = new Vue({
            el: '#app',
            data: data,
            methods: {
                add: function () {
                    this.list.push(this.title)
                    this.title = ''
                }
            }
        })

        
        with(this){  // this 就是 vm
            return _c(
                'div',
                {
                    attrs:{"id":"app"}
                },
                [
                    _c(
                        'div',
                        [
                            _c(
                                'input',
                                {
                                    directives:[
                                        {
                                            name:"model",
                                            rawName:"v-model",
                                            value:(title),
                                            expression:"title"
                                        }
                                    ],
                                    domProps:{
                                        "value":(title)
                                    },
                                    on:{
                                        "input":function($event){
                                            if($event.target.composing)return;
                                            title=$event.target.value
                                        }
                                    }
                                }
                            ),
                            _v(" "),
                            _c(
                                'button',
                                {
                                    on:{
                                        "click":add
                                    }
                                },
                                [_v("submit")]
                            )
                        ]
                    ),
                    _v(" "),
                    _c('div',
                        [
                            _c(
                                'ul',
                                _l((list),function(item){return _c('li',[_v(_s(item))])})
                            )
                        ]
                    )
                ]
            )
        }

在一開始,由於new操做符,因此this指向了vm,經過with咱們將vm這個對象放在做用域鏈的頂部,由於在函數內部咱們會屢次調用vm內部的屬性,因此使用with能夠縮短變量長度,提供系統運行效率。

其中的_c函數表示的是建立一個新的html元素,其基本用法爲:

_c(element,{attrs},[children...])

其中的element表示所要建立的html元素類型,attrs表示所要建立的元素的屬性,children表示該html元素的子元素。

_v函數表示建立一個文本節點,_l函數表示建立一個數組。

最終render函數返回的是一個虛擬DOM。

如何將模板渲染爲html

模板渲染爲html分爲兩種狀況,第一種是初次渲染的時候,第二種是渲染以後數據發生改變的時候,它們都須要調用updateComponent,其形式以下:

vm._update(vnode){
  const prevVnode = vm._vnode
  vm._vnode = vnode
  if (!prevVnode){
    vm.$el = vm.__patch__(vm.$el,vnode)
  } else {
    vm.$el = vm.__patch__(prevVnode,vnode)
  }
}

function updateComponent(){
  vm._update(vm._render())
}

首先讀取當前的虛擬DOM——vm._vnode,判斷其是否爲空,若爲空,則爲初次渲染,將虛擬DOM所有渲染到所對應的容器當中(vm.$el),若不爲空,則是數據發生了修改,經過響應式咱們能夠監聽到這一狀況,使用diff算法完成新舊對比並修改。

相關文章
相關標籤/搜索