Vue中的響應式數據
前言
在Vue3發布的這段時間裡面,可以看到許多人分享在面試中,很常被問到「Vue2和Vue3的響應式數據的原理分別是什麼」?
如果簡單回答一下:「Vue2是使用getter和setter來做響應式數據、而Vue3是用proxy(代理)的方式來實現響應式」,搞不好就可以應付一下面試官,表示我們是會大概了解一下新技術、而不是直接拿來用的工程師;但如果被更深入得追問:「所以這兩者的原理個別是什麼?」,如果沒有對虛擬DOM和Side Effect以及ECMAScript的一些了解,是很難答的上來。
因此本文擷取了Vue3開源團隊中的成員作品《Vue.js 設計與實現》中的程式碼片段,來重現一下Vue中的響應式數據。
虛擬DOM
使用Vue、React的前端工程師,多多少少都知道其框架是基於虛擬DOM來完成畫面的渲染,那麼虛擬DOM是什麼呢?
簡單來說就是一個Javascript物件,例如:
const demoVDom = {
tag: 'div',
children:[
{tag : 'span', children : 'Hello world'}
]
}
然後藉由各個框架的Render函數將虛擬DOM掛載到HTML上面,可以看此VDom codepen
Side Effect Function
Side Effect 函數,簡單來說就是會影響到函數之外的變量,如:
let a = 1;
function sideEffectA(){
a = 2;
}
sideEffectA();
// 執行後,‘a’這個函數之外的變量被修改了
而在網頁前端,Side Effect 函數可以說是到處都是:
// html
<body>
<div class="demo">
<p>Hello Vue2</p>
</div>
</body>
// javascript
setTimeout(()=>{
document.querySelector('.demo').innerHTML = `<p>Hello Vue3</p>`
},3000)
基本上可以說,如果有修改頁面上的任何元素,其都是Side Effect。
響應式數據
回到響應式數據的主題,結合虛擬DOM、render函數、Side Effect函數,我們可以大概釐清思路:
如果現在有一個數據 : let demoData = { text : 'Hello Data'}
,如果修改其中的text
,就執行一次Render函數,便可以實現響應式數據!
- 利用一個
new Set()
來存取所有的Side Effect函數 - 定義Side Effect 函數,使更新的數據
text
渲染到靜態HTML上 - 綁定數據,讓數據更動的時候出發
Set
裡面的所有Side Effect函數
Vue2 中的響應式數據
在Vue2的時代,還沒ES2015
的規範,因此在步驟三的綁定數據,是使用Javascript 中的Object.defineProperty 函數
來實現
// save side-effect function
const bucket = new Set()
// init data
const dataVue2 = { text: '' }
// side effect function
function myVue2Effect() {
const vue2Node = document.querySelector('.vue2')
vue2Node.innerText = dataVue2.text
}
// define reactive
function vue2_defineReactive(obj, key, value) {
Object.defineProperty(obj, key, {
get() {
bucket.add(myVue2Effect)
return value
},
set(newValue) {
if (newValue !== value) {
value = newValue
bucket.forEach((fn) => fn())
}
},
})
}
實際操作可以在此codepen查看
Vue3 中的響應式數據
在ES2015
的規範之後,Vue3 可以使用代理對象Proxy來實現對數據的劫持:
// origin data
const dataVue3 = { text: 'Vue3: hello world' }
// proxy the origin data
const objVue3 = new Proxy(dataVue3, {
get(target, key) {
bucket.add(myVue3Effect)
return target[key]
},
set(target, key, newVal) {
target[key] = newVal
bucket.forEach((fn) => fn())
return true
},
})
實際操作可以在此codepen查看
結論
以上只是實現了簡單的響應式數據,實際上在Vue2/Vue3框架中,因為要考慮到各個數據結構、其他操作(for循環等等)以及效能考量,會使用不一樣的數據結構來處理響應式數據。
Ref
ChangeLog
- 20221006 - 初稿