vue3.0後的學習proxy筆記(覺得和reflect一起對比學習效果更佳)
最近看相關報道,vue3.0 開發計劃 傳送門 https://juejin.im/post/5bb719b9f265da0ab915dbdd
其中,監測機制:
一句話介紹:更加全面、精準、高效;更具可除錯性的響應跟蹤;以及可用來建立響應式物件的 API。
3.0 將帶來一個基於 Proxy 的 observer 實現,它可以提供覆蓋語言 (JavaScript——譯註) 全範圍的響應式能力,消除了當前 Vue 2 系列中基於 Object.defineProperty
所存在的一些侷限,如:
- 對屬性的新增、刪除動作的監測
- 對陣列基於下標的修改、對於 .length 修改的監測
- 對 Map、Set、WeakMap 和 WeakSet 的支援
另外這個新的 observer 還有以下特性:
- 公開的用於建立 observable (即響應式物件——譯註) 的 API。這為小型到中型的應用提供了一種輕量級的、極其簡單的跨元件狀態管理解決方案。(譯註:在這之前我們可以通過另外
new Vue({data : {...}})
來建立這裡所謂的 observable;另外,其實 vuex 內部也是用這種方式來實現的) - 預設為惰性監測(Lazy Observation)。在 2.x 版本中,任何響應式資料,不管它的大小如何,都會在啟動的時候被監測。如果你的資料量很大的話,在應用啟動的時候,這就可能造成可觀的效能消耗。而在 3.x 版本中,只有應用的初始可見部分所用到的資料會被監測,更不用說這種監測方案本身其實也是更加快的。
- 更精準的變動通知。舉個例子:在 2.x 系列中,通過
Vue.set
強制新增一個新的屬性,將導致所有依賴於這個物件的 watch 函式都會被執行一次;而在 3.x 中,只有依賴於這個具體屬性的 watch 函式會被通知到。 - 不可變監測物件(Immutable observable):我們可以建立一個物件的“不可變”版本,以此來阻止對他的修改——包括他的巢狀屬性,除非系統內部臨時解除了這個限制。這種機制可以用來凍結傳遞到元件屬性上的物件和處在 mutation 範圍外的 Vuex 狀態樹。
- 更良好的可除錯能力:通過使用新增的
renderTracked
和renderTriggered
那麼 proxy 到底是什麼 該怎麼使用呢???
target是指代理的原物件,它是你需要攔截訪問的原始物件,它總是作為Proxy構造器的第一個方法,也可以傳遞到每個trap中。
handler是一個包含你要進行攔截和處理的物件,也就是你攔截了原始物件以後想幹什麼?主要的代理內容在這裡,是作為Proxy構造器的第二個方法引數傳統,它是實現Proxy API。
trap用來規定對於指定什麼方法進行攔截處理,如果你想攔截get方法的呼叫,那麼你要定義一個get trap。
let obj = { name:"bruce",age:"25"}
let handler = {
//get運算子有兩個引數 - 物件本身和被訪問的屬性。
get:function(target,prop){
console.log('target:'+target+"prop:"+prop);
},
//set操作符有三個引數 - 物件本身,被訪問的屬性和為該屬性設定的值。
set:function(target,prop,value){
if(typeof(value) == 'string'){ //用來作 型別檢驗
}
console.log('target:'+target+"prop:"+prop+"value:"+value);
}
}
let proxy = new Proxy(obj,handler)
proxy.name //列印 target:[object Object]prop:name
proxy.name = 333 //列印 target:[object Object]prop:namevalue:333
proxy與設計模式
在面向物件的程式設計中,代理模式的合理使用能夠很好的體現下面兩條原則:
-
單一職責原則: 面向物件設計中鼓勵將不同的職責分佈到細粒度的物件中,Proxy 在原物件的基礎上進行了功能的衍生而又不影響原物件,符合鬆耦合高內聚的設計理念。
- 開放-封閉原則:代理可以隨時從程式中去掉,而不用對其他部分的程式碼進行修改,在實際場景中,隨著版本的迭代可能會有多種原因不再需要代理,那麼就可以容易的將代理物件換成原物件的呼叫
Proxy
實現前端中3種代理模式的使用場景,分別是:快取代理、驗證代理、實現私有屬性。
1.0.0 快取代理
//計算斐波那契數列 40以上很慢
const getFib = (number) => {
if (number <= 2) {
return 1;
} else {
return getFib(number - 1) + getFib(number - 2);
}
}
//建立快取代理的工廠函式
const getCacheProxy = (fn, cache = new Map()) => {
return new Proxy(fn, {
apply(target, context, args) {
const argsString = args.join(' ');
console.log(args)
if (cache.has(argsString)) {
console.log(argsString)
// 如果有快取,直接返回快取資料
console.log(`輸出${args}的快取結果: ${cache.get(argsString)}`);
return cache.get(argsString);
}
const result = fn(...args);
cache.set(argsString, result);
return result;
}
})
}
//使用
const getFibProxy = getCacheProxy(getFib)
getFibProxy(40); // 102334155
getFibProxy(40); // 輸出40的快取結果: 102334155
1.0.1 驗證代理
// 表單物件
const userForm = {
account: '',
password: '',
}
// 驗證方法
const validators = {
account(value) {
// account 只允許為中文
const re = /^[\u4e00-\u9fa5]+$/;
return {
valid: re.test(value),
error: '"account" is only allowed to be Chinese'
}
},
password(value) {
// password 的長度應該大於6個字元
return {
valid: value.length >= 6,
error: '"password "should more than 6 character'
}
}
}
// 校驗器
const getValidateProxy = (target, validators) => {
return new Proxy(target, {
_validators: validators,
set(target, prop, value) {
if (value === '') {
console.error(`"${prop}" is not allowed to be empty`);
return target[prop] = false;
}
const validResult = this._validators[prop](value);
if(validResult.valid) {
return Reflect.set(target, prop, value);
} else {
console.error(`${validResult.error}`);
return target[prop] = false;
}
}
})
}
// 使用
const userFormProxy = getValidateProxy(userForm, validators);
userFormProxy.account = '123'; // "account" is only allowed to be Chinese
userFormProxy.password = 'he'; // "password "should more than 6 character
1.0.2 實現私有屬性 (設定訪問限制)
function getPrivateProps(obj, filterFunc) {
return new Proxy(obj, {
get(obj, prop) {
if (!filterFunc(prop)) {
let value = Reflect.get(obj, prop);
// 如果是方法, 將this指向修改原物件
if (typeof value === 'function') {
value = value.bind(obj);
}
return value;
}
},
set(obj, prop, value) {
if (filterFunc(prop)) {
throw new TypeError(`Can't set property "${prop}"`);
}
return Reflect.set(obj, prop, value);
},
has(obj, prop) {
return filterFunc(prop) ? false : Reflect.has(obj, prop);
},
ownKeys(obj) {
return Reflect.ownKeys(obj).filter(prop => !filterFunc(prop));
},
getOwnPropertyDescriptor(obj, prop) {
return filterFunc(prop) ? undefined : Reflect.getOwnPropertyDescriptor(obj, prop);
}
});
}
//因為私有屬性 一般以 _開頭 這裡就簡單過濾
function propFilter(prop) {
return prop.indexOf('_') === 0;
}
參考資料:https://www.imooc.com/article/47896
http://es6.ruanyifeng.com/#docs/proxy
https://www.cnblogs.com/diligenceday/p/5474126.html