亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

一個(gè)只有十行的精簡(jiǎn)MVVM框架

Charles / 2354人閱讀

摘要:框架的誕生以上便是一個(gè)簡(jiǎn)短精簡(jiǎn)的風(fēng)格的學(xué)生信息的示例。至此,一個(gè)精簡(jiǎn)的框架其實(shí)已經(jīng)出來(lái)了什么你確定不是在開(kāi)玩笑一個(gè)只有十行的框架請(qǐng)記住框架是對(duì)如何組織代碼和整個(gè)項(xiàng)目如何通用運(yùn)作的抽象。

前言

MVVM模式相信做前端的人都不陌生,去網(wǎng)上搜MVVM,會(huì)出現(xiàn)一大堆關(guān)于MVVM模式的博文,但是這些博文大多都只是用圖片和文字來(lái)進(jìn)行抽象的概念講解,對(duì)于剛接觸MVVM模式的新手來(lái)說(shuō),這些概念雖然能夠讀懂,但是也很難做到理解透徹。因此,我寫(xiě)了這篇文章。

這篇文章旨在通過(guò)代碼的形式讓大家更好的理解MVVM模式,相信大多數(shù)人讀了這篇文章之后再去看其他諸如regular、vue等基于MVVM模式框架的源碼,會(huì)容易很多。

如果你對(duì)MVVM模式已經(jīng)很熟悉并且也已經(jīng)研讀過(guò)并深刻理解了當(dāng)下主流的前端框架,可以忽略下面的內(nèi)容。如果你沒(méi)有一點(diǎn)JavaScript基礎(chǔ),也請(qǐng)先去學(xué)習(xí)下再來(lái)閱讀讀此文。

引子

來(lái)張圖來(lái)鎮(zhèn)壓此文:

MVVMModel-View-ViewModel的縮寫(xiě)。簡(jiǎn)單的講,它將ViewModel層分隔開(kāi),利用ViewModel層將Model層的數(shù)據(jù)經(jīng)過(guò)一定的處理變成適用于View層的數(shù)據(jù)結(jié)構(gòu)并傳送到View層渲染界面,同時(shí)View層的視圖更新也會(huì)告知ViewModel層,然后ViewModel層再更新Model層的數(shù)據(jù)。

我們用一段學(xué)生信息的代碼作為引子,然后一步步再重構(gòu)成MVVM模式的樣子。

編寫(xiě)類(lèi)似下面結(jié)構(gòu)的學(xué)生信息:

Name: Jessica Bre

Height: 1.8m

Weight: 70kg

用常規(guī)的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)

好長(zhǎng)的一堆代碼呀!別急,下面我們一步步優(yōu)化!

DRY一下如何

程序設(shè)計(jì)中最廣泛接受的規(guī)則之一就是“DRY”: "Do not Repeat Yourself"。很顯然,上面的一段代碼有很多重復(fù)的部分,不僅與這個(gè)準(zhǔn)則相違背,而且給人一種不舒服的感覺(jué)。是時(shí)候做下處理,來(lái)讓這段學(xué)生信息更"Drier"。

可以發(fā)現(xiàn),代碼里寫(xiě)了很多遍document.createElement來(lái)創(chuàng)建節(jié)點(diǎn),但是由于列表項(xiàng)都是相似的結(jié)構(gòu),所以我們沒(méi)有必要一遍一遍的寫(xiě)。因此,進(jìn)行如下封裝:

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
}

經(jīng)過(guò)這步轉(zhuǎn)化之后,整個(gè)學(xué)生信息應(yīng)用就變成了這樣:

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函數(shù)的實(shí)現(xiàn),光看const nameLi = createListItem("Name: ", student["first-name"] + " " + student["last-name"])也能大致明白這段代碼時(shí)干什么的。

但是上面的代碼封裝的還不夠,因?yàn)槊看蝿?chuàng)建一個(gè)列表項(xiàng),我們都要多調(diào)用一遍createListItem,上面的代碼為了創(chuàng)建name,height,weight標(biāo)簽,調(diào)用了三遍createListItem,這里顯然還有精簡(jiǎn)的空間。因此,我們?cè)龠M(jìn)一步封裝:

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)

有沒(méi)有看到MVVM風(fēng)格的影子?student對(duì)象是原始數(shù)據(jù),相當(dāng)于Model層;createList創(chuàng)建了dom樹(shù),相當(dāng)于View層,那么ViewModel層呢?仔細(xì)觀(guān)察,其實(shí)我們傳給createList函數(shù)的參數(shù)就是Model的數(shù)據(jù)的改造,為了讓Model的數(shù)據(jù)符合View的結(jié)構(gòu),我們做了這樣的改造,因此雖然這段函數(shù)里面沒(méi)有獨(dú)立的ViewModel層,但是它確實(shí)是存在的!聰明的同學(xué)應(yīng)該想到了,下一步就是來(lái)獨(dú)立出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 run = function (root, {model, view, vm}) {
  const rendered = view(vm(model))
  root.appendChild(rendered)
}
run(document.body, {
      model: tk, 
      view: createList, 
      vm: formatStudent
})

這種寫(xiě)法,熟悉vue或者regular的同學(xué),應(yīng)該會(huì)覺(jué)得似曾相識(shí)吧?

讓我們來(lái)加點(diǎn)互動(dòng)

前面學(xué)生信息的身高的單位都是默認(rèn)m,如果新增一個(gè)需求,要求學(xué)生的身高的單位可以在mcm之間切換呢?

首先需要一個(gè)變量來(lái)保存度量單位,因此這里必須用一個(gè)新的Model:

const tk = {
    "first-name": "Jessica",
    "last-name": "Bre",
    "height": 180,
    "weight": 70,
}
const measurement = "cm"

為了讓tk更方便的被其他模塊重用,這里選擇增加一個(gè)measurement數(shù)據(jù)源,而不是直接修改tk。

在視圖部分要增加一個(gè)radio單選表單,用來(lái)切換身高單位。

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 createToggle = function (options) {
  const createRadio = function (name, opt){
    const radio = document.createElement("input")
    radio.name = name
    radio.value = opt.value
    radio.type = "radio"
    radio.textContent = opt.value
    radio.addEventListener("click", opt.onclick)
    radio.checked = opt.checked
    return radio
  }
  const root = document.createElement("form")
  options.opts.forEach(function (x) {
    root.appendChild(createRadio(options.name, x))
    root.appendChild(document.createTextNode(x.value))
  })
  return root
}
const createToggleableList = function(vm){
  const listView = createList(vm.kvPairs)
  const toggle = createToggle(vm.options)
  const root = document.createElement("div")
  root.appendChild(toggle)
  root.appendChild(listView)
  return root
}

接下來(lái)是ViewModel部分,createToggleableList函數(shù)需要與之前的createList函數(shù)不同的參數(shù)。因此,對(duì)View-Model結(jié)構(gòu)重構(gòu)是有必要的:

const createVm = function (model) {
  const calcHeight = function (measurement, cms) {
    if (measurement === "m"){
      return cms / 100 + "m"
    }else{
      return cms + "cm"
    }
  }
  const options = {
    name: "measurement",
    opts: [
      {
        value: "cm",
        checked: model.measurement === "cm",
        onclick: () => model.measurement = "cm"
      },
      {
        value: "m",
        checked: model.measurement === "m",
        onclick: () => model.measurement = "m"
      }
    ]
  }
  const kvPairs = [
    {
      key: "Name: ",
      value: model.student["first-name"] + " " + model.student["last-name"]
    },
    {
      key: "Height: ",
      value: calcHeight(model.measurement, model.student["height"])
    },
    {
      key: "Weight: ",
      value: model.student["weight"] + "kg"
    },
    {
      key: "BMI: ",
      value:  model.student["weight"] / (model.student["height"] * model.student["height"] / 10000)
    }]
  return {kvPairs, options}
}

這里為createToggle添加了ops,并且將ops封裝成了一個(gè)對(duì)象。根據(jù)度量單位,使用不同的方式去計(jì)算身高。當(dāng)任何一個(gè)radio被點(diǎn)擊,數(shù)據(jù)的度量單位將會(huì)改變。

看上去很完美,但是當(dāng)你點(diǎn)擊radio標(biāo)簽的時(shí)候,視圖不會(huì)有任何改變。因?yàn)檫@里還沒(méi)有為視圖做更新算法。有關(guān)MVVM如何處理視圖更新,那是一個(gè)比較大的課題,需要另辟一個(gè)博文來(lái)講,由于本文寫(xiě)的是一個(gè)精簡(jiǎn)的MVVM框架,這里就不再贅述,并用最簡(jiǎn)單的方式實(shí)現(xiàn)視圖更新:

const run = function (root, {model, view, vm}) {
  let m = {...model}
  let m_old = {}
  setInterval( function (){
    if(!_.isEqual(m, m_old)){
      const rendered = view(vm(m))
      root.innerHTML = ""
      root.appendChild(rendered)
      m_old = {...m}
    }
  },1000)
}
run(document.body, {
      model: {student:tk, measurement}, 
      view: createToggleableList, 
      vm: createVm 
})

上述代碼引用了一個(gè)外部庫(kù)lodashisEqual方法來(lái)比較數(shù)據(jù)模型是否有更新。此段代碼應(yīng)用了輪詢(xún),每秒都會(huì)檢測(cè)數(shù)據(jù)是否發(fā)生變化,有變化了再更新視圖。這是最笨的方法,并且在DOM結(jié)構(gòu)比較復(fù)雜時(shí),性能也會(huì)受到很大的影響。還是同樣的話(huà),本文的主題是一個(gè)精簡(jiǎn)的MVVM框架,因此略去了很多細(xì)節(jié)性的東西,只把主要的東西提煉出來(lái),以達(dá)到更好的理解MVVM模式的目的。

MVVM框架的誕生

以上便是一個(gè)簡(jiǎn)短精簡(jiǎn)的MVVM風(fēng)格的學(xué)生信息的示例。至此,一個(gè)精簡(jiǎn)的MVVM框架其實(shí)已經(jīng)出來(lái)了:

/**
* @param {Node} root
* @param {Object} model
* @param {Function} view
* @param {Function} vm
*/
const run = function (root, {model, view, vm}) {
  let m = {...model}
  let m_old = {}
  setInterval( function (){
    if(!_.isEqual(m, m_old)){
      const rendered = view(vm(m))
      root.innerHTML = ""
      root.appendChild(rendered)
      m_old = {...m}
    }
  },1000)
}

什么?你確定不是在開(kāi)玩笑?一個(gè)只有十行的框架?請(qǐng)記住:

框架是對(duì)如何組織代碼和整個(gè)項(xiàng)目如何通用運(yùn)作的抽象。

這并不意味著你應(yīng)該有一堆代碼或混亂的類(lèi),盡管企業(yè)可用的API列表經(jīng)常都很可怕的長(zhǎng)。但是如果你研讀一個(gè)框架倉(cāng)庫(kù)的核心文件夾,你可能發(fā)現(xiàn)它會(huì)出乎意料的?。ㄏ啾扔谡麄€(gè)項(xiàng)目來(lái)說(shuō))。其核心代碼包含主要工作進(jìn)程,而其他部分只是幫助開(kāi)發(fā)人員以更加舒適的方式構(gòu)建應(yīng)用程序的附件。有興趣的同學(xué)可以去看看cycle.js,這個(gè)框架只有124行(包含注釋和空格)。

總結(jié)

此時(shí)用一張圖來(lái)作為總結(jié)再好不過(guò)了!

當(dāng)然這里還有很多細(xì)節(jié)需要進(jìn)一步探討,比如如何選擇或設(shè)計(jì)一個(gè)更加友好的View層的視圖工具,如何更新和何時(shí)更新視圖比較合適等等。如果把這些問(wèn)題都解決了,相信這種MVVM框架會(huì)更加健壯。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/91828.html

相關(guān)文章

  • 2017-09-16 前端日?qǐng)?bào)

    摘要:前端日?qǐng)?bào)精選深入理解綁定請(qǐng)使用千位分隔符逗號(hào)表示網(wǎng)頁(yè)中的大數(shù)字跨頁(yè)面通信的各種姿勢(shì)你所不知道的濾鏡技巧與細(xì)節(jié)代碼質(zhì)量管控復(fù)雜度檢測(cè)中文翻譯基于與的三種代碼分割范式掘金系列如何構(gòu)建應(yīng)用程序冷星的前端雜貨鋪第期美團(tuán)旅行前端技術(shù)體系 2017-09-16 前端日?qǐng)?bào) 精選 深入理解 js this 綁定請(qǐng)使用千位分隔符(逗號(hào))表示web網(wǎng)頁(yè)中的大數(shù)字跨頁(yè)面通信的各種姿勢(shì)你所不知道的 CSS 濾...

    cheng10 評(píng)論0 收藏0
  • 前端MVVM模式及其在Vue和React中體現(xiàn)

    摘要:在模式中一般把層算在層中,只有在理想的雙向綁定模式下,才會(huì)完全的消失。層將通過(guò)特定的展示出來(lái),并在控件上綁定視圖交互事件,一般由框架自動(dòng)生成在瀏覽器中。三大框架的異同三大框架都是數(shù)據(jù)驅(qū)動(dòng)型的框架及是雙向數(shù)據(jù)綁定是單向數(shù)據(jù)綁定。 MVVM相關(guān)概念 1) MVVM典型特點(diǎn)是有四個(gè)概念:Model、View、ViewModel、綁定器。MVVM可以是單向綁定也可以是雙向綁定甚至是不綁...

    沈建明 評(píng)論0 收藏0
  • Vue 雙向數(shù)據(jù)綁定原理分析

    摘要:關(guān)于雙向數(shù)據(jù)綁定當(dāng)我們?cè)谇岸碎_(kāi)發(fā)中采用的模式時(shí),,指的是模型,也就是數(shù)據(jù),,指的是視圖,也就是頁(yè)面展現(xiàn)的部分。參考沉思錄一數(shù)據(jù)綁定雙向數(shù)據(jù)綁定實(shí)現(xiàn)數(shù)據(jù)與視圖的綁定與同步,最終體現(xiàn)在對(duì)數(shù)據(jù)的讀寫(xiě)處理過(guò)程中,也就是定義的數(shù)據(jù)函數(shù)中。 關(guān)于雙向數(shù)據(jù)綁定 當(dāng)我們?cè)谇岸碎_(kāi)發(fā)中采用MV*的模式時(shí),M - model,指的是模型,也就是數(shù)據(jù),V - view,指的是視圖,也就是頁(yè)面展現(xiàn)的部分。通常,...

    nanfeiyan 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<