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