Proxy Standard Build-in Object
Proxy یک شئ پیش فرض در جاوا اسکریپت است که بر اساس Proxy Design Pattern در جاوا اسکریپت تعریف شده است و قابلیت رهگیری (intercept) و به دام انداختن Property ها و Descriptor های یک Object را واقع می شود . به این ترتیب می توان برخی عملیات های یک شئ را فیلتر گذاری کرد
Proxy خود یک شئ است که روی یک شئ دیگر سوار می شود و می تواند به ویژگی های Public آن دسترسی داشته باشید و آن ها را رهگیری کند و همچنین روی برخی متد های این شئ Middleware اعمال کند (یا همان به دام انداختن پیش از ارسال به سمت بلوک کدی که درخواست اجرای این متد را داشته).
کاربرد های معمول این Build-in Object می توان به موارد زیر ارشاره کرد:
- logs Property access
- Setters , Getters
- validate
- format
- sanitize inputs
در زمان ساخت Proxy دو مورد زیر را باید تعیین کنید:
- target : همان شئ (JS Native Object , …) است که قرار است Proxy روی آن سوار شود و رهگیری روی آن انجام می شود.
- handler : این یک شئ است که متد های پیش فرض Proxy روی آن پیاده سازی می شود و رهگیری توسط این متد ها اتفاق می افتد .
let target = { name: 'ali', } let handler = {} let p = new Proxy(target,handler) console.log(p.name)
به طور مثال می توان با متد Get Proxy Handler Method یک فیلتر گذاری در زمان مقدار دهی Target properties انجام داد:
let target = { name: 'ali', age: 20, } let handler = { get(target,prop,reciever){ console.log(target,prop,reciever) return target[prop] } } let p = new Proxy(target,handler) console.log(p.name)
در مثال بالا می توان پیش از برگشت دادن مقدار درخواست شده مقادیر پارامتر های Get Proxy Handler Method را پرینت گرفت.عملیات بالا می تواند رهگیری دریافت یک مقدار از یک شئ است.
Proxy Handler Methods
مهم ترین متد های رهگیری یک شئ در Handler را در جدول زیر به همراه کاربرد آن مشاهده می کنید. برای مشاهده لیست کامل این متد ها می توان به وب سایت developer.mozilla رجوع کنید.
get | در زمان دریافت مقدار یکی از property های شئ target می توان این مقدار را دریافت کرد یا به دام انداخت . |
set | در زمان set کردن مقادیر در property های شئ target می توان این عملیات را به دام انداخت و مقدار ورودی این property را فیلتر کرد. |
has | در زمان چک کردن با عملگر in می توان مقادیر را رهگیری کرد.
let target = { name: 'ali', age: 27, _id: 20014242, } let handler = { has(target,key){ if('_' == key[0]){ return false } return true } } let p = new Proxy(target,handler) console.log('_id' in p) |
constructor | در زمان اجرای constructor می توان شئ برگشتی از این متد سازنده را فیلتر کرد. |
apply | در زمان اجرای متد apply می توان عملیات را فیلتر کرد . |
defineProperty | به دام انداختن متد استاتیک Object.defineProperty . اگر شما قرار است یک property برای یک Native Object تعریف کنید می توانید لیست داده ها و عملیات را رهگیری کنید. |
deleteProperty | رهگیری در زمان اجرای عملگر delete |
getOwnPropertyDescriptor | به دام انداختن متد استاتیک Object.getOwnPropertyDescriptor . |
Proxy Common Usage Examples
در این بخش کاربردهای Proxy را در قالب یک سری مثال تعیین می کنیم که برای اجرای این امکانات از متدهای Proxy Handlers استفاده می کنیم:
Property Default Value
در این مثال اگر یک Property در target object وجود نداشت مقدار پیش فرض Defualt Value را بر می گرداند.
let target = { name: 'ali', age: 27 } let handler = { get(target,prop,reciever){ if(prop in target){ return target[prop] } return 'Default value' } } let p = new Proxy(target,handler) console.log(p.job)
Extending Constructor
در این مثال ما constructor را در یک function object در تله می اندازیم . روال کار در این مثال به این شکل است که یک تابع به نام extendingConstructor داریم که این تابع پارامتر اول آن یک تابع یا function object است و پارامتر دوم تابعی دیگر که قرار است constructor این دو تابع را به هم متصل کنیم.
function extendingContructor(parent,base){ let exn = new Function exn.prototype.constructor = new Proxy(base,{ construct(target,args,newTarget){ let object = new Object this.apply(target,object,args) return object }, apply(target,object,args){ parent.apply(object,args) base.apply(object,args) } }) return exn.prototype.constructor } function Parent (name){ this.name = name this.getName = function(){ return this.name } } function Children(name,age){ this.age = age, this.getAge = function(){ return this.age } } let Person = extendingContructor(Parent,Children) let ali = new Person('ali',29) console.log(ali.getAge())
بدنه این تابع به این شکل است که دو تابع (function object or object constructor) تعریف می کنیم و در نهایت یک تابع هم بر میگردانیم که constructor آن رفتار هر دو تابع را دارد . پیش از بررسی بدنه این تابع باید Fact های زیر را بدانیم:
- یک تابع یک شئ است که قابلیت constructor دارد. یعنی در زمان ساختن می توان Property های آن را مقدار دهی کنید.
- در یک تابع یک شئ جریان دارد . این شئ در درون تابع پس از ساخته شدن this می باشد.
- در زمان new شدن یک تابع constructor آن فراخوانی می شود
- constructor این تابع یک شئ برمی گرداند.
- Proxy handler constructor روی constructor این تابع که یکی از توابع ورودی است دسترسی دارد.
- هر تابع یک ویژگی به نام prototype دارد که متدهایی از جمله constructor از آن ارث بری می شود.
- با استفاده از apply می توانیم یک شئ را روی یک تابع اجرا کنیم .
در این تابع ما دو تابع و آرگومان های آن ها را داریم . توابع که با نام های super , base تعریف شده اند که مقدار پارامتر های این تابع هستند. پارامتر های این تابع را با فیلتر گذاری روی constructor می توانیم به دست بیاوریم . در نهایت با استفاده از apply یک شئ را می سازیم و هر دو متد super , base را با استفاده از آرگومان های آن اجرا می کنیم و در نهایت همان شئ را بر می گردانیم.
Mount/Day Validation
در این مثال ما مقادیر ماه و روز اگر به اشتباه وارد شود خطا Range اجرا می کنیم. مقادیر یک ماه باید از ۱۲ کوچکتر و مقادیر روز باید از ۳۱ کوچکتر باشند.
let handler = { set(target,prop,value,proxy){ if('mount' == prop && value > 12){ throw new RangeError('invalid value for Mount') } else if('day' == prop && value > 31){ throw new RangeError('invalid value for Day') } target[prop] = value } } let target = { year: 0, mount: 0, day: 0, } let p = new Proxy(target,handler) p.mount=20 p.day = 33 p.year = 2022 console.log(p)
Operation Forward
اگر در یک Proxy پس از initialize یک مقدار set کنیم در target آن نیز این operator وجود دارد (operation forwarding)
let target = {} let handler = {} let p = new Proxy(target,handler) p.a = 'hello world' console.log(target,p)
Proxy Access Private property
در Proxy ما به مقادیر Private چه Method/Property دسترسی نداریم.
class Template{ #secret } let target = new Template let handler = {} let p = new Proxy(target,handler) console.log(p.#secret) /* Private field '#secret' must be declared in an enclosing class */
نکته : در Proxy در برخی اشیا به طور مثال map هم به ویژگی های آن دسترسی نداریم (Proxy internal slot)
let target = new Map let handler = target let p = new Proxy(target,handler) console.log(target.size) // 0 console.log(p.size) //undefined
Proxy Manipulating Dom
در proxy ما می توانیم روی عملیات های خاص یک شئ فیلتر هایی بگذاریم که قرار است یک سری عملیات روی یک dom اعمال کند. به طول مثال ما می خواهیم اگر یک کلاس روی یک node قرار گرفت به صورت toggle یک کلاس روی node قبلی تغییر کند.
let target = { current: false } let handler = { set(target,prop,newValue){ let oldElement = target[prop] if(oldElement){ oldElement.classList.add('disable') oldElement.classList.remove('selected') } newValue.classList.add('selected') newValue.classList.remove('disable') target[prop] = newValue return true } } let toggler = new Proxy(target,handler) function clickElement(event){ let element = event.target toggler.current = element }
Value Correction Extra Value
در این مثال ما یک لیست عدد داریم و اگر مقدار اشتباه برای آن set کنیم تبدیل به NaN می شود . همچنین یک سری متد که در شئ وجود ندارد با استفاده از Proxy Handler Method مدیریت می کنیم.مثلا اگر آخرین عدد از این لیست را خواستیم این متد که در آن نوشته شده است را می توان با استفاده از مقدار NumberList و طول آن برگرداند در صورتی که که در target ویژگی با نام latest وجود ندارد.
let target = { numberList: [] } let handler = { set(target,prop,value){ if('numberList' != prop){ return undefined } value = parseInt(value) target.numberList.push(value) }, get(target,prop,proxy){ if('latest' == prop){ return target['numberList'][target.numberList.length - 1] } return undefined } } let proxyNumberList = new Proxy(target,handler) proxyNumberList.numberList = '3' proxyNumberList.numberList = 'ali' proxyNumberList.numberList = '54' console.log(proxyNumberList) console.log(proxyNumberList.latest)
Finding Array Item Object
با استفاده از یک Proxy که روی get Proxy handler method اعمال شده است حالت های زیر را روی یک collection پیاده سازی می کنیم.
- اگر عدد index از این آرایه را برگرداند ما مقدار متناظر آن را بر می گردانیم .
- اگر مقدار number را درخواست کرد تعداد کل این collection را بر می گردانیم.
- اگر نام – name – یکی از این آیتم های collection را درخواست کرد آن آیتم از collection را بر می گردانیم.
- اگر نوع – type – را درخواست داد ما آیتم هایی از این نوع را در collection موجود است را در قالب یک آرایه برمی گردانیم.
- اگر types را درخواست داد لیست انواع آیتم های این collection را در قالب یک آرایه بر می گردانیم.
let target = [ {name: 'firefox',type: 'browser'}, {name: 'seaMonkey', type: 'browser'}, {name: 'thunderbird', type: 'mailer'} ] let handler = { get(target,prop,proxy){ if(target[prop]){ return target[prop] } if('number' == prop){ return target.length } let result = new Array let types = new Array for(const product of target){ if(product.name == prop || product.type == prop){ result.push(product) } if('types' == prop){ if(!types.includes(product.type)){ types.push(product.type) result = types } } } if(result.length > 0){ return result } return undefined } } let p = new Proxy(target,handler) console.log('Finding By index------------------------') console.log(p[1]) console.log('Retrive Product Count-------------------') console.log(p.number) console.log('Find By Product Name--------------------') console.log(p.seaMonkey) console.log('Find By Product Type--------------------') console.log(p.browser) console.log(p.mailer) console.log('Find Types------------------------------') console.log(p.types) console.log('Not Found!------------------------------') console.log(p.chrome)
دیدگاهتان را بنویسید