View
和Model
,自動渲染模板在JQuery
時期,若是須要刷新UI
時,須要先取到對應的DOM
再更新UI
,這樣數據和業務的邏輯就和頁面有強耦合。javascript
在MVVM
中,UI
是挺數據驅動的,數據一旦改變就會刷新相應的UI
,UI
變化也會改變相應的數據。這種方式在開發中只須要關心數據的變化,不用直接去操做DOM
。而且能夠將一些可複用的邏輯放在一個ViewModel
中,多個View
複用這個ViewModel
。vue
在 MVVM
中,最核心的也就是數據雙向綁定,例如 Angluar 的髒數據檢測,Vue 的數據劫持,React的數據綁定java
當觸發了指定事件後進入髒數據檢測,這時期會調用$digest
循環遍歷全部的數據觀察者,判斷當前值是否和先前的值有區別,若是檢測到變化的話,會調用$watch
函數,而後再次調用$digest
循環直到發現沒有變化。因此這個過程可能會循環幾回,一直到再也不有數據變化發生後,將變動的數據發送到視圖,更新頁面展示。若是是手動對 ViewModel
的數據進行變動,爲確保變動同步到視圖,須要手動觸發一次「髒值檢測」。react
髒數據檢測雖然須要每次去循環遍歷查看是否有數據變化,存在低效的問題,與Vue
的雙向綁定原理不一樣,可是髒數據檢測可以同時檢測出要更新的值,再去統一更新UI
,這樣也能夠減小操做DOM
的次數。數組
Vue2.0
版本內部使用了Object.defineProperty()
來實現數據與視圖的雙向綁定,體如今對數據的讀寫處理過程當中。即Object.defineProperty()
中定義的數據set
、get
函數。app
使用Object.defineProperty()
實現Vue2.0
雙向綁定的小demo
:框架
<div id="app"> <input type="text" id="input"> <span id="text"></span> </div> ---------------------------------------------------------------------- var obj = {}; Object.defineProperty(obj, 'prop', { get: function() { return val; }, set: function(newVal) { val = newVal; document.getElementById('input').value = val; document.getElementById('text').innerHTML = val; } }); document.addEventListener('keyup', function(e) { obj.prop = e.target.value; });
如上所述,vue.js
經過Object.defineProperty()
來劫持各個屬性的setter
,getter
。再結合發佈者-訂閱者的方式,發佈消息給訂閱者,觸發相應的監聽回調。經過Directives
指令去對DOM
作封裝,當數據發生變化,會通知指令去修改對應的DOM
,數據驅動DOM
的變化。vue.js
還會對操做作一些監聽(DOM Listener)
,當咱們修改視圖的時候,vue.js
監聽到這些變化,從而改變數據。這樣就造成了數據的雙向綁定。函數
在對數據進行讀取時,若是當前有 Watcher
(對數據的觀察者,watcher
會負責將獲取的新數據發送給視圖),那將該 Watcher
綁定到當前的數據上(dep.depend()
,dep
關聯當前數據和全部的 watcher
的依賴關係),是一個檢查並記錄依賴的過程。而在對數據進行賦值時,若是數據發生改變,則通知全部的 watcher
(藉助 dep.notify()
)。這樣,即使是咱們手動改變了數據,框架也可以自動將數據同步到視圖。
完整的簡易雙向綁定代碼以下:this
var data = { name: 'yck' } observe(data) let name = data.name // -> get value data.name = 'yyy' // -> change value function observe(obj) { // 判斷類型 if (!obj || typeof obj !== 'object') { return } Object.keys(obj).forEach(key => { defineReactive(obj, key, obj[key]) }) } function defineReactive(obj, key, val) { // 遞歸子屬性 observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { console.log('get value') return val }, set: function reactiveSetter(newVal) { console.log('change value') val = newVal } }) }
以上代碼簡單的實現瞭如何監聽數據的set
和get
,下面再給屬性添加發布訂閱lua
<div>{{name}}</div>
在解析模板的時候遇到{{ name }}
就給屬性name
添加發布訂閱
// 經過 Dep 解耦 class Dep { constructor() { this.subs = [] } addSub(sub) { // sub 是 Watcher 實例 this.subs.push(sub) } notify() { this.subs.forEach(sub => { sub.update() }) } } // 全局屬性,經過該屬性配置 Watcher Dep.target = null function update(value) { document.querySelector('div').innerText = value } class Watcher { constructor(obj, key, cb) { // 將 Dep.target 指向本身 // 而後觸發屬性的 getter 添加監聽 // 最後將 Dep.target 置空 Dep.target = this this.cb = cb this.obj = obj this.key = key this.value = obj[key] Dep.target = null } update() { // 得到新值 this.value = this.obj[this.key] // 調用 update 方法更新 Dom this.cb(this.value) } } var data = { name: 'yck' } observe(data) // 模擬解析到 `{{name}}` 觸發的操做 new Watcher(data, 'name', update) // update Dom innerText data.name = 'yyy'
下面對defineReactive
函數進行改造:
function defineReactive(obj, key, val) { // 遞歸子屬性 observe(val) let dp = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { console.log('get value') // 將 Watcher 添加到訂閱 if (Dep.target) { dp.addSub(Dep.target) } return val }, set: function reactiveSetter(newVal) { console.log('change value') val = newVal // 執行 watcher 的 update 方法 dp.notify() } }) }
核心思路就是手動觸發一次屬性的 getter
來實現發佈訂閱的添加。
Vue3.0
將用proxy
代替Object.defineProperty()
Object.defineProperty()
的缺陷:
1.只能對屬性進行數據劫持,因此須要深度遍歷整個對象
2.對於數組不能監聽到數據的變化
雖然 Vue 2.0
中能檢測到數組數據的變化,可是實際上是使用了 hack
的辦法,而且也是有缺陷的
const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) // hack 如下幾個函數 const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach(function(method) { // 得到原生函數 const original = arrayProto[method] def(arrayMethods, method, function mutator(...args) { // 調用原生函數 const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // 觸發更新 ob.dep.notify() return result }) })
Proxy
原生支持監聽數組變化,而且能夠直接對整個對象進行攔截,因此Vue3.0
使用 Proxy
替換 Object.defineProperty
let onWatch = (obj, setBind, getLogger) => { let handler = { get(target, property, receiver) { getLogger(target, property) return Reflect.get(target, property, receiver) }, set(target, property, value, receiver) { setBind(value) return Reflect.set(target, property, value) } } return new Proxy(obj, handler) } let obj = { a: 1 } let value let p = onWatch( obj, v => { value = v }, (target, property) => { console.log(`Get '${property}' = ${target[property]}`) } ) p.a = 2 // bind `value` to `2` p.a // -> Get 'a' = 2
React
的數據綁定是虛擬DOM
樹的更新相關:
屬性更新,組件本身處理
結構更新,從新「渲染」子樹(虛擬DOM
),找出最小改動步驟,打包DOM操做,給真實DOM
樹打補丁
單純從數據綁定來看,React
虛擬DOM
沒有數據綁定,由於setState()
不維護上一個狀態(狀態丟棄)
從數據更新機制來看,React
相似於提供數據模型的方式(必須經過state
更新)