[Frontend筆記] Vue是如何實現響應式數據

結合虛擬DOM、數據劫持、Side Effect 函數,在Vue中實現響應式數據

Posted by 李定宇 on Thursday, October 6, 2022

Vue中的響應式數據

前言

在Vue3發布的這段時間裡面,可以看到許多人分享在面試中,很常被問到「Vue2和Vue3的響應式數據的原理分別是什麼」

如果簡單回答一下:「Vue2是使用getter和setter來做響應式數據、而Vue3是用proxy(代理)的方式來實現響應式」,搞不好就可以應付一下面試官,表示我們是會大概了解一下新技術、而不是直接拿來用的工程師;但如果被更深入得追問:「所以這兩者的原理個別是什麼?」,如果沒有對虛擬DOMSide 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函數,便可以實現響應式數據!

  1. 利用一個 new Set()來存取所有的Side Effect函數
  2. 定義Side Effect 函數,使更新的數據text渲染到靜態HTML上
  3. 綁定數據,讓數據更動的時候出發 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 - 初稿