বিশ্বব্যাপী ডেভেলপারদের জন্য জাভাস্ক্রিপ্ট প্রক্সি API আয়ত্ত করার একটি সম্পূর্ণ নির্দেশিকা। ব্যবহারিক উদাহরণ, ব্যবহারের ক্ষেত্র এবং পারফরম্যান্স টিপস সহ অবজেক্ট অপারেশন ইন্টারসেপ্ট ও কাস্টমাইজ করতে শিখুন।
জাভাস্ক্রিপ্ট প্রক্সি API: অবজেক্টের আচরণ পরিবর্তনের একটি গভীর বিশ্লেষণ
আধুনিক জাভাস্ক্রিপ্টের ক্রমবর্ধমান জগতে, ডেভেলপাররা ডেটা পরিচালনা এবং তার সাথে ইন্টারঅ্যাক্ট করার জন্য আরও শক্তিশালী এবং সুন্দর উপায় খুঁজছে। যদিও ক্লাস, মডিউল এবং async/await এর মতো ফিচারগুলো আমাদের কোড লেখার পদ্ধতিতে বিপ্লব এনেছে, ECMAScript 2015 (ES6)-এ একটি শক্তিশালী মেটাপ্রোগ্রামিং ফিচার যুক্ত করা হয়েছে যা প্রায়শই কম ব্যবহৃত হয়: প্রক্সি API।
মেটাপ্রোগ্রামিং শব্দটি ভীতিজনক মনে হতে পারে, কিন্তু এটি আসলে এমন কোড লেখার ধারণা যা অন্য কোডের উপর কাজ করে। প্রক্সি API হল জাভাস্ক্রিপ্টের এর জন্য প্রধান টুল, যা আপনাকে অন্য একটি অবজেক্টের জন্য একটি 'প্রক্সি' তৈরি করতে দেয়, যা সেই অবজেক্টের জন্য মৌলিক অপারেশনগুলোকে ইন্টারসেপ্ট এবং পুনরায় সংজ্ঞায়িত করতে পারে। এটি অনেকটা একটি অবজেক্টের সামনে একজন কাস্টমাইজযোগ্য দারোয়ান রাখার মতো, যা আপনাকে এটি কীভাবে অ্যাক্সেস এবং পরিবর্তন করা হবে তার উপর সম্পূর্ণ নিয়ন্ত্রণ দেয়।
এই বিস্তারিত নির্দেশিকাটি প্রক্সি API-কে সহজবোধ্য করে তুলবে। আমরা এর মূল ধারণাগুলো অন্বেষণ করব, ব্যবহারিক উদাহরণ সহ এর বিভিন্ন ক্ষমতা বিশ্লেষণ করব এবং উন্নত ব্যবহারের ক্ষেত্র ও পারফরম্যান্সের বিষয়গুলো আলোচনা করব। এটি পড়ার শেষে, আপনি বুঝতে পারবেন কেন প্রক্সি আধুনিক ফ্রেমওয়ার্কগুলোর একটি ভিত্তি এবং কীভাবে আপনি এগুলো ব্যবহার করে আরও পরিষ্কার, শক্তিশালী এবং রক্ষণাবেক্ষণযোগ্য কোড লিখতে পারেন।
মূল ধারণাগুলো বোঝা: টার্গেট, হ্যান্ডলার এবং ট্র্যাপস
প্রক্সি API তিনটি মৌলিক উপাদানের উপর নির্মিত। প্রক্সি আয়ত্ত করার জন্য এদের ভূমিকা বোঝা অত্যন্ত গুরুত্বপূর্ণ।
- টার্গেট (Target): এটি হল মূল অবজেক্ট যা আপনি র্যাপ করতে চান। এটি যেকোনো ধরনের অবজেক্ট হতে পারে, যেমন অ্যারে, ফাংশন বা এমনকি অন্য একটি প্রক্সি। প্রক্সি এই টার্গেটকে ভার্চুয়ালাইজ করে এবং সমস্ত অপারেশন শেষ পর্যন্ত (যদিও অপরিহার্য নয়) এটিতে ফরোয়ার্ড করা হয়।
- হ্যান্ডলার (Handler): এটি একটি অবজেক্ট যা প্রক্সির জন্য যুক্তি ধারণ করে। এটি একটি প্লেসহোল্ডার অবজেক্ট যার প্রোপার্টিগুলো হল ফাংশন, যা 'ট্র্যাপ' নামে পরিচিত। যখন প্রক্সিতে কোনো অপারেশন ঘটে, তখন এটি হ্যান্ডলারে সংশ্লিষ্ট ট্র্যাপটি খোঁজে।
- ট্র্যাপস (Traps): এগুলি হ্যান্ডলারের মেথড যা প্রোপার্টি অ্যাক্সেস প্রদান করে। প্রতিটি ট্র্যাপ একটি মৌলিক অবজেক্ট অপারেশনের সাথে সম্পর্কিত। উদাহরণস্বরূপ,
get
ট্র্যাপ প্রোপার্টি রিডিং ইন্টারসেপ্ট করে এবংset
ট্র্যাপ প্রোপার্টি রাইটিং ইন্টারসেপ্ট করে। যদি হ্যান্ডলারে কোনো ট্র্যাপ সংজ্ঞায়িত না থাকে, তাহলে অপারেশনটি সরাসরি টার্গেটে ফরোয়ার্ড করা হয়, যেন প্রক্সিটি সেখানে ছিলই না।
একটি প্রক্সি তৈরি করার সিনট্যাক্সটি সহজ:
const proxy = new Proxy(target, handler);
আসুন একটি খুব সাধারণ উদাহরণ দেখি। আমরা একটি খালি হ্যান্ডলার ব্যবহার করে একটি প্রক্সি তৈরি করব যা সমস্ত অপারেশন টার্গেট অবজেক্টে পাস করে দেবে।
// মূল অবজেক্ট
const target = {
message: "হ্যালো, ওয়ার্ল্ড!"
};
// একটি খালি হ্যান্ডলার। সমস্ত অপারেশন টার্গেটে ফরোয়ার্ড করা হবে।
const handler = {};
// প্রক্সি অবজেক্ট
const proxy = new Proxy(target, handler);
// প্রক্সিতে একটি প্রোপার্টি অ্যাক্সেস করা হচ্ছে
console.log(proxy.message); // আউটপুট: হ্যালো, ওয়ার্ল্ড!
// অপারেশনটি টার্গেটে ফরোয়ার্ড করা হয়েছে
console.log(target.message); // আউটপুট: হ্যালো, ওয়ার্ল্ড!
// প্রক্সির মাধ্যমে একটি প্রোপার্টি পরিবর্তন করা হচ্ছে
proxy.anotherMessage = "হ্যালো, প্রক্সি!";
console.log(proxy.anotherMessage); // আউটপুট: হ্যালো, প্রক্সি!
console.log(target.anotherMessage); // আউটপুট: হ্যালো, প্রক্সি!
এই উদাহরণে, প্রক্সিটি ঠিক মূল অবজেক্টের মতোই আচরণ করে। আসল শক্তি তখনই আসে যখন আমরা হ্যান্ডলারে ট্র্যাপ সংজ্ঞায়িত করা শুরু করি।
প্রক্সির অ্যানাটমি: সাধারণ ট্র্যাপগুলো অন্বেষণ
হ্যান্ডলার অবজেক্টে ১৩টি পর্যন্ত বিভিন্ন ট্র্যাপ থাকতে পারে, যার প্রতিটি জাভাস্ক্রিপ্ট অবজেক্টের একটি মৌলিক অভ্যন্তরীণ মেথডের সাথে সম্পর্কিত। চলুন সবচেয়ে সাধারণ এবং দরকারী ট্র্যাপগুলো অন্বেষণ করি।
প্রোপার্টি অ্যাক্সেস ট্র্যাপস
1. `get(target, property, receiver)`
এটি সম্ভবত সবচেয়ে বেশি ব্যবহৃত ট্র্যাপ। এটি তখন ট্রিগার হয় যখন প্রক্সির কোনো প্রোপার্টি পড়া হয়।
target
: মূল অবজেক্ট।property
: যে প্রোপার্টিটি অ্যাক্সেস করা হচ্ছে তার নাম।receiver
: প্রক্সি নিজে, বা এমন একটি অবজেক্ট যা এটি থেকে উত্তরাধিকার সূত্রে প্রাপ্ত।
উদাহরণ: অস্তিত্বহীন প্রোপার্টির জন্য ডিফল্ট মান।
const user = {
firstName: 'John',
lastName: 'Doe',
age: 30
};
const userHandler = {
get(target, property) {
// যদি টার্গেটে প্রোপার্টিটি বিদ্যমান থাকে, তবে সেটি রিটার্ন করুন।
// অন্যথায়, একটি ডিফল্ট বার্তা রিটার্ন করুন।
return property in target ? target[property] : `প্রোপার্টি '${property}' এর অস্তিত্ব নেই।`;
}
};
const userProxy = new Proxy(user, userHandler);
console.log(userProxy.firstName); // আউটপুট: John
console.log(userProxy.age); // আউটপুট: 30
console.log(userProxy.country); // আউটপুট: প্রোপার্টি 'country' এর অস্তিত্ব নেই।
2. `set(target, property, value, receiver)`
set
ট্র্যাপটি তখন কল করা হয় যখন প্রক্সির কোনো প্রোপার্টিতে একটি মান নির্ধারণ করা হয়। এটি ভ্যালিডেশন, লগিং বা শুধুমাত্র-পঠনযোগ্য (read-only) অবজেক্ট তৈরির জন্য উপযুক্ত।
value
: প্রোপার্টিতে নির্ধারিত নতুন মান।- ট্র্যাপটিকে অবশ্যই একটি বুলিয়ান রিটার্ন করতে হবে: অ্যাসাইনমেন্ট সফল হলে
true
, এবং অন্যথায়false
(যা স্ট্রিক্ট মোডে একটিTypeError
থ্রো করবে)।
উদাহরণ: ডেটা ভ্যালিডেশন।
const person = {
name: 'Jane Doe',
age: 25
};
const validationHandler = {
set(target, property, value) {
if (property === 'age') {
if (typeof value !== 'number' || !Number.isInteger(value)) {
throw new TypeError('বয়স অবশ্যই একটি পূর্ণসংখ্যা হতে হবে।');
}
if (value <= 0) {
throw new RangeError('বয়স অবশ্যই একটি ধনাত্মক সংখ্যা হতে হবে।');
}
}
// যদি ভ্যালিডেশন পাস হয়, টার্গেট অবজেক্টে মানটি সেট করুন।
target[property] = value;
// সফল হয়েছে তা নির্দেশ করুন।
return true;
}
};
const personProxy = new Proxy(person, validationHandler);
personProxy.age = 30; // এটি বৈধ
console.log(personProxy.age); // আউটপুট: 30
try {
personProxy.age = 'thirty'; // TypeError থ্রো করবে
} catch (e) {
console.error(e.message); // আউটপুট: বয়স অবশ্যই একটি পূর্ণসংখ্যা হতে হবে।
}
try {
personProxy.age = -5; // RangeError থ্রো করবে
} catch (e) {
console.error(e.message); // আউটপুট: বয়স অবশ্যই একটি ধনাত্মক সংখ্যা হতে হবে।
}
3. `has(target, property)`
এই ট্র্যাপটি in
অপারেটরকে ইন্টারসেপ্ট করে। এটি আপনাকে নিয়ন্ত্রণ করতে দেয় যে কোন প্রোপার্টিগুলো একটি অবজেক্টে বিদ্যমান বলে মনে হবে।
উদাহরণ: 'প্রাইভেট' প্রোপার্টি লুকানো।
জাভাস্ক্রিপ্টে, প্রাইভেট প্রোপার্টির আগে একটি আন্ডারস্কোর (_) ব্যবহার করা একটি সাধারণ নিয়ম। আমরা has
ট্র্যাপ ব্যবহার করে in
অপারেটর থেকে এগুলো লুকাতে পারি।
const secretData = {
_apiKey: 'xyz123abc',
publicKey: 'pub456def',
id: 1
};
const hidingHandler = {
has(target, property) {
if (property.startsWith('_')) {
return false; // ভান করুন যে এটির অস্তিত্ব নেই
}
return property in target;
}
};
const dataProxy = new Proxy(secretData, hidingHandler);
console.log('publicKey' in dataProxy); // আউটপুট: true
console.log('_apiKey' in dataProxy); // আউটপুট: false (যদিও এটি টার্গেটে আছে)
console.log('id' in dataProxy); // আউটপুট: true
দ্রষ্টব্য: এটি শুধুমাত্র in
অপারেটরকে প্রভাবিত করে। dataProxy._apiKey
এর মতো সরাসরি অ্যাক্সেস এখনও কাজ করবে যদি না আপনি একটি সংশ্লিষ্ট get
ট্র্যাপও প্রয়োগ করেন।
4. `deleteProperty(target, property)`
এই ট্র্যাপটি তখন কার্যকর হয় যখন delete
অপারেটর ব্যবহার করে একটি প্রোপার্টি মুছে ফেলা হয়। এটি গুরুত্বপূর্ণ প্রোপার্টি মুছে ফেলা প্রতিরোধ করার জন্য দরকারী।
ট্র্যাপটিকে সফলভাবে ডিলিট করার জন্য true
অথবা ব্যর্থতার জন্য false
রিটার্ন করতে হবে।
উদাহরণ: প্রোপার্টি ডিলিট করা প্রতিরোধ করা।
const immutableConfig = {
databaseUrl: 'prod.db.server',
port: 8080
};
const deletionGuardHandler = {
deleteProperty(target, property) {
if (property in target) {
console.warn(`সুরক্ষিত প্রোপার্টি ডিলিট করার চেষ্টা করা হয়েছে: '${property}'। অপারেশন বাতিল করা হয়েছে।`);
return false;
}
return true; // প্রোপার্টিটি এমনিতেও ছিল না
}
};
const configProxy = new Proxy(immutableConfig, deletionGuardHandler);
delete configProxy.port;
// কনসোল আউটপুট: সুরক্ষিত প্রোপার্টি ডিলিট করার চেষ্টা করা হয়েছে: 'port'। অপারেশন বাতিল করা হয়েছে।
console.log(configProxy.port); // আউটপুট: 8080 (এটি ডিলিট করা হয়নি)
অবজেক্ট এনুমারেশন এবং ডেসক্রিপশন ট্র্যাপস
5. `ownKeys(target)`
এই ট্র্যাপটি সেইসব অপারেশনের দ্বারা ট্রিগার হয় যা একটি অবজেক্টের নিজস্ব প্রোপার্টির তালিকা পায়, যেমন Object.keys()
, Object.getOwnPropertyNames()
, Object.getOwnPropertySymbols()
, এবং Reflect.ownKeys()
।
উদাহরণ: কী ফিল্টার করা।
আসুন এটিকে আমাদের পূর্ববর্তী 'প্রাইভেট' প্রোপার্টির উদাহরণের সাথে একত্রিত করে সেগুলিকে সম্পূর্ণরূপে লুকিয়ে ফেলি।
const secretData = {
_apiKey: 'xyz123abc',
publicKey: 'pub456def',
id: 1
};
const keyHidingHandler = {
has(target, property) {
return !property.startsWith('_') && property in target;
},
ownKeys(target) {
return Reflect.ownKeys(target).filter(key => !key.startsWith('_'));
},
get(target, property, receiver) {
// সরাসরি অ্যাক্সেসও প্রতিরোধ করুন
if (property.startsWith('_')) {
return undefined;
}
return Reflect.get(target, property, receiver);
}
};
const fullProxy = new Proxy(secretData, keyHidingHandler);
console.log(Object.keys(fullProxy)); // আউটপুট: ['publicKey', 'id']
console.log('publicKey' in fullProxy); // আউটপুট: true
console.log('_apiKey' in fullProxy); // আউটপুট: false
console.log(fullProxy._apiKey); // আউটপুট: undefined
লক্ষ্য করুন আমরা এখানে Reflect
ব্যবহার করছি। Reflect
অবজেক্টটি ইন্টারসেপ্টযোগ্য জাভাস্ক্রিপ্ট অপারেশনের জন্য মেথড সরবরাহ করে এবং এর মেথডগুলোর নাম এবং সিগনেচার প্রক্সি ট্র্যাপের মতোই। ডিফল্ট আচরণ সঠিকভাবে বজায় রাখা নিশ্চিত করার জন্য মূল অপারেশনটিকে টার্গেটে ফরোয়ার্ড করতে Reflect
ব্যবহার করা একটি সেরা অনুশীলন।
ফাংশন এবং কনস্ট্রাক্টর ট্র্যাপস
প্রক্সি শুধুমাত্র প্লেইন অবজেক্টের মধ্যে সীমাবদ্ধ নয়। যখন টার্গেটটি একটি ফাংশন হয়, আপনি কল এবং কনস্ট্রাকশন ইন্টারসেপ্ট করতে পারেন।
6. `apply(target, thisArg, argumentsList)`
এই ট্র্যাপটি তখন কল করা হয় যখন একটি ফাংশনের প্রক্সি চালানো হয়। এটি ফাংশন কলটিকে ইন্টারসেপ্ট করে।
target
: মূল ফাংশন।thisArg
: কলের জন্যthis
কনটেক্সট।argumentsList
: ফাংশনে পাস করা আর্গুমেন্টের তালিকা।
উদাহরণ: ফাংশন কল এবং তাদের আর্গুমেন্ট লগ করা।
function sum(a, b) {
return a + b;
}
const loggingHandler = {
apply(target, thisArg, argumentsList) {
console.log(`ফাংশন '${target.name}' কল করা হচ্ছে আর্গুমেন্ট সহ: ${argumentsList}`);
// সঠিক কনটেক্সট এবং আর্গুমেন্টসহ মূল ফাংশনটি চালান
const result = Reflect.apply(target, thisArg, argumentsList);
console.log(`ফাংশন '${target.name}' রিটার্ন করেছে: ${result}`);
return result;
}
};
const proxiedSum = new Proxy(sum, loggingHandler);
proxiedSum(5, 10);
// কনসোল আউটপুট:
// ফাংশন 'sum' কল করা হচ্ছে আর্গুমেন্ট সহ: 5,10
// ফাংশন 'sum' রিটার্ন করেছে: 15
7. `construct(target, argumentsList, newTarget)`
এই ট্র্যাপটি কোনো ক্লাস বা ফাংশনের প্রক্সিতে new
অপারেটরের ব্যবহার ইন্টারসেপ্ট করে।
উদাহরণ: সিঙ্গেলটন প্যাটার্ন বাস্তবায়ন।
class MyDatabaseConnection {
constructor(url) {
this.url = url;
console.log(`${this.url}-এ সংযোগ করা হচ্ছে...`);
}
}
let instance;
const singletonHandler = {
construct(target, argumentsList) {
if (!instance) {
console.log('নতুন ইনস্ট্যান্স তৈরি করা হচ্ছে।');
instance = Reflect.construct(target, argumentsList);
}
console.log('বিদ্যমান ইনস্ট্যান্স রিটার্ন করা হচ্ছে।');
return instance;
}
};
const ProxiedConnection = new Proxy(MyDatabaseConnection, singletonHandler);
const conn1 = new ProxiedConnection('db://primary');
// কনসোল আউটপুট:
// নতুন ইনস্ট্যান্স তৈরি করা হচ্ছে।
// db://primary-এ সংযোগ করা হচ্ছে...
// বিদ্যমান ইনস্ট্যান্স রিটার্ন করা হচ্ছে।
const conn2 = new ProxiedConnection('db://secondary'); // URL উপেক্ষা করা হবে
// কনসোল আউটপুট:
// বিদ্যমান ইনস্ট্যান্স রিটার্ন করা হচ্ছে।
console.log(conn1 === conn2); // আউটপুট: true
console.log(conn1.url); // আউটপুট: db://primary
console.log(conn2.url); // আউটপুট: db://primary
ব্যবহারিক প্রয়োগ এবং উন্নত প্যাটার্নস
এখন যেহেতু আমরা স্বতন্ত্র ট্র্যাপগুলো কভার করেছি, চলুন দেখি বাস্তব জগতের সমস্যা সমাধানে এগুলিকে কীভাবে একত্রিত করা যায়।
১. API অ্যাবস্ট্র্যাকশন এবং ডেটা ট্রান্সফর্মেশন
API প্রায়শই এমন একটি ফরম্যাটে ডেটা রিটার্ন করে যা আপনার অ্যাপ্লিকেশনের নিয়মের সাথে মেলে না (যেমন, snake_case
বনাম camelCase
)। একটি প্রক্সি স্বচ্ছভাবে এই রূপান্তরটি পরিচালনা করতে পারে।
function snakeToCamel(s) {
return s.replace(/(_\w)/g, (m) => m[1].toUpperCase());
}
// ধরুন এটি একটি API থেকে আমাদের কাঁচা ডেটা
const apiResponse = {
user_id: 123,
first_name: 'Alice',
last_name: 'Wonderland',
account_status: 'active'
};
const camelCaseHandler = {
get(target, property) {
const camelCaseProperty = snakeToCamel(property);
// camelCase সংস্করণটি সরাসরি বিদ্যমান কিনা তা পরীক্ষা করুন
if (camelCaseProperty in target) {
return target[camelCaseProperty];
}
// মূল প্রোপার্টির নামে ফিরে যান
if (property in target) {
return target[property];
}
return undefined;
}
};
const userModel = new Proxy(apiResponse, camelCaseHandler);
// আমরা এখন camelCase ব্যবহার করে প্রোপার্টি অ্যাক্সেস করতে পারি, যদিও সেগুলি snake_case হিসাবে সংরক্ষণ করা আছে
console.log(userModel.userId); // আউটপুট: 123
console.log(userModel.firstName); // আউটপুট: Alice
console.log(userModel.accountStatus); // আউটপুট: active
২. অবজারভেবল এবং ডেটা বাইন্ডিং (আধুনিক ফ্রেমওয়ার্কের মূল ভিত্তি)
প্রক্সি হল Vue 3-এর মতো আধুনিক ফ্রেমওয়ার্কগুলির রিঅ্যাক্টিভিটি সিস্টেমের পেছনের ইঞ্জিন। যখন আপনি একটি প্রক্সিড স্টেট অবজেক্টের একটি প্রোপার্টি পরিবর্তন করেন, তখন set
ট্র্যাপটি UI বা অ্যাপ্লিকেশনের অন্যান্য অংশে আপডেট ট্রিগার করতে ব্যবহৃত হতে পারে।
এখানে একটি অত্যন্ত সরলীকৃত উদাহরণ দেওয়া হল:
function createObservable(target, callback) {
const handler = {
set(obj, prop, value) {
const result = Reflect.set(obj, prop, value);
callback(prop, value); // পরিবর্তনের উপর কলব্যাকটি ট্রিগার করুন
return result;
}
};
return new Proxy(target, handler);
}
const state = {
count: 0,
message: 'Hello'
};
function render(prop, value) {
console.log(`পরিবর্তন শনাক্ত হয়েছে: প্রোপার্টি '${prop}' এর মান '${value}' সেট করা হয়েছে। UI পুনরায় রেন্ডার করা হচ্ছে...`);
}
const observableState = createObservable(state, render);
observableState.count = 1;
// কনসোল আউটপুট: পরিবর্তন শনাক্ত হয়েছে: প্রোপার্টি 'count' এর মান '1' সেট করা হয়েছে। UI পুনরায় রেন্ডার করা হচ্ছে...
observableState.message = 'Goodbye';
// কনসোল আউটপুট: পরিবর্তন শনাক্ত হয়েছে: প্রোপার্টি 'message' এর মান 'Goodbye' সেট করা হয়েছে। UI পুনরায় রেন্ডার করা হচ্ছে...
৩. নেগেটিভ অ্যারে ইনডেক্স
একটি ক্লাসিক এবং মজার উদাহরণ হল নেটিভ অ্যারের আচরণকে প্রসারিত করে নেগেটিভ ইনডেক্স সমর্থন করা, যেখানে -1
শেষ উপাদানটিকে বোঝায়, পাইথনের মতো ভাষার মতো।
function createNegativeArrayProxy(arr) {
const handler = {
get(target, property) {
const index = Number(property);
if (!Number.isNaN(index) && index < 0) {
// ঋণাত্মক ইন্ডেক্সকে শেষ থেকে একটি ধনাত্মক ইন্ডেক্সে রূপান্তর করুন
property = String(target.length + index);
}
return Reflect.get(target, property);
}
};
return new Proxy(arr, handler);
}
const originalArray = ['a', 'b', 'c', 'd', 'e'];
const proxiedArray = createNegativeArrayProxy(originalArray);
console.log(proxiedArray[0]); // আউটপুট: a
console.log(proxiedArray[-1]); // আউটপুট: e
console.log(proxiedArray[-2]); // আউটপুট: d
console.log(proxiedArray.length); // আউটপুট: 5
পারফরম্যান্স বিবেচনা এবং সেরা অনুশীলন
যদিও প্রক্সিগুলি অবিশ্বাস্যভাবে শক্তিশালী, তবে সেগুলি কোনো জাদুকরী সমাধান নয়। তাদের প্রভাব বোঝা অত্যন্ত গুরুত্বপূর্ণ।
পারফরম্যান্স ওভারহেড
একটি প্রক্সি একটি পরোক্ষ স্তর তৈরি করে। একটি প্রক্সিড অবজেক্টের প্রতিটি অপারেশনকে হ্যান্ডলারের মধ্য দিয়ে যেতে হয়, যা একটি সাধারণ অবজেক্টের উপর সরাসরি অপারেশনের তুলনায় সামান্য ওভারহেড যোগ করে। বেশিরভাগ অ্যাপ্লিকেশনের জন্য (যেমন ডেটা ভ্যালিডেশন বা ফ্রেমওয়ার্ক-স্তরের রিঅ্যাক্টিভিটি), এই ওভারহেড নগণ্য। তবে, পারফরম্যান্স-ক্রিটিক্যাল কোডে, যেমন লক্ষ লক্ষ আইটেম প্রক্রিয়াকরণকারী একটি টাইট লুপে, এটি একটি বাধা হয়ে দাঁড়াতে পারে। পারফরম্যান্স যদি একটি প্রাথমিক উদ্বেগ হয়, তবে সর্বদা বেঞ্চমার্ক করুন।
প্রক্সি ইনভ্যারিয়েন্টস (Proxy Invariants)
একটি ট্র্যাপ টার্গেট অবজেক্টের প্রকৃতি সম্পর্কে পুরোপুরি মিথ্যা বলতে পারে না। জাভাস্ক্রিপ্ট 'ইনভ্যারিয়েন্টস' নামক কিছু নিয়ম প্রয়োগ করে যা প্রক্সি ট্র্যাপগুলিকে অবশ্যই মেনে চলতে হবে। একটি ইনভ্যারিয়েন্ট লঙ্ঘন করলে একটি TypeError
হবে।
উদাহরণস্বরূপ, deleteProperty
ট্র্যাপের জন্য একটি ইনভ্যারিয়েন্ট হল যে টার্গেট অবজেক্টের সংশ্লিষ্ট প্রোপার্টিটি যদি নন-কনফিগারেবল হয়, তবে এটি true
(সফলতা নির্দেশ করে) রিটার্ন করতে পারে না। এটি প্রক্সিকে এমন একটি প্রোপার্টি মুছে ফেলার দাবি করা থেকে বিরত রাখে যা মুছে ফেলা যায় না।
const target = {};
Object.defineProperty(target, 'unbreakable', { value: 10, configurable: false });
const handler = {
deleteProperty(target, prop) {
// এটি ইনভ্যারিয়েন্ট লঙ্ঘন করবে
return true;
}
};
const proxy = new Proxy(target, handler);
try {
delete proxy.unbreakable; // এটি একটি এরর থ্রো করবে
} catch (e) {
console.error(e.message);
// আউটপুট: প্রক্সিতে 'deleteProperty': কনফিগার করা যায় না এমন প্রোপার্টি 'unbreakable'-এর জন্য true রিটার্ন করেছে
}
কখন প্রক্সি ব্যবহার করবেন (এবং কখন করবেন না)
- এর জন্য ভালো: ফ্রেমওয়ার্ক এবং লাইব্রেরি তৈরি করা (যেমন, স্টেট ম্যানেজমেন্ট, ORMs), ডিবাগিং এবং লগিং, শক্তিশালী ভ্যালিডেশন সিস্টেম বাস্তবায়ন করা এবং অন্তর্নিহিত ডেটা স্ট্রাকচারকে অ্যাবস্ট্রাক্ট করে এমন শক্তিশালী API তৈরি করা।
- বিকল্প বিবেচনা করুন: পারফরম্যান্স-ক্রিটিক্যাল অ্যালগরিদম, সাধারণ অবজেক্ট এক্সটেনশন যেখানে একটি ক্লাস বা ফ্যাক্টরি ফাংশনই যথেষ্ট, অথবা যখন আপনার খুব পুরানো ব্রাউজার সমর্থন করতে হবে যেখানে ES6 সমর্থন নেই।
প্রত্যাহারযোগ্য প্রক্সি (Revocable Proxies)
যেসব ক্ষেত্রে আপনার একটি প্রক্সি 'বন্ধ' করার প্রয়োজন হতে পারে (যেমন, নিরাপত্তার কারণে বা মেমরি ম্যানেজমেন্টের জন্য), জাভাস্ক্রিপ্ট Proxy.revocable()
প্রদান করে। এটি একটি অবজেক্ট রিটার্ন করে যাতে প্রক্সি এবং একটি revoke
ফাংশন উভয়ই থাকে।
const target = { data: 'sensitive' };
const handler = {};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.data); // আউটপুট: sensitive
// এখন, আমরা প্রক্সির অ্যাক্সেস প্রত্যাহার করছি
revoke();
try {
console.log(proxy.data); // এটি একটি এরর থ্রো করবে
} catch (e) {
console.error(e.message);
// আউটপুট: প্রত্যাহার করা হয়েছে এমন প্রক্সিতে 'get' অপারেশন করা যাবে না
}
প্রক্সি বনাম অন্যান্য মেটাপ্রোগ্রামিং কৌশল
প্রক্সির আগে, ডেভেলপাররা একই ধরনের লক্ষ্য অর্জনের জন্য অন্যান্য পদ্ধতি ব্যবহার করত। প্রক্সির সাথে তাদের তুলনা বোঝা দরকারী।
`Object.defineProperty()`
Object.defineProperty()
নির্দিষ্ট প্রোপার্টির জন্য গেটার এবং সেটার নির্ধারণ করে একটি অবজেক্টকে সরাসরি পরিবর্তন করে। অন্যদিকে, প্রক্সি মূল অবজেক্টকে মোটেও পরিবর্তন করে না; তারা এটিকে র্যাপ করে।
- স্কোপ: `defineProperty` প্রতি-প্রোপার্টি ভিত্তিতে কাজ করে। আপনি যে প্রতিটি প্রোপার্টি দেখতে চান তার জন্য আপনাকে একটি গেটার/সেটার নির্ধারণ করতে হবে। একটি প্রক্সির
get
এবংset
ট্র্যাপগুলি গ্লোবাল, যা যেকোনো প্রোপার্টির উপর অপারেশন ধরে, এমনকি পরে যোগ করা নতুন প্রোপার্টিও। - ক্ষমতা: প্রক্সিগুলি আরও বিস্তৃত অপারেশন ইন্টারসেপ্ট করতে পারে, যেমন
deleteProperty
,in
অপারেটর এবং ফাংশন কল, যা `defineProperty` করতে পারে না।
উপসংহার: ভার্চুয়ালাইজেশনের শক্তি
জাভাস্ক্রিপ্ট প্রক্সি API শুধুমাত্র একটি চতুর ফিচার নয়; এটি আমরা কীভাবে অবজেক্ট ডিজাইন এবং তার সাথে ইন্টারঅ্যাক্ট করি তার একটি মৌলিক পরিবর্তন। মৌলিক অপারেশনগুলিকে ইন্টারসেপ্ট এবং কাস্টমাইজ করার অনুমতি দিয়ে, প্রক্সিগুলি শক্তিশালী প্যাটার্নের একটি জগতের দরজা খুলে দেয়: নির্বিঘ্ন ডেটা ভ্যালিডেশন এবং রূপান্তর থেকে শুরু করে আধুনিক ইউজার ইন্টারফেসকে শক্তি যোগানো রিঅ্যাক্টিভ সিস্টেম পর্যন্ত।
যদিও এগুলির সাথে সামান্য পারফরম্যান্স খরচ এবং কিছু নিয়ম মেনে চলার বিষয় রয়েছে, তাদের পরিষ্কার, ডিকাপলড এবং শক্তিশালী অ্যাবস্ট্রাকশন তৈরি করার ক্ষমতা অতুলনীয়। অবজেক্টগুলিকে ভার্চুয়ালাইজ করে, আপনি আরও শক্তিশালী, রক্ষণাবেক্ষণযোগ্য এবং অভিব্যক্তিপূর্ণ সিস্টেম তৈরি করতে পারেন। পরের বার যখন আপনি ডেটা ম্যানেজমেন্ট, ভ্যালিডেশন বা অবজারভেবিলিটি সম্পর্কিত কোনো জটিল চ্যালেঞ্জের মুখোমুখি হবেন, তখন বিবেচনা করুন যে প্রক্সি কাজটি করার জন্য সঠিক টুল কিনা। এটি আপনার টুলকিটের সবচেয়ে সুন্দর সমাধান হতে পারে।