摘要:由來最近在看深入淺出,第一篇變化偵測,想把自己的理解總結(jié)一下。的變化偵測總結(jié)一下我看了后的理解將數(shù)據(jù)變成可響應(yīng)式的,即將數(shù)據(jù)變成可監(jiān)聽的。
由來
最近在看“深入淺出vuejs”,第一篇變化偵測,想把自己的理解總結(jié)一下。
Object的變化偵測 總結(jié)一下我看了后的理解將數(shù)據(jù)變成可響應(yīng)式的,即將數(shù)據(jù)變成可監(jiān)聽的。通過Observer類來實現(xiàn)
依賴是什么?就是這個數(shù)據(jù)在哪里用到了,相當(dāng)于this當(dāng)前的上下文;所以當(dāng)數(shù)據(jù)變化時,我們可以通知他,觸發(fā)update,從而觸發(fā)渲染
那么這個依賴,誰來收集存起來。通過Dep類來實現(xiàn)
先看Observerclass Observer { constructor(value) { this.value = value if(!Array.isArray(value) { this.walk(value) } } walk (obj) { const keys = Object.keys(obj) for(let i = 0; i < keys.length; i++) { definedReactive(obj, keys[i], obj[keys[i]]) } } } function definedReactive(data, key, value) { if(typeof val === "object") { new Observer(value) } let dep = new Dep() Object.defineProperty(data, key, { enumberable: true, configurable: true, get: function () { dep.depend() return value }, set: function (newVal) { if(value === newVal) { //這邊最好是value === newVal || (value !== value && newVal !== newVal) return } value = newVal //這邊新的newVal如果是引用類型也應(yīng)該進(jìn)行進(jìn)行new Observer() dep.notify() } }) }很容易看懂
將vue中的data對象進(jìn)行遍歷設(shè)置其屬性描述對象
get的設(shè)置就是為了在數(shù)據(jù)被訪問時,將依賴dep.depend()進(jìn)去,至于做了什么看詳細(xì)看Dep類
set的設(shè)置則是為了判斷新值和舊值是否一樣(注意NaN),若不一樣,則執(zhí)行dep.notify(),通知相應(yīng)依賴進(jìn)行更新變化
Dep類class Dep { constructor () { this.subs = [] //存放依賴 } addSub () { this.subs.push(sub) }, remove () { remove(this.subs, sub) }, depend () { if(window.target) { this.addSub(window.target) //window.target 是this,watcher的上下文 } }, notify () { const subs = this.subs.slice() for(let i = 0, l = subs.length; i < l; i ++) { subs[i].update() //update這個方法來自watcher實例對象的方法 } } } function remove(arr, item) { if(arr.length) { const index = arr.indexOf(item) if(index > -1) { return arr.splice(index, 1) } } }分析一下
主要就是對dep實例對象的增刪改查的操作
window.target 這個依賴怎么來,就看watcher實例對象了
Watcher類初版:
class Watcher { constructor (vm, expOrFn, cb) { this.vm = vm this.getter = parsePath(expOrFn) this.cb = cb this.value = this.get() } get() { window.target = this let value = this.getter.call(this.vm, this.vm) window.target = undefined return value } update() { const oldValue = this.value this.value = this.get() this.cb.call(this.vm, this.value, oldValue) } }分析
怎么觸發(fā)?可以利用
vm.$watch("data.a", function (newValue, oldValue) { //執(zhí)行相關(guān)操作 })
parsePath(expOrFn)做了什么?從下面代碼中可以看出作用就是返回一個函數(shù),這個函數(shù)用來讀取value值
const bailRE = /[^w.$]/ // function parsePath(path) { if(bailRE.test(path) { return //當(dāng)path路徑中有一個字符不滿足正則要求就直接return } return function () { const arr = path.split(".") let data = this for(let i = 0, l = arr.length; i < l; i ++) { let data = data.arr[i] } return data } }
在new Watcher時會執(zhí)行this.value,從而執(zhí)行this.get(),所以這時的window.target是當(dāng)前watcher實例對象this;接著執(zhí)行this.getter.call(this.vm, this.vm),觸發(fā)屬性描述對象的get方法,進(jìn)行dep.depend(),最后將其window.target = undefined
update的方法是在數(shù)據(jù)改變后觸發(fā),但這邊有個問題就是會重復(fù)添加依賴
上面版本中比較明顯的問題依賴被重復(fù)添加
只能對已有key進(jìn)行監(jiān)聽
刪除key-value不會被監(jiān)聽
對數(shù)組對象,并沒有添加監(jiān)聽
對于數(shù)據(jù)變化時,并沒有對新數(shù)據(jù)判斷是否需要進(jìn)行Observer
Array的偵測 怎么實現(xiàn)在數(shù)組發(fā)生變化時來觸發(fā)dep.notify(),以及如何收集數(shù)組的依賴通過push, pop, shift, unshift, splice, sort, reverse這幾個方法的封裝來觸發(fā)dep.notify()
怎么的封裝?分兩種;第一種對于支持_proto_屬性的,直接改寫原型鏈的這些方法;第二種對于不支持的,直接在實例對象上添加改變后的7個方法
const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) //新建對象,繼承Array的原型鏈 class Observer { constructor (value) { this.value = value this.dep = new Dep() //在Observer中添加dep屬性為了記錄數(shù)組的依賴 def(value, "_ob_", this) //在當(dāng)前value上新增`_ob_`屬性,其值為this,當(dāng)前observer實例對象 if(Array.isArray(value) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observerArray(value) //將數(shù)組內(nèi)元素也進(jìn)行Observer }else { this.walk(value) } } //新增 observerArray (items) { for(let i = 0, l = items.length; i < l; i ++) { observe(items[i]) } } } //作用就是為obj,添加key值為val function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }) } function observe(value, asRootData) { if(!isObject(value)) { return } let ob //判斷value是否已經(jīng)是Observer實例對象,避免重復(fù)執(zhí)行Observer if(hasOwn(value, "_ob_") && value._ob_ instanceof Observer) { ob = value._ob_ } else { ob = new Observer(value) } return ob } function definedReactive(data, key, value) { let childOb = observe(value) //修改 let dep = new Dep() Object.defineProperty(data, key, { enumberable: true, configurable: true, get: function () { dep.depend() if(childOb) { //新增 childOb.dep.depend() } return value }, set: function (newVal) { if(value === newVal) { //這邊最好是value === newVal || (value !== value && newVal !== newVal) return } value = newVal //這邊新的newVal如果是引用類型也應(yīng)該進(jìn)行進(jìn)行new Observer() dep.notify() } }) } //觸發(fā)數(shù)組攔截 ;[ "push", "pop", "shift", "unshift", "splice", "sort", "reverse" ].forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator() { const result = original.apply(this, args) const ob = this._ob_ //this就是數(shù)據(jù)value let inserted //對于新增變化的元素頁進(jìn)行observerArray() switch (method) { //因為這幾個是有參數(shù)的 case "push": case "unshift": //因為push和unshift都是一樣的取args,所以push不需要加break了 inserted = args break case "splice": //新增變化元素是從索引2開始的 inserted = args.slice(2) break } ob.dep.notify() //通知依賴執(zhí)行update return result }) }分析,已data = { a: [1, 2, 3] }為例
首先對data對象進(jìn)行Observer,將執(zhí)行this.walk(data)
接著執(zhí)行let childOb = observe(val),發(fā)現(xiàn)value是一個數(shù)組對象,進(jìn)行Observer,主要進(jìn)行是augment(value, arrayMethods, arrayKeys),將7個方法進(jìn)行攔截,接著遍歷內(nèi)部元素是否有引用數(shù)據(jù)類型,有繼續(xù)Observer,最后返回Observer實例對象ob
重點是get方法,當(dāng)數(shù)據(jù)data被訪問時,首先執(zhí)行dep.depend()這里將依賴添加到data的dep中;接著因為childOb為true所以執(zhí)行childOb.dep.depend(),這里是將依賴加入到observer實例對象的dep中,為什么,這個dep是給數(shù)組發(fā)生變化時執(zhí)行this._ob_.dep.notify(),這個this就是value對象,因為def(value, "_ob_", this) ,所以可以執(zhí)行dep.notify()
這種數(shù)組變化偵測存在的問題對于進(jìn)行this.list.length = 0進(jìn)行清空時,不會觸發(fā)它的依賴更新,也就不會觸發(fā)視圖的渲染更新
對于this.list[0] = 2,這種通過索引來改變元素值時頁一樣不會觸發(fā)更新
所以我們盡量避免通過這種方式來改變數(shù)據(jù)
還有vm.$watch,vm.$set,vm.$delete下篇中進(jìn)行整理掘金地址
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/109682.html
摘要:由來最近在看深入淺出,第一篇變化偵測,想把自己的理解總結(jié)一下。的變化偵測總結(jié)一下我看了后的理解將數(shù)據(jù)變成可響應(yīng)式的,即將數(shù)據(jù)變成可監(jiān)聽的。由來 最近在看深入淺出vuejs,第一篇變化偵測,想把自己的理解總結(jié)一下。 Object的變化偵測 總結(jié)一下我看了后的理解 將數(shù)據(jù)變成可響應(yīng)式的,即將數(shù)據(jù)變成可監(jiān)聽的。通過Observer類來實現(xiàn) 依賴是什么?就是這個數(shù)據(jù)在哪里用到了,相當(dāng)于this當(dāng)...
摘要:總結(jié)最后我們依照下圖參考深入淺出,再來回顧下整個過程在后,會調(diào)用函數(shù)進(jìn)行初始化,也就是過程,在這個過程通過轉(zhuǎn)換成了的形式,來對數(shù)據(jù)追蹤變化,當(dāng)被設(shè)置的對象被讀取的時候會執(zhí)行函數(shù),而在當(dāng)被賦值的時候會執(zhí)行函數(shù)。 前言 Vue 最獨特的特性之一,是其非侵入性的響應(yīng)式系統(tǒng)。數(shù)據(jù)模型僅僅是普通的 JavaScript 對象。而當(dāng)你修改它們時,視圖會進(jìn)行更新。這使得狀態(tài)管理非常簡單直接,不過理解...
vm.$watch 用法: vm.$watch( expOrFn, callback, [options] ),返回值為unwatch是一個函數(shù)用來取消觀察;下面主要理解options中的兩個參數(shù)deep和immediate以及unwatch Vue.prototype.$watch = function (expOrFn, cb, options) { const vm = this ...
摘要:中的觀察者模式觀察者模式一般包含發(fā)布者和訂閱者兩種角色顧名思義發(fā)布者負(fù)責(zé)發(fā)布消息,訂閱者通過訂閱消息響應(yīng)動作了。中主要有兩種類型的,一種是另外一種是是通過或者中的屬性定義的。結(jié)束好了,基本結(jié)束,如有錯漏,望指正。 碎碎念 四月份真是慵懶無比的一個月份,看著手頭上沒啥事干,只好翻翻代碼啥的,看了一會Vue的源碼,忽而有點感悟,于是便記錄一下。 Vue中的觀察者模式 觀察者模式一般包含發(fā)布...
摘要:雙向數(shù)據(jù)綁定指的是,將對象屬性變化與視圖的變化相互綁定。數(shù)據(jù)雙向綁定已經(jīng)了解到是通過數(shù)據(jù)劫持的方式來做數(shù)據(jù)綁定的,其中最核心的方法便是通過來實現(xiàn)對屬性的劫持,達(dá)到監(jiān)聽數(shù)據(jù)變動的目的。和允許觀察數(shù)據(jù)的更改并觸發(fā)更新。 1 MVVM 雙向數(shù)據(jù)綁定指的是,將對象屬性變化與視圖的變化相互綁定。換句話說,如果有一個擁有name屬性的user對象,與元素的內(nèi)容綁定,當(dāng)給user.name賦予一個新...
閱讀 3415·2023-04-26 02:41
閱讀 2469·2023-04-26 00:14
閱讀 2884·2021-08-11 10:22
閱讀 1292·2019-12-27 11:38
閱讀 3582·2019-08-29 18:34
閱讀 2390·2019-08-29 12:13
閱讀 2963·2019-08-26 18:26
閱讀 1873·2019-08-26 16:49