欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

前端MVVM框架解析之雙向綁定

 更新時(shí)間:2018年01月24日 13:39:29   作者:牧之  
這篇文章主要介紹了MVVM 框架解析之雙向綁定,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧

MVVM 框架

近年來前端一個(gè)明顯的開發(fā)趨勢(shì)就是架構(gòu)從傳統(tǒng)的 MVC 模式向 MVVM 模式遷移。在傳統(tǒng)的 MVC 下,當(dāng)前前端和后端發(fā)生數(shù)據(jù)交互后會(huì)刷新整個(gè)頁(yè)面,從而導(dǎo)致比較差的用戶體驗(yàn)。因此我們通過 Ajax 的方式和網(wǎng)關(guān) REST API 作通訊,異步的刷新頁(yè)面的某個(gè)區(qū)塊,來優(yōu)化和提升體驗(yàn)。

MVVM 框架基本概念

在 MVVM 框架中,View(視圖) 和 Model(數(shù)據(jù)) 是不可以直接通訊的,在它們之間存在著 ViewModel 這個(gè)中間介充當(dāng)著觀察者的角色。當(dāng)用戶操作 View(視圖),ViewModel 感知到變化,然后通知 Model 發(fā)生相應(yīng)改變;反之當(dāng) Model(數(shù)據(jù)) 發(fā)生改變,ViewModel 也能感知到變化,使 View 作出相應(yīng)更新。這個(gè)一來一回的過程就是我們所熟知的雙向綁定。

MVVM 框架的應(yīng)用場(chǎng)景

MVVM 框架的好處顯而易見:當(dāng)前端對(duì)數(shù)據(jù)進(jìn)行操作的時(shí)候,可以通過 Ajax 請(qǐng)求對(duì)數(shù)據(jù)持久化,只需改變 dom 里需要改變的那部分?jǐn)?shù)據(jù)內(nèi)容,而不必刷新整個(gè)頁(yè)面。特別是在移動(dòng)端,刷新頁(yè)面的代價(jià)太昂貴。雖然有些資源會(huì)被緩存,但是頁(yè)面的 dom、css、js 都會(huì)被瀏覽器重新解析一遍,因此移動(dòng)端頁(yè)面通常會(huì)被做成 SPA 單頁(yè)應(yīng)用。由此在這基礎(chǔ)上誕生了很多 MVVM 框架,比如 React.js、Vue.js、Angular.js 等等。

MVVM 框架的簡(jiǎn)單實(shí)現(xiàn)

模擬 Vue 的雙向綁定流,實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的MVVM 框架,從上圖中可以看出虛線方形中就是之前提到的 ViewModel 中間介層,它充當(dāng)著觀察者的角色。另外可以發(fā)現(xiàn)雙向綁定流中的 View 到 Model 其實(shí)是通過 input 的事件監(jiān)聽函數(shù)實(shí)現(xiàn)的,如果換成 React(單向綁定流) 的話,它在這一步交給狀態(tài)管理工具(比如 Redux)來實(shí)現(xiàn)。另外雙向綁定流中的 Model 到 View 其實(shí)各個(gè) MVVM 框架實(shí)現(xiàn)的都是大同小異的,都用到的核心方法是 Object.defineProperty(),通過這個(gè)方法可以進(jìn)行數(shù)據(jù)劫持,當(dāng)數(shù)據(jù)發(fā)生變化時(shí)可以捕捉到相應(yīng)變化,從而進(jìn)行后續(xù)的處理。

Mvvm(入口文件) 的實(shí)現(xiàn)

一般會(huì)這樣調(diào)用 Mvvm 框架

const vm = new Mvvm({
      el: '#app',
      data: {
       title: 'mvvm title',
       name: 'mvvm name'
      },
     })

但是這樣子的話,如果要得到 title 屬性就要形如 vm.data.title 這樣取得,為了讓 vm.title 就能獲得 title 屬性,從而在 Mvvm 的 prototype 上加上一個(gè)代理方法,代碼如下:

function Mvvm (options) {
 this.data = options.data
 const self = this
 Object.keys(this.data).forEach(key =>
  self.proxyKeys(key)
 )
}
Mvvm.prototype = {
 proxyKeys: function(key) {
  const self = this
  Object.defineProperty(this, key, {
   get: function () { // 這里的 get 和 set 實(shí)現(xiàn)了 vm.data.title 和 vm.title 的值同步
    return self.data[key]
   },
   set: function (newValue) {
    self.data[key] = newValue
   }
  })
 }
}

實(shí)現(xiàn)了代理方法后,就步入主流程的實(shí)現(xiàn)

function Mvvm (options) {
 this.data = options.data
 // ...
 observe(this.data)
 new Compile(options.el, this)
}

observer(觀察者) 的實(shí)現(xiàn)

observer 的職責(zé)是監(jiān)聽 Model(JS 對(duì)象) 的變化,最核心的部分就是用到了 Object.defineProperty() 的 get 和 set 方法,當(dāng)要獲取 Model(JS 對(duì)象) 的值時(shí),會(huì)自動(dòng)調(diào)用 get 方法;當(dāng)改動(dòng)了 Model(JS 對(duì)象) 的值時(shí),會(huì)自動(dòng)調(diào)用 set 方法;從而實(shí)現(xiàn)了對(duì)數(shù)據(jù)的劫持,代碼如下所示。

let data = {
 number: 0
}
observe(data)
data.number = 1 // 值發(fā)生變化
function observe(data) {
 if (!data || typeof(data) !== 'object') {
  return
 }
 const self = this
 Object.keys(data).forEach(key =>
  self.defineReactive(data, key, data[key])
 )
}
function defineReactive(data, key, value) {
 observe(value) // 遍歷嵌套對(duì)象
 Object.defineProperty(data, key, {
  get: function() {
   return value
  },
  set: function(newValue) {
   if (value !== newValue) {
    console.log('值發(fā)生變化', 'newValue:' + newValue + ' ' + 'oldValue:' + value)
    value = newValue
   }
  }
 })
}

運(yùn)行代碼,可以看到控制臺(tái)輸出 值發(fā)生變化 newValue:1 oldValue:0,至此就完成了 observer 的邏輯。

Dep(訂閱者數(shù)組) 和 watcher(訂閱者) 的關(guān)系

觀測(cè)到變化后,我們總要通知給特定的人群,讓他們做出相應(yīng)的處理吧。為了更方便地理解,我們可以把訂閱當(dāng)成是訂閱了一個(gè)微信公眾號(hào),當(dāng)微信公眾號(hào)的內(nèi)容有更新時(shí),那么它會(huì)把內(nèi)容推送(update) 到訂閱了它的人。

那么訂閱了同個(gè)微信公眾號(hào)的人有成千上萬個(gè),那么首先想到的就是要 new Array() 去存放這些人(html 節(jié)點(diǎn))吧。于是就有了如下代碼:

// observer.js
function Dep() {
 this.subs = [] // 存放訂閱者
}
Dep.prototype = {
 addSub: function(sub) { // 添加訂閱者
  this.subs.push(sub)
 },
 notify: function() { // 通知訂閱者更新
  this.subs.forEach(function(sub) {
   sub.update()
  })
 }
}
function observe(data) {...}
function defineReactive(data, key, value) {
 var dep = new Dep()
 observe(value) // 遍歷嵌套對(duì)象
 Object.defineProperty(data, key, {
  get: function() {
   if (Dep.target) { // 往訂閱器添加訂閱者
    dep.addSub(Dep.target)
   }
   return value
  },
  set: function(newValue) {
   if (value !== newValue) {
    console.log('值發(fā)生變化', 'newValue:' + newValue + ' ' + 'oldValue:' + value)
    value = newValue
    dep.notify()
   }
  }
 })
}

初看代碼也比較順暢了,但可能會(huì)卡在 Dep.target 和 sub.update,由此自然而然地將目光移向 watcher,

// watcher.js
function Watcher(vm, exp, cb) {
 this.vm = vm
 this.exp = exp
 this.cb = cb
 this.value = this.get()
}
Watcher.prototype = {
 update: function() {
  this.run()
 },
 run: function() {
  // ...
  if (value !== oldVal) {
   this.cb.call(this.vm, value) // 觸發(fā) compile 中的回調(diào)
  }
 },
 get: function() {
  Dep.target = this // 緩存自己
  const value = this.vm.data[this.exp] // 強(qiáng)制執(zhí)行監(jiān)聽器里的 get 函數(shù)
  Dep.target = null // 釋放自己
  return value
 }
}

從代碼中可以看到當(dāng)構(gòu)造 Watcher 實(shí)例時(shí),會(huì)調(diào)用 get() 方法,接著重點(diǎn)關(guān)注 const value = this.vm.data[this.exp] 這句,前面說了當(dāng)要獲取 Model(JS 對(duì)象) 的值時(shí),會(huì)自動(dòng)調(diào)用 Object.defineProperty 的 get 方法,也就是當(dāng)執(zhí)行完這句的時(shí)候,Dep.target 的值傳進(jìn)了 observer.js 中的 Object.defineProperty 的 get 方法中。同時(shí)也一目了然地在 Watcher.prototype 中發(fā)現(xiàn)了 update 方法,其作用即觸發(fā) compile 中綁定的回調(diào)來更新界面。至此解釋了 Observer 中 Dep.target 和 sub.update 的由來。

來歸納下 Watcher 的作用,其充當(dāng)了 observer 和 compile 的橋梁。

1 在自身實(shí)例化的過程中,往訂閱器(dep) 中添加自己

2 當(dāng) model 發(fā)生變動(dòng),dep.notify() 通知時(shí),其能調(diào)用自身的 update 函數(shù),并觸發(fā) compile 綁定的回調(diào)函數(shù)實(shí)現(xiàn)視圖更新

最后再來看下生成 Watcher 實(shí)例的 compile.js 文件。

compile(編譯) 的實(shí)現(xiàn)

首先遍歷解析的過程有多次操作 dom 節(jié)點(diǎn),為提高性能和效率,會(huì)先將跟節(jié)點(diǎn) el 轉(zhuǎn)換成 fragment(文檔碎片) 進(jìn)行解析編譯,解析完成,再將 fragment 添加回原來的真實(shí) dom 節(jié)點(diǎn)中。代碼如下:

function Compile(el, vm) {
 this.vm = vm
 this.el = document.querySelector(el)
 this.fragment = null
 this.init()
}
Compile.prototype = {
 init: function() {
  if (this.el) {
   this.fragment = this.nodeToFragment(this.el) // 將節(jié)點(diǎn)轉(zhuǎn)為 fragment 文檔碎片
   this.compileElement(this.fragment) // 對(duì) fragment 進(jìn)行編譯解析
   this.el.appendChild(this.fragment)
  }
 },
 nodeToFragment: function(el) {
  const fragment = document.createDocumentFragment()
  let child = el.firstChild // △ 第一個(gè) firstChild 是 text
  while(child) {
   fragment.appendChild(child)
   child = el.firstChild
  }
  return fragment
 },
 compileElement: function(el) {...},
}

這個(gè)簡(jiǎn)單的 mvvm 框架在對(duì) fragment 編譯解析的過程中對(duì) {{}} 文本元素、v-on:click 事件指令、v-model 指令三種類型進(jìn)行了相應(yīng)的處理。

Compile.prototype = {
 init: function() {
  if (this.el) {
   this.fragment = this.nodeToFragment(this.el) // 將節(jié)點(diǎn)轉(zhuǎn)為 fragment 文檔碎片
   this.compileElement(this.fragment) // 對(duì) fragment 進(jìn)行編譯解析
   this.el.appendChild(this.fragment)
  }
 },
 nodeToFragment: function(el) {...},
 compileElement: function(el) {...},
 compileText: function (node, exp) { // 對(duì)文本類型進(jìn)行處理,將 {{abc}} 替換掉
  const self = this
  const initText = this.vm[exp]
  this.updateText(node, initText) // 初始化
  new Watcher(this.vm, exp, function(value) { // 實(shí)例化訂閱者
   self.updateText(node, value)
  })
 },
 compileEvent: function (node, vm, exp, dir) { // 對(duì)事件指令進(jìn)行處理
  const eventType = dir.split(':')[1]
  const cb = vm.methods && vm.methods[exp]
  if (eventType && cb) {
   node.addEventListener(eventType, cb.bind(vm), false)
  }
 },
 compileModel: function (node, vm, exp) { // 對(duì) v-model 進(jìn)行處理
  let val = vm[exp]
  const self = this
  this.modelUpdater(node, val)
  node.addEventListener('input', function (e) {
   const newValue = e.target.value
   self.vm[exp] = newValue // 實(shí)現(xiàn) view 到 model 的綁定
  })
 },
}

在上述代碼的 compileTest 函數(shù)中看到了期盼已久的 Watcher 實(shí)例化,對(duì) Watcher 作用模糊的朋友可以往上回顧下 Watcher 的作用。另外在 compileModel 函數(shù)中看到了本文最開始提到的雙向綁定流中的 View 到 Model 是借助 input 監(jiān)聽事件變化實(shí)現(xiàn)的。

項(xiàng)目地址

本文記錄了些閱讀 mvvm 框架源碼關(guān)于雙向綁定的心得,并動(dòng)手實(shí)踐了一個(gè)簡(jiǎn)版的 mvvm 框架,不足之處在所難免,歡迎指正。

項(xiàng)目演示

項(xiàng)目地址

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Vue3如何獲取proxy對(duì)象的值而不是引用的方式

    Vue3如何獲取proxy對(duì)象的值而不是引用的方式

    這篇文章主要介紹了Vue3如何獲取proxy對(duì)象的值而不是引用的方式,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-10-10
  • element-plus中el-upload組件限制上傳文件類型的方法

    element-plus中el-upload組件限制上傳文件類型的方法

    ?Element Plus 中,el-upload 組件可以通過設(shè)置 accept 屬性來限制上傳文件的格式,這篇文章主要介紹了element-plus中el-upload組件限制上傳文件類型,需要的朋友可以參考下
    2024-02-02
  • Vue導(dǎo)航守衛(wèi)使用教程詳解

    Vue導(dǎo)航守衛(wèi)使用教程詳解

    這篇文章主要介紹了Vue導(dǎo)航守衛(wèi)使用教程,可以向任意給定的導(dǎo)航守衛(wèi)傳遞next,但是next的使用過程容易出錯(cuò),需要確保next在任何給定的導(dǎo)航守衛(wèi)中都被嚴(yán)格調(diào)用一次
    2023-04-04
  • vue3 el-table 如何通過深度選擇器::v-deep修改組件內(nèi)部樣式(默認(rèn)樣式)

    vue3 el-table 如何通過深度選擇器::v-deep修改組件內(nèi)部樣式(默認(rèn)樣式)

    在Vue3中,通過使用深度選擇器::v-deep可以有效修改element-plus中el-table組件的內(nèi)部樣式,這種方法允許開發(fā)者覆蓋默認(rèn)的樣式,實(shí)現(xiàn)自定義的視覺效果,本文給大家介紹vue3 el-table 通過深度選擇器::v-deep修改組件內(nèi)部樣式,感興趣的朋友一起看看吧
    2024-10-10
  • v-distpicker地區(qū)選擇器組件使用實(shí)例詳解

    v-distpicker地區(qū)選擇器組件使用實(shí)例詳解

    代碼添加了一個(gè)vDistpickerHandle的事件處理函數(shù)對(duì)地區(qū)選擇器中的數(shù)據(jù)進(jìn)行處理,將數(shù)據(jù)存儲(chǔ)到form對(duì)象的相應(yīng)屬性中,方便數(shù)據(jù)提交,這篇文章主要介紹了v-distpicker地區(qū)選擇器組件使用,需要的朋友可以參考下
    2024-02-02
  • Vue3中關(guān)于setup與自定義指令詳解

    Vue3中關(guān)于setup與自定義指令詳解

    這篇文章主要介紹了Vue3中關(guān)于setup與自定義指令,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • 詳解如何使用webpack打包Vue工程

    詳解如何使用webpack打包Vue工程

    本篇文章主要介紹了詳解如何使用webpack打包Vue工程,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-05-05
  • vue基于session和github-oauth2實(shí)現(xiàn)登錄注冊(cè)驗(yàn)證思路詳解

    vue基于session和github-oauth2實(shí)現(xiàn)登錄注冊(cè)驗(yàn)證思路詳解

    通過 sessionId 可以在 session 表中獲取用戶的信息,此外,還利用 session 表實(shí)現(xiàn)了GitHub 的 OAuth2 第三方登錄,本文講解前端通過簡(jiǎn)單的方式實(shí)現(xiàn)一個(gè)基本的登錄注冊(cè)驗(yàn)證功能,感興趣的朋友跟隨小編一起看看吧
    2024-08-08
  • 簡(jiǎn)單的vuex 的使用案例筆記

    簡(jiǎn)單的vuex 的使用案例筆記

    這篇文章主要介紹了簡(jiǎn)單的vuex 的使用案例筆記,本文通過demo 是一個(gè) 改變 app 的模式 的一個(gè)appellation ,選擇是 夜間模式還是白天模式,具體代碼大家參考下本文
    2018-04-04
  • Vue3計(jì)算屬性是如何實(shí)現(xiàn)的

    Vue3計(jì)算屬性是如何實(shí)現(xiàn)的

    這篇文章主要介紹了Vue3計(jì)算屬性是如何實(shí)現(xiàn)的,對(duì)于任何包含響應(yīng)式數(shù)據(jù)的復(fù)雜邏輯,我們都應(yīng)該使用計(jì)算屬性,更多相關(guān)內(nèi)容需要的小伙伴可以參考一下
    2022-08-08

最新評(píng)論