اسرار پروکسی ها: رهگیری و کنترل اشیاء در JavaScript

در پشت صحنه اشیاء JavaScript ابزاری قدرتمند قرار دارد که به شما امکان می دهد هرگونه تعامل با اشیاء را رهگیری ، اصلاح و کنترل کنید. به دنیای پروکسی خوش آمدید.
مقدمه: پروکسی ها چیست؟
در JavaScript ، پراکسی ها به عنوان واسطه برای اشیاء عمل می کنند و به شما امکان می دهند رفتارهای اساسی مانند خواندن ملک ، تکلیف ارزش ، شمارش ، دعوت عملکرد و حتی عملیات را با آن سفارشی کنید new
اپراتور معرفی شده در ES6 (ECMAScript 2015) ، با وجود ارائه امکانات چشمگیر ، پروکسی ها توسط بسیاری از توسعه دهندگان مورد استفاده قرار نمی گیرند.
یک پروکسی یک هدف هدف را می پیچد و تعریف می کند تله که در صورت انجام عملیات خاص بر روی شی انجام می شوند. این تله ها به شما امکان می دهد رفتار پیش فرض JavaScript را رهگیری و سفارشی کنید.
آناتومی یک پروکسی
ساختار اساسی یک پروکسی از دو مؤلفه اصلی تشکیل شده است:
const proxy = new Proxy(target, handler);
- هدف: شیء اصلی که توسط پروکسی پیچیده می شود
- کنترل کننده: یک شیء حاوی “تله” که رفتار سفارشی را تعریف می کند
تله های اساسی
JavaScript 13 تله مختلف را ارائه می دهد ، اما بیایید روی قدرتمندترین آنها تمرکز کنیم:
1. دریافت کنید
در get
Trap Rejection Properts خوانده شده است. هر زمان که به یک ویژگی شی دسترسی داشته باشید ، شروع می شود.
const handler = {
get(target, prop, receiver) {
console.log(`Accessing property: ${prop}`);
return Reflect.get(target, prop, receiver);
}
};
const user = { name: "Anna", age: 28 };
const userProxy = new Proxy(user, handler);
console.log(userProxy.name);
// Output:
// Accessing property: name
// Anna
2. مجموعه
در set
هنگامی که مقادیر به خواص اختصاص می یابد ، تله ایجاد می شود.
const handler = {
set(target, prop, value, receiver) {
if (prop === 'age' && typeof value !== 'number') {
throw new TypeError('Age must be a number');
}
console.log(`Setting ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver);
}
};
const userProxy = new Proxy({}, handler);
userProxy.name = "Charles"; // Setting name = Charles
userProxy.age = 30; // Setting age = 30
try {
userProxy.age = "thirty"; // Error!
} catch (e) {
console.log(e.message); // Age must be a number
}
3. درخواست کنید
تماس های عملکردی را به شما امکان می دهد تا نحوه اجرای یک عملکرد را تغییر دهید.
function add(a, b) {
return a + b;
}
const handler = {
apply(target, thisArg, args) {
console.log(`Calling function with arguments: ${args}`);
return Reflect.apply(target, thisArg, args);
}
};
const addProxy = new Proxy(add, handler);
console.log(addProxy(5, 3));
// Output:
// Calling function with arguments: 5,3
// 8
4
رهگیری new
اپراتور ، به شما این امکان را می دهد تا نحوه فوری اشیاء را کنترل کنید.
class Person {
constructor(name) {
this.name = name;
}
}
const handler = {
construct(target, args, newTarget) {
console.log(`Creating new instance with: ${args}`);
return Reflect.construct(target, args, newTarget);
}
};
const PersonProxy = new Proxy(Person, handler);
const person = new PersonProxy("Daniel");
// Output: Creating new instance with: Daniel
موارد استفاده پیشرفته
1. اعتبار سنجی خودکار
یکی از قدرتمندترین کاربردهای پروکسی ، اعتبار سنجی اتوماتیک بدون آلاینده کد شی اصلی است.
function createValidator(schema) {
return {
set(target, prop, value) {
if (!schema[prop]) {
target[prop] = value;
return true;
}
// Type validation
if (schema[prop].type && typeof value !== schema[prop].type) {
throw new TypeError(`${prop} must be of type ${schema[prop].type}`);
}
// Format validation (regex)
if (schema[prop].pattern && !schema[prop].pattern.test(value)) {
throw new Error(`${prop} does not match the expected pattern`);
}
// Minimum value validation
if (schema[prop].min !== undefined && value < schema[prop].min) {
throw new RangeError(`${prop} must be >= ${schema[prop].min}`);
}
target[prop] = value;
return true;
}
};
}
const userSchema = {
name: { type: "string" },
email: {
type: "string",
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
},
age: {
type: "number",
min: 18
}
};
const user = new Proxy({}, createValidator(userSchema));
user.name = "Laura"; // OK
user.age = 25; // OK
// user.email = "invalid"; // Error: email does not match the expected pattern
// user.age = 15; // Error: age must be >= 18
2. اشیاء واکنشی (سبک vue.js)
vue.js از تکنیکی مشابه پروکسی ها برای ایجاد سیستم واکنش پذیری خود استفاده می کند. در اینجا یک اجرای ساده وجود دارد:
function reactive(obj) {
const observers = new Map();
return new Proxy(obj, {
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
// Collect the current dependency if it exists
if (activeEffect && typeof value !== 'object') {
if (!observers.has(prop)) {
observers.set(prop, new Set());
}
observers.get(prop).add(activeEffect);
}
return value;
},
set(target, prop, value, receiver) {
const result = Reflect.set(target, prop, value, receiver);
// Notify observers
if (observers.has(prop)) {
observers.get(prop).forEach(effect => effect());
}
return result;
}
});
}
// Simplified tracking system
let activeEffect = null;
function watchEffect(fn) {
activeEffect = fn;
fn(); // Execute the function to track dependencies
activeEffect = null;
}
// Usage
const state = reactive({ counter: 0 });
watchEffect(() => {
console.log(`The counter is: ${state.counter}`);
});
// Initial output: The counter is: 0
state.counter++; // Output: The counter is: 1
state.counter += 2; // Output: The counter is: 3
3. محافظت در برابر اصلاحات ناخواسته
پروکسی ها می توانند از اشیاء مهم در برابر اصلاحات ناخواسته محافظت کنند ، و همچنین دسترسی غیرمجاز را تشخیص دهند:
function protectObject(obj, allowedOperations = {}) {
return new Proxy(obj, {
get(target, prop) {
if (allowedOperations.read && !allowedOperations.read.includes(prop)) {
console.warn(`Unauthorized read of property: ${prop}`);
return undefined;
}
return target[prop];
},
set(target, prop, value) {
if (allowedOperations.write && !allowedOperations.write.includes(prop)) {
console.error(`Unauthorized attempt to modify: ${prop}`);
return false;
}
target[prop] = value;
return true;
},
deleteProperty(target, prop) {
console.error(`Attempt to delete property: ${prop}`);
return false;
}
});
}
const secureConfig = protectObject(
{
debug: true,
apiKey: 'sk_12345',
timeout: 30000
},
{
read: ['debug', 'timeout'],
write: ['timeout']
}
);
console.log(secureConfig.debug); // true
console.log(secureConfig.apiKey); // Warning and undefined
secureConfig.timeout = 60000; // Allowed
secureConfig.apiKey = 'new_key'; // Error: Unauthorized attempt
4. بارگذاری تنبل و حافظه پنهان املاک
ما می توانیم از پروکسی ها برای اجرای بارگیری تنبل و ذخیره سازی خواص محاسباتی گران استفاده کنیم:
function lazyProperties(initializer) {
const values = {};
const computed = {};
return new Proxy({}, {
get(target, prop) {
if (!(prop in values)) {
// If the property hasn't been calculated yet
if (prop in initializer) {
console.log(`Calculating value for ${prop}...`);
// Memoization: save the result for future use
values[prop] = initializer[prop]();
console.log(`Value calculated and cached.`);
}
}
return values[prop];
}
});
}
// Usage:
const data = lazyProperties({
expensiveValue: () => {
// Simulating an expensive operation
console.log('Executing time-consuming calculation...');
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.random();
}
return result / 1000000;
},
anotherExpensiveValue: () => {
// Another expensive operation
return fetch('https://api.example.com/data').then(r => r.json());
}
});
console.log(data.expensiveValue); // Calculates the first time
console.log(data.expensiveValue); // Uses cached value
خطرات و ملاحظات عملکرد
اگرچه قدرتمند است ، اما پروکسی ها محدودیت های مهمی دارند:
1. تأثیر عملکرد
پروکسی ها لایه ای از غیرمستقیم را اضافه می کنند که می تواند بر عملکرد تأثیر بگذارد ، به خصوص در عملیات با فرکانس بالا:
// Performance test
function testPerformance() {
const obj = { value: 0 };
const proxy = new Proxy(obj, {
get: (target, prop) => Reflect.get(target, prop),
set: (target, prop, value) => Reflect.set(target, prop, value)
});
console.time('Direct Object');
for (let i = 0; i < 1000000; i++) {
obj.value++;
}
console.timeEnd('Direct Object');
console.time('Via Proxy');
for (let i = 0; i < 1000000; i++) {
proxy.value++;
}
console.timeEnd('Via Proxy');
}
testPerformance();
// Typical output:
// Direct Object: ~5ms
// Via Proxy: ~50ms
2. رفتار با روشهای داخلی
روش ها و خصوصیات داخلی یک شی می تواند به طور غیر منتظره ای در هنگام دسترسی از طریق یک پروکسی رفتار کند:
class MyClass {
constructor() {
this.value = 42;
}
method() {
return this.value;
}
}
const instance = new MyClass();
const proxy = new Proxy(instance, {});
console.log(instance.method()); // 42
console.log(proxy.method()); // Can cause issues if 'this' is used internally
3. پروکسی ها به عنوان مثال شفاف نیستند
class Example {}
const proxy = new Proxy(new Example(), {});
console.log(proxy instanceof Example); // false - this might surprise you!
ادغام پروکسی با سایر ویژگی های مدرن
پروکسی با API بازتاب
API بازتاب در کنار پراکسی ها معرفی شد و روش هایی را ارائه می دهد که مطابق با تله های پروکسی است:
const object = { a: 1, b: 2 };
const proxy = new Proxy(object, {
get(target, prop, receiver) {
console.log(`Accessing ${prop}`);
// Using Reflect.get instead of target[prop]
return Reflect.get(target, prop, receiver);
}
});
// Reflect also allows metaprogramming operations
console.log(Reflect.ownKeys(proxy)); // ['a', 'b']
پروکسی با نقشه ضعیف برای داده های خصوصی
ما می توانیم پروکسی ها را با مپ های ضعیف ترکیب کنیم تا خصوصیات واقعاً خصوصی ایجاد کنیم:
const privateData = new WeakMap();
class SecureUser {
constructor(name, password) {
const data = { name, password, passwordAttempts: 0 };
privateData.set(this, data);
return new Proxy(this, {
get(target, prop) {
if (prop === 'name') {
return privateData.get(target).name;
}
if (prop === 'checkPassword') {
return password => {
const data = privateData.get(target);
data.passwordAttempts++;
if (data.passwordAttempts > 3) {
throw new Error('Account locked after multiple attempts');
}
return data.password === password;
};
}
return target[prop];
}
});
}
}
const user = new SecureUser('john', 'password123');
console.log(user.name); // 'john'
console.log(user.checkPassword('wrong')); // false
console.log(user.checkPassword('wrong')); // false
console.log(user.checkPassword('wrong')); // false
// user.checkPassword('wrong'); // Error: Account locked
console.log(user.password); // undefined - not accessible
الگوهای پیشرفته با پروکسی ها
مجاورت عمیق
برای ایجاد ساختارهای داده کاملاً واکنشی ، باید پروکسی ها را به صورت بازگشتی اعمال کنیم:
function deepProxy(obj, handler) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// Process arrays and objects recursively
for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
obj[key] = deepProxy(obj[key], handler);
}
}
return new Proxy(obj, handler);
}
// Handler that logs all operations
const logHandler = {
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
console.log(`GET: ${prop}`);
return value;
},
set(target, prop, value, receiver) {
console.log(`SET: ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver);
}
};
const data = deepProxy({
user: {
profile: {
name: 'Anna',
contact: {
email: 'anna@example.com'
}
},
preferences: {
theme: 'dark'
}
}
}, logHandler);
// Nested access tracked
data.user.profile.contact.email; // Logs all intermediate accesses
الگوی: پروکسی های قابل برگشت برای کنترل دسترسی موقت
JavaScript اجازه می دهد تا پروکسی های قابل بازگشت ، برای اعطای دسترسی موقت مفید باشد:
function createTemporaryAccess(obj, timeoutMs = 30000) {
const { proxy, revoke } = Proxy.revocable(obj, {});
setTimeout(() => {
console.log('Access automatically revoked');
revoke();
}, timeoutMs);
return proxy;
}
const sensitiveData = { apiKey: '12345', secret: 'confidential value' };
const temporaryAccess = createTemporaryAccess(sensitiveData, 5000);
// Immediate use works
console.log(temporaryAccess.apiKey); // '12345'
// After 5 seconds...
setTimeout(() => {
try {
console.log(temporaryAccess.apiKey);
} catch (e) {
console.log('Error:', e.message); // TypeError: Cannot perform 'get' on a proxy that has been revoked
}
}, 6000);
نتیجه گیری:
پروکسی ها یکی از قدرتمندترین موارد اضافی در JavaScript مدرن را نشان می دهند ، و این امکان را فراهم می کند تا پیشرفته سازی پیشرفته را انجام دهد و نحوه تعامل ما با اشیاء را تغییر دهد. چارچوب هایی مانند vue.js در حال حاضر از این فناوری برای ایجاد سیستم های واکنشی ظریف استفاده می کنند.
نکات نهایی برای استفاده مؤثر از پروکسی ها:
- از کمبود استفاده کنید: پروکسی ها پیچیدگی را اضافه می کنند و می توانند بر عملکرد تأثیر بگذارند.
- زمینه را در نظر بگیرید: در برنامه های بحرانی با کارایی بالا ، فقط در جایی که هزینه عملکرد قابل قبول است ، از پروکسی استفاده کنید.
- با API های دیگر ترکیب کنید: تأمل ، مپ ضعیف و کلاس ها با پروکسی ها بسیار خوب کار می کنند.
- کاملاً آزمون: رفتار پروکسی می تواند ظریف باشد ، به خصوص با وراثت و روشهای داخلی.
با تسلط بر پروکسی ها ، درهای الگوهای برنامه نویسی پیشرفته را باز می کنید که می تواند کد شما را قوی تر ، ایمن تر و بیان کند.