חקור תבניות Proxy מתקדמות בג'אווהסקריפט ליירוט אובייקטים, אימות והתנהגות דינמית. למד כיצד לשפר את איכות הקוד, האבטחה והתחזוקה עם דוגמאות מעשיות.
תבניות Proxy בג'אווהסקריפט: יירוט ואימות אובייקטים מתקדמים
אובייקט ה-Proxy של JavaScript הוא תכונה רבת עוצמה המאפשרת לך ליירט ולהתאים אישית פעולות אובייקט בסיסיות. הוא מאפשר טכניקות תכנות-על מתקדמות, ומציע שליטה רבה יותר בהתנהגות האובייקט ופותח אפשרויות לתבניות עיצוב מתוחכמות. מאמר זה בוחן תבניות Proxy שונות, ומציג את מקרי השימוש שלהן באימות, יירוט ושינוי התנהגות דינמית. אנו נתעמק בדוגמאות מעשיות כדי להדגים כיצד Proxies יכולים לשפר את איכות הקוד, האבטחה והתחזוקה בפרויקטי JavaScript שלך.
הבנת ה-Proxy של JavaScript
ביסודו, אובייקט Proxy עוטף אובייקט אחר (היעד) ומיירט פעולות המבוצעות על אותו יעד. יירוטים אלה מטופלים על ידי מלכודות, שהן שיטות המגדירות התנהגות מותאמת אישית עבור פעולות ספציפיות כמו קבלת מאפיין, הגדרת מאפיין או קריאה לפונקציה. ה-Proxy API מספק מנגנון גמיש וניתן להרחבה לשינוי ברירת המחדל של התנהגות האובייקטים.
מושגי מפתח
- יעד: האובייקט המקורי שאליו ה-Proxy עוטף.
- מטפל: אובייקט המכיל את שיטות המלכודת. כל מלכודת מתאימה לפעולה ספציפית.
- מלכודות: שיטות בתוך המטפל המיירטים ומתאימים אישית פעולות אובייקט. מלכודות נפוצות כוללות
get,set,applyו-construct.
יצירת Proxy
כדי ליצור Proxy, אתה משתמש ב- constructor Proxy, מעביר את אובייקט היעד ואובייקט המטפל כארגומנטים:
const target = {};
const handler = {
get: function(target, property, receiver) {
console.log(`Getting property: ${property}`);
return Reflect.get(target, property, receiver);
}
};
const proxy = new Proxy(target, handler);
proxy.name = "John"; // Logs: Getting property: name
console.log(proxy.name); // Logs: Getting property: name, then John
מלכודות Proxy נפוצות
Proxies מציעים מגוון מלכודות ליירוט פעולות שונות. הנה כמה מהמלכודות הנפוצות ביותר:
get(target, property, receiver): מיירט גישה למאפיין.set(target, property, value, receiver): מיירט הקצאת מאפיין.has(target, property): מיירט את האופרטורin.deleteProperty(target, property): מיירט את האופרטורdelete.apply(target, thisArg, argumentsList): מיירט קריאות לפונקציות.construct(target, argumentsList, newTarget): מיירט את האופרטורnew.getPrototypeOf(target): מיירט את השיטהObject.getPrototypeOf().setPrototypeOf(target, prototype): מיירט את השיטהObject.setPrototypeOf().isExtensible(target): מיירט את השיטהObject.isExtensible().preventExtensions(target): מיירט את השיטהObject.preventExtensions().getOwnPropertyDescriptor(target, property): מיירט את השיטהObject.getOwnPropertyDescriptor().defineProperty(target, property, descriptor): מיירט את השיטהObject.defineProperty().ownKeys(target): מיירט את השיטותObject.getOwnPropertyNames()ו-Object.getOwnPropertySymbols().
תבניות Proxy
כעת, בואו נחקור כמה תבניות Proxy מעשיות והיישומים שלהן:
1. Validation Proxy
Validation Proxy אוכף אילוצים על הקצאות מאפיינים. הוא מיירט את המלכודת set כדי לאמת את הערך החדש לפני שהוא מאפשר להקצאה להמשיך.
דוגמה: אימות קלט משתמש בטופס.
const user = {};
const validator = {
set: function(target, property, value) {
if (property === 'age') {
if (!Number.isInteger(value) || value < 0 || value > 120) {
throw new Error('Invalid age. Age must be an integer between 0 and 120.');
}
}
target[property] = value;
return true; // Indicate success
}
};
const proxy = new Proxy(user, validator);
proxy.name = 'Alice';
proxy.age = 30;
console.log(user);
try {
proxy.age = 'invalid'; // Throws an error
} catch (error) {
console.error(error.message);
}
בדוגמה זו, המלכודת set בודקת אם המאפיין age הוא מספר שלם בין 0 ל-120. אם האימות נכשל, נזרקת שגיאה, המונעת מהערך הלא תקין להוקצא.
דוגמה גלובלית: תבנית אימות זו חיונית להבטחת שלמות הנתונים ביישומים גלובליים שבהם קלט משתמש עשוי להגיע ממקורות ותרבויות שונות. לדוגמה, אימות מיקודים יכול להשתנות באופן משמעותי בין מדינות. ניתן להתאים Proxy אימות לתמיכה בכללי אימות שונים בהתבסס על מיקום המשתמש.
const address = {};
const addressValidator = {
set: function(target, property, value) {
if (property === 'postalCode') {
// Example: Assuming a simple US postal code validation
if (!/^[0-9]{5}(?:-[0-9]{4})?$/.test(value)) {
throw new Error('Invalid US postal code.');
}
}
target[property] = value;
return true;
}
};
const addressProxy = new Proxy(address, addressValidator);
addressProxy.postalCode = "12345-6789"; // Valid
try {
addressProxy.postalCode = "abcde"; // Invalid
} catch(e) {
console.log(e);
}
// For a more international application, you'd use a more sophisticated validation library
// that could validate postal codes based on the user's country.
2. Logging Proxy
Logging Proxy מיירט גישה והקצאה של מאפיינים כדי לרשום פעולות אלה. זה שימושי לניפוי באגים ולביקורת.
דוגמה: רישום גישה ושינוי של מאפיינים.
const data = {
value: 10
};
const logger = {
get: function(target, property) {
console.log(`Getting property: ${property}`);
return target[property];
},
set: function(target, property, value) {
console.log(`Setting property: ${property} to ${value}`);
target[property] = value;
return true;
}
};
const proxy = new Proxy(data, logger);
console.log(proxy.value); // Logs: Getting property: value, then 10
proxy.value = 20; // Logs: Setting property: value to 20
המלכודות get ו-set רושמות את המאפיין שאליו ניגשים או משנים, ומספקות מעקב אחר אינטראקציות אובייקטים.
דוגמה גלובלית: בתאגיד רב לאומי, ניתן להשתמש ב-Logging Proxies כדי לבקר גישה לנתונים ושינויים שבוצעו על ידי עובדים במיקומים שונים. זה קריטי לצורכי תאימות ואבטחה. ייתכן שיהיה צורך לשקול אזורי זמן במידע הרישום.
const employeeData = {
name: "John Doe",
salary: 50000
};
const auditLogger = {
get: function(target, property) {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - [GET] Accessing property: ${property}`);
return target[property];
},
set: function(target, property, value) {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - [SET] Setting property: ${property} to ${value}`);
target[property] = value;
return true;
}
};
const proxiedEmployee = new Proxy(employeeData, auditLogger);
proxiedEmployee.name; // Logs timestamp and access to 'name'
proxiedEmployee.salary = 60000; // Logs timestamp and modification of 'salary'
3. Read-Only Proxy
Read-Only Proxy מונע הקצאת מאפיינים. הוא מיירט את המלכודת set וזורק שגיאה אם מנסים לשנות מאפיין.
דוגמה: הפיכת אובייקט לבלתי ניתן לשינוי.
const config = {
apiUrl: 'https://api.example.com'
};
const readOnly = {
set: function(target, property, value) {
throw new Error(`Cannot set property: ${property}. Object is read-only.`);
}
};
const proxy = new Proxy(config, readOnly);
console.log(proxy.apiUrl);
try {
proxy.apiUrl = 'https://newapi.example.com'; // Throws an error
} catch (error) {
console.error(error.message);
}
כל ניסיון להגדיר מאפיין ב-Proxy יביא לשגיאה, ויבטיח שהאובייקט יישאר בלתי ניתן לשינוי.
דוגמה גלובלית: תבנית זו שימושית להגנה על קבצי תצורה שאסור לשנות בזמן ריצה, במיוחד ביישומים מופצים גלובלית. שינוי תצורה בטעות באזור אחד יכול להשפיע על כל המערכת.
const globalSettings = {
defaultLanguage: "en",
currency: "USD",
timeZone: "UTC"
};
const immutableHandler = {
set: function(target, property, value) {
throw new Error(`Cannot modify read-only property: ${property}`);
}
};
const immutableSettings = new Proxy(globalSettings, immutableHandler);
console.log(immutableSettings.defaultLanguage); // outputs 'en'
// Attempting to change a value will throw an error
// immutableSettings.defaultLanguage = "fr"; // throws Error: Cannot modify read-only property: defaultLanguage
4. Virtual Proxy
Virtual Proxy שולט בגישה למשאב שאולי יקר ליצור או לאחזר. זה יכול לעכב את יצירת המשאב עד שהוא נחוץ בפועל.
דוגמה: טעינת תמונה עצלה.
const image = {
display: function() {
console.log('Displaying image');
}
};
const virtualProxy = {
get: function(target, property) {
if (property === 'display') {
console.log('Creating image...');
const realImage = {
display: function() {
console.log('Displaying real image');
}
};
target.display = realImage.display;
return realImage.display;
}
return target[property];
}
};
const proxy = new Proxy(image, virtualProxy);
// The image is not created until display is called.
proxy.display(); // Logs: Creating image..., then Displaying real image
אובייקט התמונה האמיתית נוצר רק כאשר נקראת השיטה display, תוך הימנעות מצריכת משאבים מיותרת.
דוגמה גלובלית: שקול אתר מסחר אלקטרוני עולמי המשרת תמונות של מוצרים. באמצעות Virtual Proxy, ניתן לטעון תמונות רק כאשר הן גלויות למשתמש, תוך אופטימיזציה של השימוש ברוחב הפס ושיפור זמני טעינת הדפים, במיוחד עבור משתמשים עם חיבורי אינטרנט איטיים באזורים שונים.
const product = {
loadImage: function() {
console.log("Loading high-resolution image...");
// Simulate loading a large image
setTimeout(() => {
console.log("Image loaded");
this.displayImage();
}, 2000);
},
displayImage: function() {
console.log("Displaying the image");
}
};
const lazyLoadProxy = {
get: function(target, property) {
if (property === "displayImage") {
// Instead of loading immediately, delay the loading
console.log("Request to display image received. Loading...");
target.loadImage();
return function() { /* Intentionally Empty */ }; // Return empty function to prevent immediate execution
}
return target[property];
}
};
const proxiedProduct = new Proxy(product, lazyLoadProxy);
// Call displayImage triggers the lazy loading process
proxiedProduct.displayImage();
5. Revocable Proxy
Revocable Proxy מאפשר לך לבטל את ה-Proxy בכל עת, ולהפוך אותו לבלתי שמיש. זה שימושי עבור תרחישים רגישים לאבטחה שבהם אתה צריך לשלוט בגישה לאובייקט.
דוגמה: מתן גישה זמנית למשאב.
const target = {
secret: 'This is a secret'
};
const handler = {
get: function(target, property) {
console.log('Accessing secret property');
return target[property];
}
};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.secret); // Logs: Accessing secret property, then This is a secret
revoke();
try {
console.log(proxy.secret); // Throws a TypeError
} catch (error) {
console.error(error.message); // Logs: Cannot perform 'get' on a proxy that has been revoked
}
השיטה Proxy.revocable() יוצרת Proxy בר-ביטול. קריאה לפונקציה revoke() הופכת את ה-Proxy לבלתי שמיש, ומונעת גישה נוספת לאובייקט היעד.
דוגמה גלובלית: במערכת מופצת גלובלית, ייתכן שתשתמש ב-Proxy בר-ביטול כדי להעניק גישה זמנית לנתונים רגישים לשירות הפועל באזור מסוים. לאחר זמן מסוים, ניתן לבטל את ה-Proxy כדי למנוע גישה לא מורשית.
const sensitiveData = {
apiKey: "SUPER_SECRET_KEY"
};
const handler = {
get: function(target, property) {
console.log("Accessing sensitive data");
return target[property];
}
};
const { proxy: dataProxy, revoke: revokeAccess } = Proxy.revocable(sensitiveData, handler);
// Allow access for 5 seconds
setTimeout(() => {
revokeAccess();
console.log("Access revoked");
}, 5000);
// Attempt to access data
console.log(dataProxy.apiKey); // Logs the API Key
// After 5 seconds, this will throw an error
setTimeout(() => {
try {
console.log(dataProxy.apiKey); // Throws: TypeError: Cannot perform 'get' on a proxy that has been revoked
} catch (error) {
console.error(error);
}
}, 6000);
6. Type Conversion Proxy
Type Conversion Proxy מיירט גישה למאפיין כדי להמיר אוטומטית את הערך המוחזר לסוג מסוים. זה יכול להיות שימושי לעבודה עם נתונים ממקורות שונים שאולי יש להם סוגים לא עקביים.
דוגמה: המרת ערכי מחרוזת למספרים.
const data = {
price: '10.99',
quantity: '5'
};
const typeConverter = {
get: function(target, property) {
const value = target[property];
if (typeof value === 'string' && !isNaN(Number(value))) {
return Number(value);
}
return value;
}
};
const proxy = new Proxy(data, typeConverter);
console.log(proxy.price + 1); // Logs: 11.99 (number)
console.log(proxy.quantity * 2); // Logs: 10 (number)
המלכודת get בודקת אם ערך המאפיין הוא מחרוזת שניתן להמיר למספר. אם כן, היא ממירה את הערך למספר לפני החזרתו.
דוגמה גלובלית: בעת טיפול בנתונים המגיעים מממשקי API עם מוסכמות עיצוב שונות (למשל, פורמטים שונים של תאריכים או סמלי מטבע), Type Conversion Proxy יכול להבטיח עקביות נתונים בכל היישום שלך, ללא קשר למקור. לדוגמה, טיפול בפורמטי תאריכים שונים והמרת כולם לתבנית ISO 8601.
const apiData = {
dateUS: "12/31/2023",
dateEU: "31/12/2023"
};
const dateFormatConverter = {
get: function(target, property) {
let value = target[property];
if (property.startsWith("date")) {
// Attempt to convert both US and EU date formats to ISO 8601
if (property === "dateUS") {
const [month, day, year] = value.split("/");
value = `${year}-${month}-${day}`;
} else if (property === "dateEU") {
const [day, month, year] = value.split("/");
value = `${year}-${month}-${day}`;
}
return value;
}
return value;
}
};
const proxiedApiData = new Proxy(apiData, dateFormatConverter);
console.log(proxiedApiData.dateUS); // Outputs: 2023-12-31
console.log(proxiedApiData.dateEU); // Outputs: 2023-12-31
שיטות עבודה מומלצות לשימוש ב-Proxies
- השתמש ב-Proxies בזהירות: Proxies יכולים להוסיף מורכבות לקוד שלך. השתמש בהם רק כאשר הם מספקים יתרונות משמעותיים, כגון אימות משופר, רישום או שליטה בהתנהגות האובייקט.
- שקול ביצועים: מלכודות Proxy יכולות להכניס תקורה. בצע פרופיל לקוד שלך כדי להבטיח ש-Proxies לא ישפיעו לרעה על הביצועים, במיוחד בקטעים קריטיים לביצועים.
- טפל בשגיאות בצורה אלגנטית: ודא ששיטות המלכודת שלך מטפלות בשגיאות כראוי, ומספקות הודעות שגיאה אינפורמטיביות בעת הצורך.
- השתמש ב-Reflect API: ה-API
Reflectמספק שיטות המשקפות את ברירת המחדל של פעולות אובייקט. השתמש בשיטותReflectבתוך שיטות המלכודת שלך כדי להאציל להתנהגות המקורית בעת הצורך. זה מבטיח שהמלכודות שלך לא ישברו פונקציונליות קיימת. - תיעד את ה-Proxies שלך: תיעד בבירור את המטרה וההתנהגות של ה-Proxies שלך, כולל המלכודות שבהן נעשה שימוש והאילוצים שאוכפים. זה יעזור למפתחים אחרים להבין ולתחזק את הקוד שלך.
סיכום
JavaScript Proxies הם כלי רב עוצמה לתפעול ויירוט אובייקטים מתקדמים. על ידי הבנה ויישום של תבניות Proxy שונות, אתה יכול לשפר את איכות הקוד, האבטחה והתחזוקה. מאימות קלט משתמש ועד לשליטה בגישה למשאבים רגישים, Proxies מציעים מנגנון גמיש וניתן להרחבה להתאמה אישית של התנהגות האובייקט. כשאתה חוקר את האפשרויות של Proxies, זכור להשתמש בהם בזהירות ולתעד את הקוד שלך ביסודיות.
הדוגמאות שסופקו מדגימות כיצד להשתמש ב-JavaScript Proxies כדי לפתור בעיות בעולם האמיתי בהקשר גלובלי. על ידי הבנה ויישום של תבניות אלה, אתה יכול ליצור יישומים חזקים, מאובטחים וניתנים לתחזוקה העונים על הצרכים של בסיס משתמשים מגוון. זכור תמיד לקחת בחשבון את ההשלכות הגלובליות של הקוד שלך ולהתאים את הפתרונות שלך לדרישות הספציפיות של אזורים ותרבויות שונות.