摘要:的數(shù)據(jù)劫持版本內(nèi)部使用了來實(shí)現(xiàn)數(shù)據(jù)與視圖的雙向綁定,體現(xiàn)在對數(shù)據(jù)的讀寫處理過程中。這樣就形成了數(shù)據(jù)的雙向綁定。
MVVM由以下三個(gè)內(nèi)容組成
View:視圖模板
Model:數(shù)據(jù)模型
ViewModel:作為橋梁負(fù)責(zé)溝通View和Model,自動渲染模板
在JQuery時(shí)期,如果需要刷新UI時(shí),需要先取到對應(yīng)的DOM再更新UI,這樣數(shù)據(jù)和業(yè)務(wù)的邏輯就和頁面有強(qiáng)耦合。
在MVVM中,UI是挺數(shù)據(jù)驅(qū)動的,數(shù)據(jù)一旦改變就會刷新相應(yīng)的UI,UI變化也會改變相應(yīng)的數(shù)據(jù)。這種方式在開發(fā)中只需要關(guān)心數(shù)據(jù)的變化,不用直接去操作DOM。并且可以將一些可復(fù)用的邏輯放在一個(gè)ViewModel中,多個(gè)View復(fù)用這個(gè)ViewModel。
在 MVVM 中,最核心的也就是數(shù)據(jù)雙向綁定,例如 Angluar 的臟數(shù)據(jù)檢測,Vue 的數(shù)據(jù)劫持,React的數(shù)據(jù)綁定
Angluar 的臟數(shù)據(jù)檢測當(dāng)觸發(fā)了指定事件后進(jìn)入臟數(shù)據(jù)檢測,這時(shí)期會調(diào)用$digest循環(huán)遍歷所有的數(shù)據(jù)觀察者,判斷當(dāng)前值是否和先前的值有區(qū)別,如果檢測到變化的話,會調(diào)用$watch函數(shù),然后再次調(diào)用$digest循環(huán)直到發(fā)現(xiàn)沒有變化。所以這個(gè)過程可能會循環(huán)幾次,一直到不再有數(shù)據(jù)變化發(fā)生后,將變更的數(shù)據(jù)發(fā)送到視圖,更新頁面展現(xiàn)。如果是手動對 ViewModel 的數(shù)據(jù)進(jìn)行變更,為確保變更同步到視圖,需要手動觸發(fā)一次“臟值檢測”。
臟數(shù)據(jù)檢測雖然需要每次去循環(huán)遍歷查看是否有數(shù)據(jù)變化,存在低效的問題,與Vue的雙向綁定原理不同,但是臟數(shù)據(jù)檢測能夠同時(shí)檢測出要更新的值,再去統(tǒng)一更新UI,這樣也可以減少操作DOM的次數(shù)。
Vue 的數(shù)據(jù)劫持Vue2.0版本內(nèi)部使用了Object.defineProperty()來實(shí)現(xiàn)數(shù)據(jù)與視圖的雙向綁定,體現(xiàn)在對數(shù)據(jù)的讀寫處理過程中。即Object.defineProperty()中定義的數(shù)據(jù)set、get函數(shù)。
使用Object.defineProperty()實(shí)現(xiàn)Vue2.0雙向綁定的小demo:
---------------------------------------------------------------------- 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()來劫持各個(gè)屬性的setter,getter。再結(jié)合發(fā)布者-訂閱者的方式,發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)。通過Directives指令去對DOM做封裝,當(dāng)數(shù)據(jù)發(fā)生變化,會通知指令去修改對應(yīng)的DOM,數(shù)據(jù)驅(qū)動DOM的變化。vue.js還會對操作做一些監(jiān)聽(DOM Listener),當(dāng)我們修改視圖的時(shí)候,vue.js監(jiān)聽到這些變化,從而改變數(shù)據(jù)。這樣就形成了數(shù)據(jù)的雙向綁定。
在對數(shù)據(jù)進(jìn)行讀取時(shí),如果當(dāng)前有 Watcher(對數(shù)據(jù)的觀察者,watcher 會負(fù)責(zé)將獲取的新數(shù)據(jù)發(fā)送給視圖),那將該 Watcher 綁定到當(dāng)前的數(shù)據(jù)上(dep.depend(),dep 關(guān)聯(lián)當(dāng)前數(shù)據(jù)和所有的 watcher 的依賴關(guān)系),是一個(gè)檢查并記錄依賴的過程。而在對數(shù)據(jù)進(jìn)行賦值時(shí),如果數(shù)據(jù)發(fā)生改變,則通知所有的 watcher(借助 dep.notify())。這樣,即便是我們手動改變了數(shù)據(jù),框架也能夠自動將數(shù)據(jù)同步到視圖。
完整的簡易雙向綁定代碼如下:
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 } }) }
以上代碼簡單的實(shí)現(xiàn)了如何監(jiān)聽數(shù)據(jù)的set和get,下面再給屬性添加發(fā)布訂閱
{{name}}
在解析模板的時(shí)候遇到{{ name }}就給屬性name添加發(fā)布訂閱
// 通過 Dep 解耦 class Dep { constructor() { this.subs = [] } addSub(sub) { // sub 是 Watcher 實(shí)例 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 指向自己 // 然后觸發(fā)屬性的 getter 添加監(jiān)聽 // 最后將 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] // 調(diào)用 update 方法更新 Dom this.cb(this.value) } } var data = { name: "yck" } observe(data) // 模擬解析到 `{{name}}` 觸發(fā)的操作 new Watcher(data, "name", update) // update Dom innerText data.name = "yyy"
下面對defineReactive函數(shù)進(jìn)行改造:
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 // 執(zhí)行 watcher 的 update 方法 dp.notify() } }) }
核心思路就是手動觸發(fā)一次屬性的 getter 來實(shí)現(xiàn)發(fā)布訂閱的添加。
Vue3.0將用proxy代替Object.defineProperty()
Object.defineProperty()的缺陷:
1.只能對屬性進(jìn)行數(shù)據(jù)劫持,所以需要深度遍歷整個(gè)對象
2.對于數(shù)組不能監(jiān)聽到數(shù)據(jù)的變化
雖然 Vue 2.0中能檢測到數(shù)組數(shù)據(jù)的變化,但是其實(shí)是使用了 hack 的辦法,并且也是有缺陷的
const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) // hack 以下幾個(gè)函數(shù) const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse" ] methodsToPatch.forEach(function(method) { // 獲得原生函數(shù) const original = arrayProto[method] def(arrayMethods, method, function mutator(...args) { // 調(diào)用原生函數(shù) 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) // 觸發(fā)更新 ob.dep.notify() return result }) })
Proxy原生支持監(jiān)聽數(shù)組變化,并且可以直接對整個(gè)對象進(jìn)行攔截,所以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" = 2React的數(shù)據(jù)綁定
React的數(shù)據(jù)綁定是虛擬DOM樹的更新相關(guān):
屬性更新,組件自己處理
結(jié)構(gòu)更新,重新“渲染”子樹(虛擬DOM),找出最小改動步驟,打包DOM操作,給真實(shí)DOM樹打補(bǔ)丁
單純從數(shù)據(jù)綁定來看,React虛擬DOM沒有數(shù)據(jù)綁定,因?yàn)?b>setState()不維護(hù)上一個(gè)狀態(tài)(狀態(tài)丟棄)
從數(shù)據(jù)更新機(jī)制來看,React類似于提供數(shù)據(jù)模型的方式(必須通過state更新)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/101461.html
摘要:發(fā)布訂閱現(xiàn)在每個(gè)人應(yīng)該都用微信吧,一個(gè)人可以關(guān)注多個(gè)公眾號,多個(gè)人可以同時(shí)關(guān)注相同的公眾號。公眾號每周都會更新內(nèi)容,并推送給我們,把寫好的文章在微信管理平臺更新就好了,點(diǎn)擊推送,就相當(dāng)于發(fā)布。 什么是MVVM MVVM——Model-View-ViewModle的縮寫,MVC設(shè)計(jì)模式的改進(jìn)版。Model是我們應(yīng)用中的數(shù)據(jù)模型,View是我們的UI層,通過ViewModle,可以把我們M...
摘要:,而且每種框架雙向數(shù)據(jù)綁定的實(shí)現(xiàn)方式都不太一致,比如內(nèi)部使用的是臟檢查,而內(nèi)部實(shí)現(xiàn)方式的本質(zhì)是設(shè)置屬性訪問器。在中也有類似的概念,不過不叫魔術(shù)方法,而是叫做訪問器。 緣起前幾天在看一些流行的迷你mvvm框架(比如avalon.js、 vue.js 這種較輕的框架,而非Angularjs、Emberjs這種較重的框架)的實(shí)現(xiàn)。現(xiàn)代流行的mvvm框架一般都會將數(shù)據(jù)雙向綁定(two-ways...
摘要:,而且每種框架雙向數(shù)據(jù)綁定的實(shí)現(xiàn)方式都不太一致,比如內(nèi)部使用的是臟檢查,而內(nèi)部實(shí)現(xiàn)方式的本質(zhì)是設(shè)置屬性訪問器。在中也有類似的概念,不過不叫魔術(shù)方法,而是叫做訪問器。 緣起前幾天在看一些流行的迷你mvvm框架(比如avalon.js、 vue.js 這種較輕的框架,而非Angularjs、Emberjs這種較重的框架)的實(shí)現(xiàn)。現(xiàn)代流行的mvvm框架一般都會將數(shù)據(jù)雙向綁定(two-ways...
摘要:當(dāng)我們的視圖和數(shù)據(jù)任何一方發(fā)生變化的時(shí)候,我們希望能夠通知對方也更新,這就是所謂的數(shù)據(jù)雙向綁定。返回值返回傳入函數(shù)的對象,即第一個(gè)參數(shù)該方法重點(diǎn)是描述,對象里目前存在的屬性描述符有兩種主要形式數(shù)據(jù)描述符和存取描述符。 前言 談起當(dāng)前前端最熱門的 js 框架,必少不了 Vue、React、Angular,對于大多數(shù)人來說,我們更多的是在使用框架,對于框架解決痛點(diǎn)背后使用的基本原理往往關(guān)注...
閱讀 2805·2019-08-30 15:55
閱讀 2853·2019-08-30 15:53
閱讀 2289·2019-08-26 13:47
閱讀 2551·2019-08-26 13:43
閱讀 3153·2019-08-26 13:33
閱讀 2794·2019-08-26 11:53
閱讀 1789·2019-08-23 18:35
閱讀 795·2019-08-23 17:16