最新文章專題視頻專題問(wèn)答1問(wèn)答10問(wèn)答100問(wèn)答1000問(wèn)答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
問(wèn)答文章1 問(wèn)答文章501 問(wèn)答文章1001 問(wèn)答文章1501 問(wèn)答文章2001 問(wèn)答文章2501 問(wèn)答文章3001 問(wèn)答文章3501 問(wèn)答文章4001 問(wèn)答文章4501 問(wèn)答文章5001 問(wèn)答文章5501 問(wèn)答文章6001 問(wèn)答文章6501 問(wèn)答文章7001 問(wèn)答文章7501 問(wèn)答文章8001 問(wèn)答文章8501 問(wèn)答文章9001 問(wèn)答文章9501
當(dāng)前位置: 首頁(yè) - 科技 - 知識(shí)百科 - 正文

實(shí)例解析ES6 Proxy使用場(chǎng)景介紹

來(lái)源:懂視網(wǎng) 責(zé)編:小采 時(shí)間:2020-11-27 22:22:03
文檔

實(shí)例解析ES6 Proxy使用場(chǎng)景介紹

實(shí)例解析ES6 Proxy使用場(chǎng)景介紹:ES6 中的箭頭函數(shù)、數(shù)組解構(gòu)、rest 參數(shù)等特性一經(jīng)實(shí)現(xiàn)就廣為流傳,但類似 Proxy 這樣的特性卻很少見(jiàn)到有開(kāi)發(fā)者在使用,一方面在于瀏覽器的兼容性,另一方面也在于要想發(fā)揮這些特性的優(yōu)勢(shì)需要開(kāi)發(fā)者深入地理解其使用場(chǎng)景。就我個(gè)人而言是非常喜歡 ES6 的
推薦度:
導(dǎo)讀實(shí)例解析ES6 Proxy使用場(chǎng)景介紹:ES6 中的箭頭函數(shù)、數(shù)組解構(gòu)、rest 參數(shù)等特性一經(jīng)實(shí)現(xiàn)就廣為流傳,但類似 Proxy 這樣的特性卻很少見(jiàn)到有開(kāi)發(fā)者在使用,一方面在于瀏覽器的兼容性,另一方面也在于要想發(fā)揮這些特性的優(yōu)勢(shì)需要開(kāi)發(fā)者深入地理解其使用場(chǎng)景。就我個(gè)人而言是非常喜歡 ES6 的

ES6 中的箭頭函數(shù)、數(shù)組解構(gòu)、rest 參數(shù)等特性一經(jīng)實(shí)現(xiàn)就廣為流傳,但類似 Proxy 這樣的特性卻很少見(jiàn)到有開(kāi)發(fā)者在使用,一方面在于瀏覽器的兼容性,另一方面也在于要想發(fā)揮這些特性的優(yōu)勢(shì)需要開(kāi)發(fā)者深入地理解其使用場(chǎng)景。就我個(gè)人而言是非常喜歡 ES6 的 Proxy,因?yàn)樗屛覀円院?jiǎn)潔易懂的方式控制了外部對(duì)對(duì)象的訪問(wèn)。在下文中,首先我會(huì)介紹 Proxy 的使用方式,然后列舉具體實(shí)例解釋 Proxy 的使用場(chǎng)景。

Proxy,見(jiàn)名知意,其功能非常類似于設(shè)計(jì)模式中的代理模式,該模式常用于三個(gè)方面:

  1. 攔截和監(jiān)視外部對(duì)對(duì)象的訪問(wèn)
  2. 降低函數(shù)或類的復(fù)雜度
  3. 在復(fù)雜操作前對(duì)操作進(jìn)行校驗(yàn)或?qū)λ栀Y源進(jìn)行管理

在支持 Proxy 的瀏覽器環(huán)境中,Proxy 是一個(gè)全局對(duì)象,可以直接使用。Proxy(target, handler) 是一個(gè)構(gòu)造函數(shù),target 是被代理的對(duì)象,handlder 是聲明了各類代理操作的對(duì)象,最終返回一個(gè)代理對(duì)象。外界每次通過(guò)代理對(duì)象訪問(wèn) target 對(duì)象的屬性時(shí),就會(huì)經(jīng)過(guò) handler 對(duì)象,從這個(gè)流程來(lái)看,代理對(duì)象很類似 middleware(中間件)。那么 Proxy 可以攔截什么操作呢?最常見(jiàn)的就是 get(讀取)、set(修改)對(duì)象屬性等操作,完整的可攔截操作列表請(qǐng)點(diǎn)擊這里。此外,Proxy 對(duì)象還提供了一個(gè) revoke 方法,可以隨時(shí)注銷所有的代理操作。在我們正式介紹 Proxy 之前,建議你對(duì) Reflect 有一定的了解,它也是一個(gè) ES6 新增的全局對(duì)象,詳細(xì)信息請(qǐng)參考MDN Reflect。

Basic

const target = { 
 name: 'Billy Bob',
 age: 15
};

const handler = { 
 get(target, key, proxy) {
 const today = new Date();
 console.log(`GET request made for ${key} at ${today}`);

 return Reflect.get(target, key, proxy);
 }
};

const proxy = new Proxy(target, handler);
proxy.name;
// => "GET request made for name at Thu Jul 21 2016 15:26:20 GMT+0800 (CST)"
// => "Billy Bob"

在上面的代碼中,我們首先定義了一個(gè)被代理的目標(biāo)對(duì)象 target,然后聲明了包含所有代理操作的 handler 對(duì)象,接下來(lái)使用 Proxy(target, handler) 創(chuàng)建代理對(duì)象 proxy,此后所有使用 proxy 對(duì) target 屬性的訪問(wèn)都會(huì)經(jīng)過(guò) handler 的處理。

1. 抽離校驗(yàn)?zāi)K

讓我們從一個(gè)簡(jiǎn)單的類型校驗(yàn)開(kāi)始做起,這個(gè)示例演示了如何使用 Proxy 保障數(shù)據(jù)類型的準(zhǔn)確性:

let numericDataStore = { 
 count: 0,
 amount: 1234,
 total: 14
};

numericDataStore = new Proxy(numericDataStore, { 
 set(target, key, value, proxy) {
 if (typeof value !== 'number') {
 throw Error("Properties in numericDataStore can only be numbers");
 }
 return Reflect.set(target, key, value, proxy);
 }
});

// 拋出錯(cuò)誤,因?yàn)?"foo" 不是數(shù)值
numericDataStore.count = "foo";

// 賦值成功
numericDataStore.count = 333;

如果要直接為對(duì)象的所有屬性開(kāi)發(fā)一個(gè)校驗(yàn)器可能很快就會(huì)讓代碼結(jié)構(gòu)變得臃腫,使用 Proxy 則可以將校驗(yàn)器從核心邏輯分離出來(lái)自成一體:

function createValidator(target, validator) { 
 return new Proxy(target, {
 _validator: validator,
 set(target, key, value, proxy) {
 if (target.hasOwnProperty(key)) {
 let validator = this._validator[key];
 if (!!validator(value)) {
 return Reflect.set(target, key, value, proxy);
 } else {
 throw Error(`Cannot set ${key} to ${value}. Invalid.`);
 }
 } else {
 throw Error(`${key} is not a valid property`)
 }
 }
 });
}

const personValidators = { 
 name(val) {
 return typeof val === 'string';
 },
 age(val) {
 return typeof age === 'number' && age > 18;
 }
}
class Person { 
 constructor(name, age) {
 this.name = name;
 this.age = age;
 return createValidator(this, personValidators);
 }
}

const bill = new Person('Bill', 25);

// 以下操作都會(huì)報(bào)錯(cuò)
bill.name = 0; 
bill.age = 'Bill'; 
bill.age = 15; 

通過(guò)校驗(yàn)器和主邏輯的分離,你可以無(wú)限擴(kuò)展 personValidators 校驗(yàn)器的內(nèi)容,而不會(huì)對(duì)相關(guān)的類或函數(shù)造成直接破壞。更復(fù)雜一點(diǎn),我們還可以使用 Proxy 模擬類型檢查,檢查函數(shù)是否接收了類型和數(shù)量都正確的參數(shù):

let obj = { 
 pickyMethodOne: function(obj, str, num) { /* ... */ },
 pickyMethodTwo: function(num, obj) { /*... */ }
};

const argTypes = { 
 pickyMethodOne: ["object", "string", "number"],
 pickyMethodTwo: ["number", "object"]
};

obj = new Proxy(obj, { 
 get: function(target, key, proxy) {
 var value = target[key];
 return function(...args) {
 var checkArgs = argChecker(key, args, argTypes[key]);
 return Reflect.apply(value, target, args);
 };
 }
});

function argChecker(name, args, checkers) { 
 for (var idx = 0; idx < args.length; idx++) {
 var arg = args[idx];
 var type = checkers[idx];
 if (!arg || typeof arg !== type) {
 console.warn(`You are incorrectly implementing the signature of ${name}. Check param ${idx + 1}`);
 }
 }
}

obj.pickyMethodOne(); 
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 1
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 2
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 3

obj.pickyMethodTwo("wopdopadoo", {}); 
// > You are incorrectly implementing the signature of pickyMethodTwo. Check param 1

// No warnings logged
obj.pickyMethodOne({}, "a little string", 123); 
obj.pickyMethodOne(123, {});

2. 私有屬性

在 JavaScript 或其他語(yǔ)言中,大家會(huì)約定俗成地在變量名之前添加下劃線 _ 來(lái)表明這是一個(gè)私有屬性(并不是真正的私有),但我們無(wú)法保證真的沒(méi)人會(huì)去訪問(wèn)或修改它。在下面的代碼中,我們聲明了一個(gè)私有的 apiKey,便于 api 這個(gè)對(duì)象內(nèi)部的方法調(diào)用,但不希望從外部也能夠訪問(wèn) api._apiKey:

var api = { 
 _apiKey: '123abc456def',
 /* mock methods that use this._apiKey */
 getUsers: function(){}, 
 getUser: function(userId){}, 
 setUser: function(userId, config){}
};

// logs '123abc456def';
console.log("An apiKey we want to keep private", api._apiKey);

// get and mutate _apiKeys as desired
var apiKey = api._apiKey; 
api._apiKey = '987654321';

很顯然,約定俗成是沒(méi)有束縛力的。使用 ES6 Proxy 我們就可以實(shí)現(xiàn)真實(shí)的私有變量了,下面針對(duì)不同的讀取方式演示兩個(gè)不同的私有化方法。第一種方法是使用 set / get 攔截讀寫請(qǐng)求并返回 undefined:

let api = { 
 _apiKey: '123abc456def',
 getUsers: function(){ }, 
 getUser: function(userId){ }, 
 setUser: function(userId, config){ }
};

const RESTRICTED = ['_apiKey'];
api = new Proxy(api, { 
 get(target, key, proxy) {
 if(RESTRICTED.indexOf(key) > -1) {
 throw Error(`${key} is restricted. Please see api documentation for further info.`);
 }
 return Reflect.get(target, key, proxy);
 },
 set(target, key, value, proxy) {
 if(RESTRICTED.indexOf(key) > -1) {
 throw Error(`${key} is restricted. Please see api documentation for further info.`);
 }
 return Reflect.get(target, key, value, proxy);
 }
});

// 以下操作都會(huì)拋出錯(cuò)誤
console.log(api._apiKey);
api._apiKey = '987654321'; 

第二種方法是使用 has 攔截 in 操作:

var api = { 
 _apiKey: '123abc456def',
 getUsers: function(){ }, 
 getUser: function(userId){ }, 
 setUser: function(userId, config){ }
};

const RESTRICTED = ['_apiKey'];
api = new Proxy(api, { 
 has(target, key) {
 return (RESTRICTED.indexOf(key) > -1) ?
 false :
 Reflect.has(target, key);
 }
});

// these log false, and `for in` iterators will ignore _apiKey
console.log("_apiKey" in api);

for (var key in api) { 
 if (api.hasOwnProperty(key) && key === "_apiKey") {
 console.log("This will never be logged because the proxy obscures _apiKey...")
 }
}

3. 訪問(wèn)日志

對(duì)于那些調(diào)用頻繁、運(yùn)行緩慢或占用執(zhí)行環(huán)境資源較多的屬性或接口,開(kāi)發(fā)者會(huì)希望記錄它們的使用情況或性能表現(xiàn),這個(gè)時(shí)候就可以使用 Proxy 充當(dāng)中間件的角色,輕而易舉實(shí)現(xiàn)日志功能:

let api = { 
 _apiKey: '123abc456def',
 getUsers: function() { /* ... */ },
 getUser: function(userId) { /* ... */ },
 setUser: function(userId, config) { /* ... */ }
};

function logMethodAsync(timestamp, method) { 
 setTimeout(function() {
 console.log(`${timestamp} - Logging ${method} request asynchronously.`);
 }, 0)
}

api = new Proxy(api, { 
 get: function(target, key, proxy) {
 var value = target[key];
 return function(...arguments) {
 logMethodAsync(new Date(), key);
 return Reflect.apply(value, target, arguments);
 };
 }
});

api.getUsers();

4. 預(yù)警和攔截

假設(shè)你不想讓其他開(kāi)發(fā)者刪除 noDelete 屬性,還想讓調(diào)用 oldMethod 的開(kāi)發(fā)者了解到這個(gè)方法已經(jīng)被廢棄了,或者告訴開(kāi)發(fā)者不要修改 doNotChange 屬性,那么就可以使用 Proxy 來(lái)實(shí)現(xiàn):

let dataStore = { 
 noDelete: 1235,
 oldMethod: function() {/*...*/ },
 doNotChange: "tried and true"
};

const NODELETE = ['noDelete']; 
const NOCHANGE = ['doNotChange'];
const DEPRECATED = ['oldMethod']; 

dataStore = new Proxy(dataStore, { 
 set(target, key, value, proxy) {
 if (NOCHANGE.includes(key)) {
 throw Error(`Error! ${key} is immutable.`);
 }
 return Reflect.set(target, key, value, proxy);
 },
 deleteProperty(target, key) {
 if (NODELETE.includes(key)) {
 throw Error(`Error! ${key} cannot be deleted.`);
 }
 return Reflect.deleteProperty(target, key);

 },
 get(target, key, proxy) {
 if (DEPRECATED.includes(key)) {
 console.warn(`Warning! ${key} is deprecated.`);
 }
 var val = target[key];

 return typeof val === 'function' ?
 function(...args) {
 Reflect.apply(target[key], target, args);
 } :
 val;
 }
});

// these will throw errors or log warnings, respectively
dataStore.doNotChange = "foo"; 
delete dataStore.noDelete; 
dataStore.oldMethod();

5. 過(guò)濾操作

某些操作會(huì)非常占用資源,比如傳輸大文件,這個(gè)時(shí)候如果文件已經(jīng)在分塊發(fā)送了,就不需要在對(duì)新的請(qǐng)求作出相應(yīng)(非絕對(duì)),這個(gè)時(shí)候就可以使用 Proxy 對(duì)當(dāng)請(qǐng)求進(jìn)行特征檢測(cè),并根據(jù)特征過(guò)濾出哪些是不需要響應(yīng)的,哪些是需要響應(yīng)的。下面的代碼簡(jiǎn)單演示了過(guò)濾特征的方式,并不是完整代碼,相信大家會(huì)理解其中的妙處:

let obj = { 
 getGiantFile: function(fileId) {/*...*/ }
};

obj = new Proxy(obj, { 
 get(target, key, proxy) {
 return function(...args) {
 const id = args[0];
 let isEnroute = checkEnroute(id);
 let isDownloading = checkStatus(id); 
 let cached = getCached(id);

 if (isEnroute || isDownloading) {
 return false;
 }
 if (cached) {
 return cached;
 }
 return Reflect.apply(target[key], target, args);
 }
 }
});

6. 中斷代理

Proxy 支持隨時(shí)取消對(duì) target 的代理,這一操作常用于完全封閉對(duì)數(shù)據(jù)或接口的訪問(wèn)。在下面的示例中,我們使用了 Proxy.revocable 方法創(chuàng)建了可撤銷代理的代理對(duì)象:

let sensitiveData = { username: 'devbryce' };
const {sensitiveData, revokeAccess} = Proxy.revocable(sensitiveData, handler);
function handleSuspectedHack(){ 
 revokeAccess();
}

// logs 'devbryce'
console.log(sensitiveData.username);
handleSuspectedHack();
// TypeError: Revoked
console.log(sensitiveData.username);

Decorator

ES7 中實(shí)現(xiàn)的 Decorator,相當(dāng)于設(shè)計(jì)模式中的裝飾器模式。如果簡(jiǎn)單地區(qū)分 Proxy 和 Decorator 的使用場(chǎng)景,可以概括為:Proxy 的核心作用是控制外界對(duì)被代理者內(nèi)部的訪問(wèn),Decorator 的核心作用是增強(qiáng)被裝飾者的功能。只要在它們核心的使用場(chǎng)景上做好區(qū)別,那么像是訪問(wèn)日志這樣的功能,雖然本文使用了 Proxy 實(shí)現(xiàn),但也可以使用 Decorator 實(shí)現(xiàn),開(kāi)發(fā)者可以根據(jù)項(xiàng)目的需求、團(tuán)隊(duì)的規(guī)范、自己的偏好自由選擇。

聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

文檔

實(shí)例解析ES6 Proxy使用場(chǎng)景介紹

實(shí)例解析ES6 Proxy使用場(chǎng)景介紹:ES6 中的箭頭函數(shù)、數(shù)組解構(gòu)、rest 參數(shù)等特性一經(jīng)實(shí)現(xiàn)就廣為流傳,但類似 Proxy 這樣的特性卻很少見(jiàn)到有開(kāi)發(fā)者在使用,一方面在于瀏覽器的兼容性,另一方面也在于要想發(fā)揮這些特性的優(yōu)勢(shì)需要開(kāi)發(fā)者深入地理解其使用場(chǎng)景。就我個(gè)人而言是非常喜歡 ES6 的
推薦度:
標(biāo)簽: 介紹 pr 詳解
  • 熱門焦點(diǎn)

最新推薦

猜你喜歡

熱門推薦

專題
Top