本文來自網易雲社區。前端
MVVM模式相信作前端的人都不陌生,去網上搜MVVM,會出現一大堆關於MVVM模式的博文,可是這些博文大多都只是用圖片和文字來進行抽象的概念講解,對於剛接觸MVVM模式的新手來講,這些概念雖然可以讀懂,可是也很難作到理解透徹。所以,我寫了這篇文章。vue
這篇文章旨在經過代碼的形式讓你們更好的理解MVVM模式,相信大多數人讀了這篇文章以後再去看其餘諸如regular、vue等基於MVVM模式框架的源碼,會容易不少。web
若是你對MVVM模式已經很熟悉而且也已經研讀過並深入理解了當下主流的前端框架,能夠忽略下面的內容。若是你沒有一點JavaScript基礎,也請先去學習下再來閱讀讀此文。前端框架
來張圖來鎮壓此文:數據結構
MVVM
是Model-View-ViewModel
的縮寫。簡單的講,它將View
與Model
層分隔開,利用ViewModel
層將Model
層的數據通過必定的處理變成適用於View
層的數據結構並傳送到View
層渲染界面,同時View
層的視圖更新也會告知ViewModel
層,而後ViewModel
層再更新Model
層的數據。app
咱們用一段學生信息的代碼做爲引子,而後一步步再重構成MVVM模式的樣子。框架
編寫相似下面結構的學生信息:dom
Name: Jessica Bremvvm
Height: 1.8m函數
Weight: 70kg
用常規的js代碼是這樣的:
const student = { 'first-name': 'Jessica', 'last-name': 'Bre', 'height': 180, 'weight': 70, } const root = document.createElement('ul') const nameLi = document.createElement('li') const nameLabel = document.createElement('span') nameLabel.textContent = 'Name: ' const name_ = document.createElement('span') name_.textContent = student['first-name'] + ' ' + student['last-name'] nameLi.appendChild(nameLabel) nameLi.appendChild(name_) const heightLi = document.createElement('li') const heightLabel = document.createElement('span') heightLabel.textContent = 'Height: ' const height = document.createElement('span') height.textContent = '' + student['height'] / 100 + 'm' heightLi.appendChild(heightLabel) heightLi.appendChild(height) const weightLi = document.createElement('li') const weightLabel = document.createElement('span') weightLabel.textContent = 'Weight: ' const weight = document.createElement('span') weight.textContent = '' + student['weight'] + 'kg' weightLi.appendChild(weightLabel) weightLi.appendChild(weight) root.appendChild(nameLi) root.appendChild(heightLi) root.appendChild(weightLi) document.body.appendChild(root)
好長的一堆代碼呀!別急,下面咱們一步步優化!
程序設計中最普遍接受的規則之一就是「DRY」: "Do not Repeat Yourself"。很顯然,上面的一段代碼有不少重複的部分,不只與這個準則相違背,並且給人一種不舒服的感受。是時候作下處理,來讓這段學生信息更"Drier"。
能夠發現,代碼裏寫了不少遍document.createElement
來建立節點,可是因爲列表項都是類似的結構,因此咱們沒有必要一遍一遍的寫。所以,進行以下封裝:
const createListItem = function (label, content) { const li = document.createElement('li') const labelSpan = document.createElement('span') labelSpan.textContent = label const contentSpan = document.createElement('span') contentSpan.textContent = content li.appendChild(labelSpan) li.appendChild(contentSpan) return li }
通過這步轉化以後,整個學生信息應用就變成了這樣:
const student = { 'first-name': 'Jessica', 'last-name': 'Bre', 'height': 180, 'weight': 70, } const createListItem = function (label, content) { const li = document.createElement('li') const labelSpan = document.createElement('span') labelSpan.textContent = label const contentSpan = document.createElement('span') contentSpan.textContent = content li.appendChild(labelSpan) li.appendChild(contentSpan) return li } const root = document.createElement('ul') const nameLi = createListItem('Name: ', student['first-name'] + ' ' + student['last-name']) const heightLi = createListItem('Height: ', student['height'] / 100 + 'm') const weightLi = createListItem('Weight: ', student['weight'] + 'kg') root.appendChild(nameLi) root.appendChild(heightLi) root.appendChild(weightLi) document.body.appendChild(root)
是否是變得更短了,也更易讀了?即便你不看createListItem
函數的實現,光看const nameLi = createListItem('Name: ', student['first-name'] + ' ' + student['last-name'])
也能大體明白這段代碼時幹什麼的。
可是上面的代碼封裝的還不夠,由於每次建立一個列表項,咱們都要多調用一遍createListItem
,上面的代碼爲了建立name,height,weight
標籤,調用了三遍createListItem
,這裏顯然還有精簡的空間。所以,咱們再進一步封裝:
const student = { 'first-name': 'Jessica', 'last-name': 'Bre', 'height': 180, 'weight': 70, } const createList = function(kvPairs){ const createListItem = function (label, content) { const li = document.createElement('li') const labelSpan = document.createElement('span') labelSpan.textContent = label const contentSpan = document.createElement('span') contentSpan.textContent = content li.appendChild(labelSpan) li.appendChild(contentSpan) return li } const root = document.createElement('ul') kvPairs.forEach(function (x) { root.appendChild(createListItem(x.key, x.value)) }) return root } const ul = createList([ { key: 'Name: ', value: student['first-name'] + ' ' + student['last-name'] }, { key: 'Height: ', value: student['height'] / 100 + 'm' }, { key: 'Weight: ', value: student['weight'] + 'kg' }]) document.body.appendChild(ul)
有沒有看到MVVM風格的影子?student
對象是原始數據,至關於Model
層;createList
建立了dom
樹,至關於View
層,那麼ViewModel
層呢?仔細觀察,其實咱們傳給createList
函數的參數就是Model
的數據的改造,爲了讓Model
的數據符合View
的結構,咱們作了這樣的改造,所以雖然這段函數裏面沒有獨立的ViewModel
層,可是它確實是存在的!聰明的同窗應該想到了,下一步就是來獨立出ViewModel
層了吧~
// Model const tk = { 'first-name': 'Jessica', 'last-name': 'Bre', 'height': 180, 'weight': 70, } //View const createList = function(kvPairs){ const createListItem = function (label, content) { const li = document.createElement('li') const labelSpan = document.createElement('span') labelSpan.textContent = label const contentSpan = document.createElement('span') contentSpan.textContent = content li.appendChild(labelSpan) li.appendChild(contentSpan) return li } const root = document.createElement('ul') kvPairs.forEach(function (x) { root.appendChild(createListItem(x.key, x.value)) }) return root } //ViewModel const formatStudent = function (student) { return [ { key: 'Name: ', value: student['first-name'] + ' ' + student['last-name'] }, { key: 'Height: ', value: student['height'] / 100 + 'm' }, { key: 'Weight: ', value: student['weight'] + 'kg' }] } const ul = createList(formatStudent(tk)) document.body.appendChild(ul)
這看上去更舒服了。可是,最後兩行還能封裝~
const smvvm = function (root, {model, view, vm}) { const rendered = view(vm(model)) root.appendChild(rendered) } smvvm(document.body, { model: tk, view: createList, vm: formatStudent })
這種寫法,熟悉vue或者regular的同窗,應該會以爲似曾相識吧?
本文來自網易雲社區,經做者顧靜受權發佈。
瞭解網易雲 :
網易雲官網:https://www.163yun.com
網易雲社區:https://sq.163yun.com/blog
網易雲新用戶大禮包:https://www.163yun.com/gift
更多網易研發、產品、運營經驗分享請訪問網易雲社區。