跳至主要内容

[Vue] Proxy

TL;DR

參考資料

相關連結


Proxy簡介

Proxy本身是JS提供的一個建構函式,可以用來建立一個Proxy物件。所以他並不是Vue獨有的東西。

那麼為什麼要先來介紹Proxy呢?這是因為Vue3本身是透過Proxy來實現雙向綁定,以及資料變更時即時重新渲染畫面的。會特別強調是Vue3是因為,Vue2本身是透過defineProperty設定getter與setter,來達成這個目的的。

MDN官方文件這麼解釋:

Proxy 對象用於創建一個對象的代理,從而實現基本操作的攔截和自定義(如屬性查找、賦值、列舉、函數調用等)。

語法

const p = new Proxy(target, handler)
  1. target:要使用Proxy包裝的目標對象,需要為物件(包含Array,Object,Function,甚至是另一個Proxy物件皆可)
  2. handler:控制器。通常會定義gettersetter,定義了在執行各種操作時代理p的行為。

範例

參數介紹

完整範例

// 定義控制器
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; //直接寫入不做額外操作
return true; //告知操作已經完成
}
}

// 建立一個Proxy物件,
const newObj= new Proxy({a:1}, handler);
console.log(newObj); // Proxy(Object) {a: 1}

newObj.a=2; //'set' {a: 1} 'a' 2 <<由控制器setter觸發,印出的值分別為物件本身、欲操作屬性名、欲傳入的值
console.log(newObj.a) //'get' {a: 1} 'a' <<由控制器getter觸發,印出的值分別為物件本身、欲操作屬性名

setter

// 定義控制器
const handler = {
set(obj,prop,newVal) {
console.log('set:',obj,prop,newVal);
return true; //告知操作已經完成
}
}

// 建立一個Proxy物件,
const newObj= new Proxy({a:1}, handler);
console.log(newObj); // Proxy(Object) {a: 1}

newObj.a=2;
//'set' {a: 1} 'a' 2 <<由控制器setter觸發,印出的值分別為物件本身、欲操作屬性名、欲傳入的值

console.log(newObj); // Proxy(Object) {a: 1}

會發現雖然我們預期將newObj.a的值更改為2,但是看起來沒有正確寫入。這是因為Proxy物件的行為會被攔截,我們需要額外在setter中設定我們想要執行的行為。

假設我們希望直接不做任何操作,將傳入的值給寫入的話,我們需要修改程式碼如下:

const handler = {
set(obj,prop,newVal) {
console.log('set:',obj,prop,newVal);
obj[prop]=newVal;
return true;
}
}
const newObj= new Proxy({a:1}, handler);
newObj.a=2; // (setter觸發) 'set' {a: 1} 'a' 2
console.log(newObj); // Proxy(Object) {a: 2}

相對的,如果我們希望將傳入的值乘上10倍再存入,可以這樣修改:

const handler = {
set(obj,prop,newVal) {
console.log('set:',obj,prop,newVal);
obj[prop]=newVal*10;
return true;
}
}
const newObj= new Proxy({a:1}, handler);
newObj.a=2; // (setter觸發) 'set' {a: 1} 'a' 2
console.log(newObj); // Proxy(Object) {a: 20}

getter

與setter類似,getter的兩個參數也是分別為物件本身以及欲操作屬性名。

const handler = {
get(obj,prop) {
console.log('get:',obj,prop);
return obj[prop];
},
}
const newObj= new Proxy({a:1}, handler);
console.log(newObj.a);
// 'get' {a: 1} 'a' <<getter內的console
// 1 <<因為設定return obj[prop],所以回傳物件內a存放的值,i.e:1

假設我們修改getter內return的值,並且同時移除console來避免其他的顯示內容:

const handler = {
get(obj,prop) {
return '滾滾長江東逝水';
},
}
const newObj= new Proxy({a:1}, handler);
console.log(newObj.a); //'滾滾長江東逝水'

可以發現我們的取值受到proxy攔截,直接回傳出預先定義的固定字串,而非物件內實際存放的1

info

Proxy(target,handler) 的target只能是物件,如果嘗試將不是物件的內容傳入,會出現錯誤

const newObj=new Proxy(1,{});  
// Uncaught TypeError: Cannot create proxy with a non-object as target or handler

// ps:這邊handler雖然是一個空物件,但是仍可視為一個handler,是不會因此報錯的,可以參考下方連結

關於無操作轉發代理,參考无操作转发代理(cn),或是Proxy-Description(en)

透過Proxy即時渲染畫面

使用Vue的時候,我們可以做到資料更動的時候畫面就更新,這邊來透過Proxy實現一個簡易的類似功能。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proxy trigger render example</title>
</head>
<body>
<div id="renderTarget"></div>

<script>
// 取出要渲染的DOM
const el=document.getElementById('renderTarget');
// 定義用來重新渲染畫面的function
const render=(prop,data)=>{
el.innerHTML=`${prop}:${data}`;
}

const handler = {
set(obj,prop,newVal) {
obj[prop]=newVal;
render(prop,obj[prop]); // 在每次觸發setter的時候重新渲染畫面
return true;
}
}

const originObj= {a:1};
const proxyObj= new Proxy(originObj,handler);

proxyObj.a=5;
</script>
</body>
</html>

接著只要每次我們重新對proxyObj.a賦值,畫面就會被更新。