Vue 中的 MVVM

MVVM 框架前生 (MVC 框架)

在介紹 MVVM 框架 之前, 先讓我們一起了解一下 MVC 框架

image
  • MVC 框架 將整個前端頁面分成 View, Controller, Modal

  • 當視圖發(fā)生變化, 通過 Controller(控件) 將響應(yīng)傳入到 Model(數(shù)據(jù)源) 中, 由數(shù)據(jù)源改變 View 上面的數(shù)據(jù)

  • 整個過程看起來是行云流水, 業(yè)務(wù)邏輯放在 Model 中, 頁面渲染邏輯放在 View

什么是 “MVVM 框架”

image
image

可以看到 MVVM 分別指 View, Model, View-Model

View 通過 View-ModelDOM Listeners 將事件綁定到 Model

Model 則通過 Data Bindings 來管理 View 中的數(shù)據(jù)

View-Model 從中起到一個連接橋的作用

  • View 層: 也叫視圖層, 在前端開發(fā)中, 通常就是 DOM 層, 主要的作用是給用戶展示各種信息

  • Model 層: 也叫數(shù)據(jù)層, 數(shù)據(jù)可能是我們固定的死數(shù)據(jù), 更多的是來自我們服務(wù)器, 從網(wǎng)絡(luò)上請求下來的數(shù)據(jù)

  • Vue-Model 層: 也叫視圖模型層, 視圖模型層是 ViewModel 溝通的橋梁, 一方面實現(xiàn)了 Data Binding(數(shù)據(jù)綁定), 將 Model 的改變實時的反應(yīng)到 View 中, 另一方面它實現(xiàn)了 DOM Listener(DOM監(jiān)聽), 當 DOM 發(fā)生一些事件 (點擊昂秃、輸入、滾動杜窄、touch等) 時, 可以監(jiān)聽到, 并在需要的情況下改變對應(yīng)的 Data

MVVM 與 MVC 的區(qū)別

  • 如果你看對 MVC 框架 有所了解的話, 你會發(fā)現(xiàn) MVC 框架 實際運用上卻存在一個問題: 那就是 MVC 框架 允許 ViewModel 直接進行通信!!!

  • 這會造成 ViewModel 之間隨著業(yè)務(wù)量的不斷龐大, 會出現(xiàn)蜘蛛網(wǎng)一樣難以處理的依賴關(guān)系, 完全背離了開發(fā)所應(yīng)該遵循的 “開放封閉原則”

  • 面對這個問題, MVVM 框架 就出現(xiàn)了, 它與 MVC 框架 的主要區(qū)別以下兩點:

    1. 實現(xiàn)數(shù)據(jù)與視圖的分離
    2. 通過數(shù)據(jù)來驅(qū)動視圖, 開發(fā)者只需要關(guān)心數(shù)據(jù)變化, DOM 操作被封裝了

MVVM 原理

MVVM 的實現(xiàn)主要是三個核心點:

  1. 響應(yīng)式: Vue 如何監(jiān)聽 data 的屬性變化
  2. 模板解析: Vue 的模板是如何被解析的
  3. 渲染: Vue 模板是如何被渲染成 HTML 的

響應(yīng)式

對于 MVVM 來說, data 一般是放在一個對象當中, 例如:

let obj = {
  rp: 0
}

當我們訪問或修改 obj 的屬性的時候, 例如:

console.log(obj.rp) // 訪問
obj.rp = 10         // 修改

但是這樣的操作 Vue 本身是沒有辦法感知到的, 那么應(yīng)該如何讓 Vue 知道我們進行了訪問或是修改的操作呢?

那就要使用 Object.defineProperty

let VueModel = {}
let data = {
  name: "zhangsan",
  age: 20
}

for (let key in data) {
  Object.defineProperty(VueModel, key, {
    get: () => {
      console.log("get", data[key]) // 監(jiān)聽
      return data[key]
    },
    set: value => {
      console.log("set", value)    // 監(jiān)聽
      data[key] = value
    }
  })
}

通過 Object.defineProperty 將 data 里的每一個屬性的訪問與修改都變成了一個函數(shù), 在函數(shù) getset 中我們即可監(jiān)聽到 data 的屬性發(fā)生了改變

模版解析

首先模板是什么肠骆?

模板本質(zhì)上是一串字符串, 它看起來和 HTML 的格式很相像, 實際上有很大的區(qū)別, 因為模板本身還帶有邏輯運算, 比如 v-if, v-for 等等, 但它最后還是要轉(zhuǎn)換為 HTML 來顯示

<div id="app">
  <div>
    <input v-model="title">
    <button @click="submit">submit</button>
  </div>
  <div>
    <ul>
        <li v-for="(item, index) in list" :key="index">{{item}}</li>
    </ul>
  </div>
</div>

模板在 Vue 中必須轉(zhuǎn)換為 JS 代碼

  • 原因在于: 在前端環(huán)境下, 只有 JS 才是一個圖靈完備語言, 才能實現(xiàn)邏輯運算, 以及渲染為 HTML 頁面

這里就引出了 Vue 中一個特別重要的函數(shù) —— render

render 函數(shù)中的核心就是 with 函數(shù)

  • with 函數(shù)將某個對象添加到作用域鏈的頂部

  • 如果在 statement 中有某個未使用命名空間的變量, 跟作用域鏈中的某個屬性同名, 則這個變量將指向這個屬性值

例如:

let obj = {
    name: "feng",
    age: 20,
    getAddress: () => {
        alert("shanghai")
    }
}

function fnc() {
    with(obj) {
        alert(age)
        alert(name)
        getAddress()
    }
}

fnc()

with 將 obj 這個對象放在了自己函數(shù)的作用域鏈的頂部, 當執(zhí)行下列函數(shù)時, 就會自動到 obj 這個對象去尋找同名的屬性

而在 render 函數(shù)中, with 的用法是這樣

<div id="app">
  <div>
    <input v-model="title">
    <button @click="submit">submit</button>
  </div>
  <div>
    <ul>
      <li v-for="(item, index) in list" :key="index">{{item}}</li>
    </ul>
  </div>
</div>

<script>
  let data = {
      title: '',
      list: []
  }
  
  let app = new Vue({
    el: '#app',
    data,
    methods: {
      submit() {
        this.list.push(this.title)
        this.title = ''
      }
    }
  })
  
  with(this) {
    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": submit
                    }
                  },
                  [_v("submit")]
                )
            ]
        ),
        _v(" "),
        _c('div',
          [
            _c(
              'ul',
              _l((list), function (item) { return _c('li',[_v(_s(item))]) })
            )
          ]
        )
      ]
    )
  }
</script>

在一開始, 因為 new 操作符, 所以 this 指向了 app, 通過 with 我們將 app 這個對象放在作用域鏈的頂部, 因為在函數(shù)內(nèi)部我們會多次調(diào)用 app 內(nèi)部的屬性, 所以使用 with 可以縮短變量長度, 提供系統(tǒng)運行效率

其中的 _c 函數(shù)表示的是創(chuàng)建一個新的 HTML 元素, 其基本用法為:

_c(element, { attrs }, [ children... ])
  • element 表示所要創(chuàng)建的 HTML 元素類型

  • attrs 表示所要創(chuàng)建的元素的屬性

  • children 表示該 HTML 元素的子元素

  • _v 函數(shù)表示創(chuàng)建一個文本節(jié)點

  • _l 函數(shù)表示創(chuàng)建一個數(shù)組

  • 最終 render 函數(shù)返回的是一個虛擬 DOM

如何將模板渲染為 HTML

模板渲染為 HTML 分為兩種情況

  1. 第一種是初次渲染的時候
  2. 第二種是渲染之后數(shù)據(jù)發(fā)生改變的時候, 它們都需要調(diào)用 updateComponent

其形式如下:

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

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

首先讀取當前的虛擬 DOM——app._vnode, 判斷其是否為空

  • 若為空, 則為初次渲染, 將虛擬 DOM 全部渲染到所對應(yīng)的容器當中(app.$el)
  • 若不為空, 則是數(shù)據(jù)發(fā)生了修改, 通過響應(yīng)式我們可以監(jiān)聽到這一情況, 使用 diff 算法完成新舊對比并修改









參考文章: zhuanglog

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市塞耕,隨后出現(xiàn)的幾起案子蚀腿,更是在濱河造成了極大的恐慌,老刑警劉巖扫外,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莉钙,死亡現(xiàn)場離奇詭異,居然都是意外死亡筛谚,警方通過查閱死者的電腦和手機磁玉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驾讲,“玉大人蚊伞,你說我怎么就攤上這事⌒保” “怎么了厚柳?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沐兵。 經(jīng)常有香客問我别垮,道長,這世上最難降的妖魔是什么扎谎? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任碳想,我火速辦了婚禮,結(jié)果婚禮上毁靶,老公的妹妹穿的比我還像新娘胧奔。我一直安慰自己,他們只是感情好预吆,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布龙填。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪岩遗。 梳的紋絲不亂的頭發(fā)上扇商,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天,我揣著相機與錄音宿礁,去河邊找鬼案铺。 笑死,一個胖子當著我的面吹牛梆靖,可吹牛的內(nèi)容都是我干的控汉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼返吻,長吁一口氣:“原來是場噩夢啊……” “哼姑子!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起思喊,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤壁酬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后恨课,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舆乔,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年剂公,在試婚紗的時候發(fā)現(xiàn)自己被綠了希俩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡纲辽,死狀恐怖颜武,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拖吼,我是刑警寧澤鳞上,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站吊档,受9級特大地震影響篙议,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜怠硼,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一鬼贱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧香璃,春花似錦这难、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嵌溢。三九已至,卻和暖如春糖权,著一層夾襖步出監(jiān)牢的瞬間堵腹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工星澳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人旱易。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓禁偎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親阀坏。 傳聞我的和親對象是個殘疾皇子如暖,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容