# 雙向綁定
## Vue2 與 Object.defineProperty
:::info
在 Vue2.X 版,使用的是 **Object.defineProperty**
:::
```javascript=
const app = new Vue({
data: {
message: 'Hello',
},
})
app.$mount('#app')
```
> Vue 將遍歷所有的 property,並使用 Object.defineProperty 把這些 property 全部轉為 Getter/Setter。Object.defineProperty 是 ES5 的語法,這也是 Vue 不支援 IE8 以及更低版本瀏覽器的原因。
也就是說,Vue 會將所有資料加上 Getter/Setter 屬性,當資料變動時會發出訊息觸發 Watcher,此時 Watcher 就會尋找與資料連動的 DOM,觸發並且重新渲染。

### 關於 Object.defineProperty
可參考 JavaScript 核心觀念 - [Object.defineProperty](/t17NCdulRLiG0H2m4QUP_Q)
### 實作 v-model
<iframe height="400" style="width: 100%;" scrolling="no" title="Vue2:Make v-model by defineProperty" src="https://codepen.io/ericadu/embed/gOWBXwV?default-tab=js%2Cresult&theme-id=l" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
See the Pen <a href="https://codepen.io/ericadu/pen/gOWBXwV">
Vue2:Make v-model by defineProperty</a> by Erica (<a href="https://codepen.io/ericadu">@ericadu</a>)
on <a href="https://codepen.io">CodePen</a>.
</iframe>
---
## 為什麼畫面沒有更新?<font size="4">:zap:</font>
Vue2 在實作過程中,經常會發生資料重新賦值,但畫面卻沒有更新的狀況。
可能會發生的這種狀況的情境為:
### **一開始沒有被註冊到的物件不會雙向綁定**
Vue2 是透過 `Object.defineProperty` 來實現資料雙向綁定,但如果是在生命週期內才定義物件屬性,因為一開始並沒有透過 `Object.defineProperty` 加上 Setter/Getter,所以這個新增的屬性將不會有雙向綁定的效果。以下提供三種解法:
* 一開始就先定義好物件
* 使用官方提供的 `Vue.$set` 語法:`this.$set(object, key, value)`
* 透過 `Object.assign()` 方法重新建立一個新的物件讓 Vue 去監控
### **陣列的長度改變或是利用索引直接賦值時**
在官方文件有舉例兩點無法監聽陣列的狀況:
> 利用索引直接設置一個陣列時,例如:vm.items[indexOfItem] = newValue
> 修改陣列長度時,例如:vm.items.length = newLength
一般來說資料大多都是為了跑迴圈才使用陣列,但是透過迴圈加入 Setter/Getter 是非常吃效能的,所以通常需要額外處理,因此如果沒有使用 Vue 處理過的語法操作陣列,就會失去雙向綁定的效果。以下提供兩種解法:
* 使用 Vue 處理過的語法操作陣列:
```
push()、pop()、shift()、unshift()、splice()、sort()、reverse()、
```
* 使用 `Vue.$set`
---
## Vue3 與 Proxy
:::info
在 Vue3.X 版,使用的是 **Proxy API**
:::
```javascript=
const app = Vue.createApp({
data() {
return {
message: 'Hello',
}
}
})
app.mount('#app')
```
### Proxy 代理
Proxy 是 ES6 的語法,不同於 `Object.defineProperty`,Proxy 不會直接改變原始資料,而是 **「代理」** 此對象,執行增加/減少的功能。
```
const p = new Proxy(target, handler)
```
* **target**:可以是任何物件,包含陣列或函式
* **handler**:主要是一個物件,裡面會有許多 target 的操作行為
使用 Proxy 取代 `Object.defineProperty`,除了改善 Vue 的執行效能外,最主要也解決 Vue2 的部分問題:
* 無法深層監聽資料:
Vue2 是採用迴圈方向去往深層監聽,因此效能上會比較差
* 陣列長度若發生變化,雙向綁定會失效
### Proxy 結構
Proxy 的結構分為 Handler(控制器) 和 Target(目標)。
用 Handler 來監控 Target 物件的變化,並透過 Getter/Setter 來進行取值或存值。
```javascript=
// 建立 Proxy 控制器
const handler = {
get(obj, prop) {
return obj[prop]
},
set(obj, prop, newVal) {
obj[prop] = newVal
return true // 告知結束,避免跳錯
},
}
// 定義 Proxy 物件,target 一定是物件
const newObj = new Proxy({}, handler)
```
### Proxy 運作
1. 首先建立渲染函式,並傳入兩個參數分別為:
* prop:要渲染的對象
* data:要渲染的資料
```javascript=
const render = (prop, data) => {
const target = document.querySelector('#app')
target.innerHTML = `${prop}:${data}`
}
```
2. 建立控制器,當每次監聽到資料變動時,控制器會先用 Getter 取值並回傳,然後再透過 Setter 寫入新的值,並且呼叫渲染函式,將資料渲染到畫面上。
```javascript=
const handler = {
get(obj, prop) {
console.log('get:', obj, prop)
return obj[prop]
},
set(obj, prop, newVal) {
console.log('set:', obj, prop, newVal)
obj[prop] = newVal
render(prop, obj[prop])
return true
},
}
```
3. 接著要定義 Proxy 物件,這時先用 `console` 看一下 `newObj` 的內容,目前只會印出 Getter 取得的值,還不會進行寫入的動作。
```javascript=
const newObj = new Proxy({message: 'Hi'}, handler)
console.log(newObj)
```

4. 最後要透過 Setter 寫入資料,這時才會呼叫渲染函式,並重新渲染到畫面上。
```javascript=
newObj.message = 'Hello'
```

### 實作 v-model
<iframe height="400" style="width: 100%;" scrolling="no" title="Vue3:v-model by Proxy" src="https://codepen.io/ericadu/embed/bGWmymy?default-tab=js%2Cresult&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
See the Pen <a href="https://codepen.io/ericadu/pen/bGWmymy">
Vue3:v-model by Proxy</a> by Erica (<a href="https://codepen.io/ericadu">@ericadu</a>)
on <a href="https://codepen.io">CodePen</a>.
</iframe>
---
## 參考資料
* [JavaScript 核心篇](https://www.hexschool.com/courses/js-core.html)
* [深入響應式原理](https://cn.vuejs.org/v2/guide/reactivity.html)
* [淺談 Vue2 雙向綁定的概念與實作](https://hsiangfeng.github.io/vue/20210607/665675438/)
* [淺談 Vue3 雙向綁定的概念與實作](https://hsiangfeng.github.io/vue/20210627/555169856/)
* [為什麼畫面沒有隨資料更新 - PJCHENder](https://pjchender.blogspot.com/2017/05/vue-vue-reactivity.html)