亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

前端MVVM框架中雙向綁定的示例分析

發布時間:2021-09-14 10:56:50 來源:億速云 閱讀:171 作者:小新 欄目:web開發

這篇文章主要介紹了前端MVVM框架中雙向綁定的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

MVVM 框架基本概念

前端MVVM框架中雙向綁定的示例分析

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

MVVM 框架的應用場景

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

MVVM 框架的簡單實現

前端MVVM框架中雙向綁定的示例分析

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

前端MVVM框架中雙向綁定的示例分析

Mvvm(入口文件) 的實現

一般會這樣調用 Mvvm 框架

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

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

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 實現了 vm.data.title 和 vm.title 的值同步
    return self.data[key]
   },
   set: function (newValue) {
    self.data[key] = newValue
   }
  })
 }
}

實現了代理方法后,就步入主流程的實現

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

observer(觀察者) 的實現

observer 的職責是監聽 Model(JS 對象) 的變化,最核心的部分就是用到了 Object.defineProperty() 的 get 和 set 方法,當要獲取 Model(JS 對象) 的值時,會自動調用 get 方法;當改動了 Model(JS 對象) 的值時,會自動調用 set 方法;從而實現了對數據的劫持,代碼如下所示。

let data = {
 number: 0
}
observe(data)
data.number = 1 // 值發生變化
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) // 遍歷嵌套對象
 Object.defineProperty(data, key, {
  get: function() {
   return value
  },
  set: function(newValue) {
   if (value !== newValue) {
    console.log('值發生變化', 'newValue:' + newValue + ' ' + 'oldValue:' + value)
    value = newValue
   }
  }
 })
}

運行代碼,可以看到控制臺輸出 值發生變化 newValue:1 oldValue:0,至此就完成了 observer 的邏輯。

Dep(訂閱者數組) 和 watcher(訂閱者) 的關系

觀測到變化后,我們總要通知給特定的人群,讓他們做出相應的處理吧。為了更方便地理解,我們可以把訂閱當成是訂閱了一個微信公眾號,當微信公眾號的內容有更新時,那么它會把內容推送(update) 到訂閱了它的人。

前端MVVM框架中雙向綁定的示例分析

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

// 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) // 遍歷嵌套對象
 Object.defineProperty(data, key, {
  get: function() {
   if (Dep.target) { // 往訂閱器添加訂閱者
    dep.addSub(Dep.target)
   }
   return value
  },
  set: function(newValue) {
   if (value !== newValue) {
    console.log('值發生變化', 'newValue:' + newValue + ' ' + 'oldValue:' + value)
    value = newValue
    dep.notify()
   }
  }
 })
}

初看代碼也比較順暢了,但可能會卡在 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) // 觸發 compile 中的回調
  }
 },
 get: function() {
  Dep.target = this // 緩存自己
  const value = this.vm.data[this.exp] // 強制執行監聽器里的 get 函數
  Dep.target = null // 釋放自己
  return value
 }
}

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

來歸納下 Watcher 的作用,其充當了 observer 和 compile 的橋梁。

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

2 當 model 發生變動,dep.notify() 通知時,其能調用自身的 update 函數,并觸發 compile 綁定的回調函數實現視圖更新

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

compile(編譯) 的實現

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

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) // 將節點轉為 fragment 文檔碎片
   this.compileElement(this.fragment) // 對 fragment 進行編譯解析
   this.el.appendChild(this.fragment)
  }
 },
 nodeToFragment: function(el) {
  const fragment = document.createDocumentFragment()
  let child = el.firstChild // △ 第一個 firstChild 是 text
  while(child) {
   fragment.appendChild(child)
   child = el.firstChild
  }
  return fragment
 },
 compileElement: function(el) {...},
}

這個簡單的 mvvm 框架在對 fragment 編譯解析的過程中對 {{}} 文本元素、v-on:click 事件指令、v-model 指令三種類型進行了相應的處理。

Compile.prototype = {
 init: function() {
  if (this.el) {
   this.fragment = this.nodeToFragment(this.el) // 將節點轉為 fragment 文檔碎片
   this.compileElement(this.fragment) // 對 fragment 進行編譯解析
   this.el.appendChild(this.fragment)
  }
 },
 nodeToFragment: function(el) {...},
 compileElement: function(el) {...},
 compileText: function (node, exp) { // 對文本類型進行處理,將 {{abc}} 替換掉
  const self = this
  const initText = this.vm[exp]
  this.updateText(node, initText) // 初始化
  new Watcher(this.vm, exp, function(value) { // 實例化訂閱者
   self.updateText(node, value)
  })
 },
 compileEvent: function (node, vm, exp, dir) { // 對事件指令進行處理
  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) { // 對 v-model 進行處理
  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 // 實現 view 到 model 的綁定
  })
 },
}

感謝你能夠認真閱讀完這篇文章,希望小編分享的“前端MVVM框架中雙向綁定的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

乐安县| 澄城县| 卓资县| 榆中县| 兴文县| 霸州市| 大荔县| 阿坝县| 鄄城县| 西乌珠穆沁旗| 喜德县| 宜昌市| 额尔古纳市| 合阳县| 常熟市| 双流县| 台中市| 乌拉特中旗| 神木县| 五家渠市| 普兰店市| 腾冲县| 且末县| 财经| 巴林左旗| 浙江省| 阿拉善左旗| 即墨市| 温宿县| 凤庆县| 周口市| 新巴尔虎左旗| 石棉县| 洛宁县| 华容县| 福建省| 锦州市| 通海县| 铜陵市| 神池县| 甘谷县|