vue修改數(shù)據(jù)視圖更新原理學(xué)習(xí)
MVVM模式
在vue使用中,我們只要修改了數(shù)據(jù),所見視圖便會進(jìn)行相應(yīng)更新。
為什么?原理是什么?
在這期間做三件事(發(fā)布訂閱者模式)
- 數(shù)據(jù)劫持
- 依賴收集
- 派發(fā)更新
Object.defineProperty()詳解
詳見:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refer...該方法有三個入?yún)?/p>
obj 要定義屬性的對象。
prop 一個字符串或 Symbol,指定了要定義或修改的屬性鍵。
descriptor 要定義或修改的屬性的描述符。
通過該方法進(jìn)行賦值(無需關(guān)注隱藏屬性)
// 定義一個對象
var obj = {}
// 通過Object.defineProperty對obj進(jìn)行賦值
Object.defineProperty(obj, 'name', {
value: '張三'
})
console.log(obj) // {name: '張三'}
console.log(obj.name) // 張三引入get
// 定義一個對象
var obj = {}
// 通過Object.defineProperty對obj進(jìn)行賦值
Object.defineProperty(obj, 'name', {
get() {
console.log('正在訪問name') // 當(dāng)訪問時(shí),會執(zhí)行該代碼
}
})
Object.defineProperty(obj, 'age', {
value: 18
})
console.log(obj.name) // undefined 因?yàn)闆]有被賦值、所以認(rèn)為不存在
console.log(obj.age) // 18
console.log(obj) // {age: 18}引入set
// 定義一個對象
var obj = {}
// 通過Object.defineProperty對obj進(jìn)行賦值
Object.defineProperty(obj, 'name', {
get() {
console.log('正在訪問name') // 當(dāng)訪問時(shí),會執(zhí)行該代碼
},
set() {
console.log('嘗試改變name') // 當(dāng)對name進(jìn)行更改時(shí),執(zhí)行該行代碼
}
})
Object.defineProperty(obj, 'age', {
value: 18
})
console.log(obj.name) // undefined
obj.name = '李四'
console.log(obj.name) // undefined 雖然改變了name,
// 但并沒有進(jìn)行具體的賦值,此時(shí)會觸發(fā)set內(nèi)代碼如何賦值?
// 定義一個對象
var obj = {}
// 通過Object.defineProperty對obj進(jìn)行賦值
Object.defineProperty(obj, 'name', {
// getter return出的值為name值,
get() {
console.log('正在訪問name') // 當(dāng)訪問時(shí),會執(zhí)行該代碼
return 'TEST' // 便于理解我們寫死
},
set(newValue) {
console.log('嘗試改變name', newValue) // 當(dāng)對name進(jìn)行更改時(shí),執(zhí)行該行代碼
}
})
console.log(obj.name) // 輸出TEST,
obj.name = '李四' // setter內(nèi)輸出 '李四'
console.log(obj.name) // 輸出TEST、因?yàn)樵趃etter中賦值為TEST,setter未成功賦值引入defineReactive()
//為什么引入該函數(shù)?
//因?yàn)間et和set不好用,并且不希望引入變量進(jìn)行周轉(zhuǎn),例子:
// 定義一個對象
var obj = {}
// 定義一個變量
var temp
// 通過Object.defineProperty對obj進(jìn)行賦值
Object.defineProperty(obj, 'name', {
// getter return出的值為name值,
get() {
console.log('正在訪問name') // 當(dāng)訪問時(shí),會執(zhí)行該代碼
return temp // 返回temp,此時(shí)已被賦值
},
set(newValue) {
temp = newValue // 賦值給全局變量
console.log('嘗試改變name', newValue) // 當(dāng)對name進(jìn)行更改時(shí),執(zhí)行該行代碼
}
})
console.log(obj.name) // undefined,因?yàn)閠emp未賦值,且setter未觸發(fā)
obj.name = '李四' // 賦值進(jìn)行setter觸發(fā),將 '李四'賦值給temp
console.log(obj.name) // 李四defineReactive提供一個閉包環(huán)境,利用閉包特性進(jìn)行周轉(zhuǎn)
var obj = {}
function defineReactive(data, key, val) {
console.log(data, key, val)
Object.defineProperty(data, key, {
get() {
return val
},
set(newValue) {
// 如果你要設(shè)置的值和原值相等,直接返回
if(newValue === val) return
val = newValue
}
})
}
// 進(jìn)行賦值操作
defineReactive(obj, 'name', '張三')
console.log(obj.name) // 張三
obj.name = '李四'
console.log(obj.name) // 李四引入遞歸偵測對象(observer)處理對象
// 不進(jìn)行遞歸操作的話,如果一個對象有好幾層,那么我們是檢測不到深層變量的
// eg:
var obj = {
a: {
b: {
c: 'i am c'
}
},
b: 'i am b'
}實(shí)現(xiàn)遞歸
定義observer
把一個正常的object轉(zhuǎn)換為每個層級都是響應(yīng)式的
//defineReactive
function defineReactive(data, key, val) {
console.log(data, key, val)
if (arguments.length == 2) {
val = data[key]
}
// 子元素要進(jìn)行遞歸
let childOb = observe(val)
Object.defineProperty(data, key, {
get() {
return val
},
set(newValue) {
// 如果你要設(shè)置的值和原值相等,直接返回
if(newValue === val) return
val = newValue
// 設(shè)置了新值,也要被observe
childOb = observe(newValue)
}
})
}
// Observer
class Observer {
constructor(value) {
// 給實(shí)例添加__ob__屬性,數(shù)值是這次new的實(shí)例
def(value, '__ob__', this, false)
this.walk(value)
}
// 遍歷
walk(value) {
for (let k in value) {
defineReactive(value, k)
}
}
}
// 不可遍歷的方法
function def(obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true,
configurable: true
})
}
// 創(chuàng)建observe函數(shù)用于輔助
function observe(value) {
// 只為對象服務(wù)
if(typeof value !== 'object') return
// 定義ob
if(typeof value.__ob__ !== 'undefined'){
ob = value.__ob__
} else {
ob = new Observer(value)
}
}
// 循環(huán)監(jiān)測
observe(obj) // 此時(shí)obj每個屬性都被掛載了__ob__標(biāo)識,且為響應(yīng)式
obj.a.b.c.e = 10
console.log(obj.a.b.c.e) // 10
// 簡單來說就是通過__ob__這個標(biāo)識來確認(rèn)你這個obj是不是響應(yīng)式的,如果不是就遞歸遍歷
// observe(obj) ==》 是否有__ob__ ==》 沒有就new Observer(obj) ==》逐個響應(yīng)式defineReactive- 處理數(shù)組
vue對數(shù)組的方法進(jìn)行了改寫(push、pop、shift、unshift、splice、sort、reverse)
以Array.prototype為原型創(chuàng)建了一個arrayMethods的對象,用es6的Object.setPrototypeOf(),強(qiáng)制指向arrayMethods
// 拿到Array.prototype
const arrayPrototype = Array.prototype
// 以Array.prototype為原型創(chuàng)建arrayMethods對象
const arrayMethods = Object.create(arrayPrototype)
const methodsNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
// 遍歷
methodsNeedChange.foreach(methodName => {
// 備份原來的方法, 因?yàn)閿?shù)組的功能不能被剝奪
const original = arrayPrototype[methodName]
// 定義新的方法
def(arrayMethods, methodName, functio(this){
// 恢復(fù)原來的功能
const result = original.apply(this, arguments)
// 把類數(shù)組對象變?yōu)閿?shù)組
const args = [...arguments]
// 把數(shù)組身上的__ob__取出來,__ob__已經(jīng)被添加了,因?yàn)閿?shù)組不是最高層,在遍歷obj對象第一層的時(shí)候,已經(jīng)添加了__ob__屬性
const ob = this.__ob__
//push, unshift, splice可以插入新項(xiàng),因此也要把插入的新項(xiàng)也變?yōu)閛bserve
let inserted = []
switch(methodName){
case 'push':
case 'unshift':
inserted = arguments;
break;
case 'splice':
// splice格式是splice(下標(biāo)、數(shù)量、插入的新項(xiàng))
inserted = args.slice(2);
break;
}
// 判斷有沒有要插入的新項(xiàng), 如果有讓新項(xiàng)也變?yōu)轫憫?yīng)的
if (inserted) {
ob.observeArray(inserted)
}
return result
}, false)
})
// 在observer 中處理數(shù)組
// Observer
class Observer {
constructor(value) {
// 給實(shí)例添加__ob__屬性,數(shù)值是這次new的實(shí)例
def(value, '__ob__', this, false)
// 如果是數(shù)組,需要把數(shù)組的原型,指向arrayMethods
if(Array.isArray(value)) {
Object.setPrototypeOf(value, arrayMethods)
// 讓數(shù)組可以響應(yīng)式、既observe
this.observeArray(value)
} else {
this.walk(value)
}
}
// 遍歷
walk(value) {
for (let k in value) {
defineReactive(value, k)
}
}
// 數(shù)組的特殊遍歷
observeArray(arr) {
for(let i = 0, l = arr.length; i < l; i++) {
// 逐項(xiàng)進(jìn)行observe
observe(arr[i])
}
}
}依賴收集
- 需要用到數(shù)據(jù)的地方,稱為依賴
- vue1.X, 細(xì)粒度依賴、用到數(shù)據(jù)的DOM依賴
- vue2.X, 中等粒度依賴、用到數(shù)據(jù)的組件是依賴
- 在getter收集依賴、在setter觸發(fā)依賴
dep類
- 把依賴收集的代碼封裝,專門用來管理依賴,每個Observer的實(shí)例,成員中都有一個Dep的實(shí)例
- dep使用發(fā)布訂閱模式、當(dāng)數(shù)據(jù)發(fā)生變化時(shí),會循環(huán)依賴列表,把所有的watcher都通知一遍
watcher類
- 中介、數(shù)據(jù)發(fā)生變化時(shí)通過Watcher進(jìn)行中轉(zhuǎn),通知組件
- 依賴就是watcher、只有watcher觸發(fā)的getter才會收集依賴、哪個watcher觸發(fā)了getter、就把哪個watcher收集到dep中
- whatcher把自己設(shè)置到一個全局指定的位置,然后讀取數(shù)據(jù),由于讀取了數(shù)據(jù),會觸發(fā)這個數(shù)據(jù)的getter。
- 在getter中就能得到當(dāng)前正在讀取數(shù)據(jù)的watcher,并把這個watcher收集到dep中
- dep類
class Dep{
constructor(){
// dep類的構(gòu)造器
// 用數(shù)組存儲自己訂閱者, 存儲的是watcher的實(shí)例
this.subs = []
}
// 添加訂閱
addSub(){
this.subs.push(sub)
}
// 添加依賴
depend() {
// Dep.target是我們自己指定的全局的位置,主要是全局唯一,沒歧義
if(Dep.target){
this.addSub(Dep.target)
}
}
// 通知更新
notify() {
// 淺克隆一份
const subs = this.subs.slice()
// 遍歷
for ()let i = 0, l = subs.length; i < 1; i++){
subs[i].update()
}
}
}- watcher類
class Watcher{
constructor(target, expression, callback){
// watcher類的構(gòu)造器
this.target = target
// parsePath是一個高階函數(shù)、用于拿到obj最底層的值
// getter被賦值的為函數(shù)
this.getter = parsePath(expression)
this.callback = callback
this.value = this.get()
}
update() {
this.run()
}
get() {
// 進(jìn)入依賴收集階段,讓全局的Dep.target設(shè)置為watcher本身,那么就是進(jìn)入依賴收集階段
Dep.target = this
const obj = this.traget
// 取出最底層的值
try {
this.getter(obj)
} finally {
Dep.target = null
}
return value
}
run() {
this.getAndInvoke(this.callback)
}
getAndInvoke(cb) {
const value = this.get()
if (value !== this.value || typeof value == 'object') {
const oldValue = this.value
this.value = value
cb.call(this.target, value, oldValue)
}
}
}
// parsePath函數(shù)
function parsePath(str) {
var segments = str.split('.')
// 返回一個函數(shù)
return (obj) => {
for (let i = 0; i < segments.length; i++) {
obj = obj[segments[i]]
}
return obj
}
}
var fb = parsePath('a.b.c.d.e')
var v =fb({a:{b:{c:{d:{e:12}}}}})
console.log(v) // 12
//dep和watcher實(shí)例化的位置
dep: defineReactive的閉包中(創(chuàng)建響應(yīng)式中、閉包內(nèi)可以得到)、observer構(gòu)造器內(nèi)
//defineReactive
function defineReactive(data, key, val) {
const dep = new Dep()
console.log(data, key, val)
if (arguments.length == 2) {
val = data[key]
}
// 子元素要進(jìn)行遞歸
let childOb = observe(val)
Object.defineProperty(data, key, {
get() {
// 如果處于依賴收集階段
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
}
}
return val
},
set(newValue) {
// 如果你要設(shè)置的值和原值相等,直接返回
if(newValue === val) return
val = newValue
// 設(shè)置了新值,也要被observe
childOb = observe(newValue)
// 發(fā)布訂閱模式,通知dep
}
})
}
// Observer
class Observer {
constructor(value) {
// 每一個observer實(shí)例身上,都有一個dep
this.dep = new Dep()
// 給實(shí)例添加__ob__屬性,數(shù)值是這次new的實(shí)例
def(value, '__ob__', this, false)
// 如果是數(shù)組,需要把數(shù)組的原型,指向arrayMethods
if(Array.isArray(value)) {
Object.setPrototypeOf(value, arrayMethods)
// 讓數(shù)組可以響應(yīng)式、既observe
this.observeArray(value)
} else {
this.walk(value)
}
}
// 遍歷
walk(value) {
for (let k in value) {
defineReactive(value, k)
}
}
// 數(shù)組的特殊遍歷
observeArray(arr) {
for(let i = 0, l = arr.length; i < l; i++) {
// 逐項(xiàng)進(jìn)行observe
observe(arr[i])
}
}
}
methodsNeedChange.foreach(methodName => {
// 備份原來的方法, 因?yàn)閿?shù)組的功能不能被剝奪
const original = arrayPrototype[methodName]
// 定義新的方法
def(arrayMethods, methodName, functio(this){
// 恢復(fù)原來的功能
const result = original.apply(this, arguments)
// 把類數(shù)組對象變?yōu)閿?shù)組
const args = [...arguments]
// 把數(shù)組身上的__ob__取出來,__ob__已經(jīng)被添加了,因?yàn)閿?shù)組不是最高層,在遍歷obj對象第一層的時(shí)候,已經(jīng)添加了__ob__屬性
const ob = this.__ob__
//push, unshift, splice可以插入新項(xiàng),因此也要把插入的新項(xiàng)也變?yōu)閛bserve
let inserted = []
switch(methodName){
case 'push':
case 'unshift':
inserted = arguments;
break;
case 'splice':
// splice格式是splice(下標(biāo)、數(shù)量、插入的新項(xiàng))
inserted = args.slice(2);
break;
}
// 判斷有沒有要插入的新項(xiàng), 如果有讓新項(xiàng)也變?yōu)轫憫?yīng)的
if (inserted) {
ob.observeArray(inserted)
}
ob.dep.notify()
return result
}, false)
})dep類和watcher類 沒太懂,后續(xù)研究后詳細(xì)說明下,更多關(guān)于vue修改數(shù)據(jù)視圖更新的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
unplugin-auto-import的配置以及eslint報(bào)錯解決詳解
unplugin-auto-import?解決了vue3-hook、vue-router、useVue等多個插件的自動導(dǎo)入,也支持自定義插件的自動導(dǎo)入,是一個功能強(qiáng)大的typescript支持工具,這篇文章主要給大家介紹了關(guān)于unplugin-auto-import的配置以及eslint報(bào)錯解決的相關(guān)資料,需要的朋友可以參考下2022-08-08
使用el-checkbox-group選中后值為true和false遇到的坑
這篇文章主要介紹了使用el-checkbox-group選中后值為true和false遇到的坑及解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
cesium開發(fā)之如何在vue項(xiàng)目中使用cesium,使用離線地圖資源
這篇文章主要介紹了cesium開發(fā)之如何在vue項(xiàng)目中使用cesium,使用離線地圖資源問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
Vue + Echarts頁面內(nèi)存占用高的問題解決方案
點(diǎn)擊左側(cè)的菜單可以切換不同的看板,有些看板頁面中的報(bào)表比較多,用戶多次切換后頁面的內(nèi)存占用可以上升為GB級,嚴(yán)重時(shí)導(dǎo)致頁面內(nèi)存溢出,使得頁面崩潰,極大影響了用戶體驗(yàn),本文給大家介紹Vue + Echarts頁面內(nèi)存占用高的問題解決方案,感興趣的朋友一起看看吧2024-02-02
vue2.0 watch里面的 deep和immediate用法說明
這篇文章主要介紹了vue2.0 watch里面的 deep和immediate用法說明,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10
springboot?vue接口測試前端動態(tài)增刪表單功能實(shí)現(xiàn)
這篇文章主要為大家介紹了springboot?vue接口測試前端動態(tài)增刪表單功能實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
Vue CLI3移動端適配(px2rem或postcss-plugin-px2rem)
這篇文章主要介紹了Vue CLI3移動端適配(px2rem或postcss-plugin-px2rem),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
node.js開發(fā)輔助工具nodemon安裝與配置詳解
node.js代碼修改后,需要重新啟動 Express 應(yīng)用,所做的修改才能生效。若之后的每次代碼修改都要重復(fù)這樣的操作,勢必會影響開發(fā)效率,本文將詳細(xì)介紹Nodemon,它會監(jiān)測項(xiàng)目中的所有文件,一旦發(fā)現(xiàn)文件有改動,Nodemon 會自動重啟應(yīng)用2020-02-02

