μλ°μ€ν¬λ¦½νΈ νλ‘μ ν¨ν΄μΌλ‘ κ°μ²΄ λμμ λ³κ²½νλ λ°©λ²μ νμν©λλ€. μ ν¨μ± κ²μ¬, κ°μν, μΆμ λ° κΈ°ν κ³ κΈ κΈ°λ²λ€μ μ½λ μμ μ ν¨κ» λ°°μ보μΈμ.
μλ°μ€ν¬λ¦½νΈ νλ‘μ ν¨ν΄: κ°μ²΄ λμ λ³κ²½ λ§μ€ν°νκΈ°
μλ°μ€ν¬λ¦½νΈ νλ‘μ(Proxy) κ°μ²΄λ κ°μ²΄μ λν κΈ°λ³Έ μμ μ κ°λ‘μ±κ³ μ¬μ©μ μ μν μ μλ κ°λ ₯ν λ©μ»€λμ¦μ μ 곡ν©λλ€. μ΄ κΈ°λ₯μ κ°μ²΄ λμμ μ μ΄νκΈ° μν κ΄λ²μν λμμΈ ν¨ν΄κ³Ό κ³ κΈ κΈ°μ μ λ¬Έμ μ΄μ΄μ€λλ€. μ΄ μ’ ν© κ°μ΄λμμλ λ€μν νλ‘μ ν¨ν΄μ μ΄ν΄λ³΄κ³ μ€μ μ½λ μμ λ₯Ό ν΅ν΄ κ·Έ μ¬μ©λ²μ μ€λͺ ν©λλ€.
μλ°μ€ν¬λ¦½νΈ νλ‘μλ 무μμΈκ°?
νλ‘μ κ°μ²΄λ λ€λ₯Έ κ°μ²΄(λμ)λ₯Ό κ°μΈκ³ κ·Έ μ°μ°μ κ°λ‘μ±λλ€. νΈλ©(trap)μ΄λΌκ³ μλ €μ§ μ΄λ¬ν μ°μ°μλ μμ± μ‘°ν, ν λΉ, μ΄κ±°, ν¨μ νΈμΆ λ±μ΄ ν¬ν¨λ©λλ€. νλ‘μλ₯Ό μ¬μ©νλ©΄ μ΄λ¬ν μμ μ , ν λλ λμ μ μ€νλ μ¬μ©μ μ μ λ‘μ§μ μ μν μ μμ΅λλ€. νλ‘μμ ν΅μ¬ κ°λ μ μλ°μ€ν¬λ¦½νΈ μΈμ΄ μ체μ λμμ μ‘°μν μ μκ² ν΄μ£Όλ "λ©ννλ‘κ·Έλλ°"μ ν¬ν¨ν©λλ€.
νλ‘μλ₯Ό μμ±νλ κΈ°λ³Έ ꡬ문μ λ€μκ³Ό κ°μ΅λλ€:
const proxy = new Proxy(target, handler);
- target: νλ‘μν μλ³Έ κ°μ²΄μ λλ€.
- handler: νλ‘μκ° λμμ λν μμ μ κ°λ‘μ±λ λ°©λ²μ μ μνλ λ©μλ(νΈλ©)λ₯Ό ν¬ν¨νλ κ°μ²΄μ λλ€.
μΌλ°μ μΈ νλ‘μ νΈλ©
νΈλ€λ¬ κ°μ²΄λ μ¬λ¬ νΈλ©μ μ μν μ μμ΅λλ€. λ€μμ κ°μ₯ μΌλ°μ μΌλ‘ μ¬μ©λλ λͺ κ°μ§ νΈλ©μ λλ€:
- get(target, property, receiver): μμ± μ κ·Όμ κ°λ‘μ±λλ€ (μ:
obj.property
). - set(target, property, value, receiver): μμ± ν λΉμ κ°λ‘μ±λλ€ (μ:
obj.property = value
). - has(target, property):
in
μ°μ°μλ₯Ό κ°λ‘μ±λλ€ (μ:'property' in obj
). - deleteProperty(target, property):
delete
μ°μ°μλ₯Ό κ°λ‘μ±λλ€ (μ:delete obj.property
). - 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()
νΈμΆμ κ°λ‘μ±λλ€.
νλ‘μ ν¨ν΄ λ° μ¬μ© μ¬λ‘
λͺ κ°μ§ μΌλ°μ μΈ νλ‘μ ν¨ν΄κ³Ό μ€μ μλ리μ€μμ μ΄λ»κ² μ μ©λ μ μλμ§ μ΄ν΄λ³΄κ² μ΅λλ€:
1. μ ν¨μ± κ²μ¬
μ ν¨μ± κ²μ¬ ν¨ν΄μ νλ‘μλ₯Ό μ¬μ©νμ¬ μμ± ν λΉμ λν μ μ½ μ‘°κ±΄μ κ°μ ν©λλ€. μ΄λ λ°μ΄ν° 무결μ±μ 보μ₯νλ λ° μ μ©ν©λλ€.
const validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('Age is not an integer');
}
if (value < 0) {
throw new RangeError('Age must be a non-negative integer');
}
}
// The default behavior to store the value
obj[prop] = value;
// Indicate success
return true;
}
};
let person = {};
let proxy = new Proxy(person, validator);
proxy.age = 25; // Valid
console.log(proxy.age); // Output: 25
try {
proxy.age = 'young'; // Throws TypeError
} catch (e) {
console.log(e); // Output: TypeError: Age is not an integer
}
try {
proxy.age = -10; // Throws RangeError
} catch (e) {
console.log(e); // Output: RangeError: Age must be a non-negative integer
}
μμ: μ¬μ©μ λ°μ΄ν° μ ν¨μ± κ²μ¬κ° νμν μ μ μκ±°λ νλ«νΌμ μκ°ν΄ 보μΈμ. νλ‘μλ λμ΄, μ΄λ©μΌ νμ, λΉλ°λ²νΈ κ°λ λ° κΈ°ν νλμ λν κ·μΉμ κ°μ νμ¬ μ ν¨νμ§ μμ λ°μ΄ν°κ° μ μ₯λλ κ²μ λ°©μ§ν μ μμ΅λλ€.
2. κ°μν (μ§μ° λ‘λ©)
μ§μ° λ‘λ©(lazy loading)μ΄λΌκ³ λ μλ €μ§ κ°μνλ λΉμ©μ΄ λ§μ΄ λλ 리μμ€μ λ‘λ©μ μ€μ λ‘ νμν λκΉμ§ μ§μ°μν΅λλ€. νλ‘μλ μ€μ κ°μ²΄μ λν νλ μ΄μ€νλ μν μ νμ¬ μμ±μ μ κ·Όν λλ§ λ‘λ©ν μ μμ΅λλ€.
const expensiveData = {
load: function() {
console.log('Loading expensive data...');
// Simulate a time-consuming operation (e.g., fetching from a database)
return new Promise(resolve => {
setTimeout(() => {
resolve({
data: 'This is the expensive data'
});
}, 2000);
});
}
};
const lazyLoadHandler = {
get: function(target, prop) {
if (prop === 'data') {
console.log('Accessing data, loading it if necessary...');
return target.load().then(result => {
target.data = result.data; // Store the loaded data
return result.data;
});
} else {
return target[prop];
}
}
};
const lazyData = new Proxy(expensiveData, lazyLoadHandler);
console.log('Initial access...');
lazyData.data.then(data => {
console.log('Data:', data); // Output: Data: This is the expensive data
});
console.log('Subsequent access...');
lazyData.data.then(data => {
console.log('Data:', data); // Output: Data: This is the expensive data (loaded from cache)
});
μμ: μλ§μ μΈλΆ μ 보μ κ΄λ ¨ λ―Έλμ΄λ₯Ό ν¬ν¨νλ μ¬μ©μ νλ‘νμ΄ μλ λκ·λͺ¨ μμ λ―Έλμ΄ νλ«νΌμ μμν΄ λ³΄μΈμ. λͺ¨λ νλ‘ν λ°μ΄ν°λ₯Ό μ¦μ λ‘λ©νλ κ²μ λΉν¨μ¨μ μΌ μ μμ΅λλ€. νλ‘μλ₯Ό μ¬μ©ν κ°μνλ₯Ό ν΅ν΄ κΈ°λ³Έ νλ‘ν μ 보λ₯Ό λ¨Όμ λ‘λ©νκ³ , μ¬μ©μκ° ν΄λΉ μΉμ μΌλ‘ μ΄λν λλ§ μΆκ° μΈλΆ μ 보λ λ―Έλμ΄ μ½ν μΈ λ₯Ό λ‘λ©ν μ μμ΅λλ€.
3. λ‘κΉ λ° μΆμ
νλ‘μλ μμ± μ κ·Ό λ° μμ μ μΆμ νλ λ° μ¬μ©λ μ μμ΅λλ€. μ΄λ λλ²κΉ , κ°μ¬ λ° μ±λ₯ λͺ¨λν°λ§μ μ μ©ν©λλ€.
const logHandler = {
get: function(target, prop, receiver) {
console.log(`GET ${prop}`);
return Reflect.get(target, prop, receiver);
},
set: function(target, prop, value) {
console.log(`SET ${prop} to ${value}`);
target[prop] = value;
return true;
}
};
let obj = { name: 'Alice' };
let proxy = new Proxy(obj, logHandler);
console.log(proxy.name); // Output: GET name, Alice
proxy.age = 30; // Output: SET age to 30
μμ: 곡λ λ¬Έμ νΈμ§ μ ν리μΌμ΄μ μμ νλ‘μλ λ¬Έμ λ΄μ©μ λν λͺ¨λ λ³κ²½ μ¬νμ μΆμ ν μ μμ΅λλ€. μ΄λ₯Ό ν΅ν΄ κ°μ¬ μΆμ μ μμ±νκ³ , μ€ν μ·¨μ/λ€μ μ€ν κΈ°λ₯μ νμ±ννλ©°, μ¬μ©μ κΈ°μ¬λμ λν ν΅μ°°λ ₯μ μ 곡ν μ μμ΅λλ€.
4. μ½κΈ° μ μ© λ·°
νλ‘μλ κ°μ²΄μ μ½κΈ° μ μ© λ·°λ₯Ό μμ±νμ¬ μ°λ°μ μΈ μμ μ λ°©μ§ν μ μμ΅λλ€. μ΄λ λ―Όκ°ν λ°μ΄ν°λ₯Ό 보νΈνλ λ° μ μ©ν©λλ€.
const readOnlyHandler = {
set: function(target, prop, value) {
console.error(`Cannot set property ${prop}: object is read-only`);
return false; // Indicate that the set operation failed
},
deleteProperty: function(target, prop) {
console.error(`Cannot delete property ${prop}: object is read-only`);
return false; // Indicate that the delete operation failed
}
};
let data = { name: 'Bob', age: 40 };
let readOnlyData = new Proxy(data, readOnlyHandler);
try {
readOnlyData.age = 41; // Throws an error
} catch (e) {
console.log(e); // No error thrown because the 'set' trap returns false.
}
try {
delete readOnlyData.name; // Throws an error
} catch (e) {
console.log(e); // No error thrown because the 'deleteProperty' trap returns false.
}
console.log(data.age); // Output: 40 (unchanged)
μμ: μΌλΆ μ¬μ©μκ° κ³μ’ μ 보μ λν μ½κΈ° μ μ© μ κ·Ό κΆνμ κ°λ κΈμ΅ μμ€ν μ μκ°ν΄ 보μΈμ. νλ‘μλ₯Ό μ¬μ©νμ¬ μ΄λ¬ν μ¬μ©μκ° κ³μ’ μμ‘μ΄λ κΈ°ν μ€μν λ°μ΄ν°λ₯Ό μμ νλ κ²μ λ°©μ§ν μ μμ΅λλ€.
5. κΈ°λ³Έκ°
νλ‘μλ λλ½λ μμ±μ λν΄ κΈ°λ³Έκ°μ μ 곡ν μ μμ΅λλ€. μ΄λ μ½λλ₯Ό λ¨μννκ³ null/undefined κ²μ¬λ₯Ό νΌνκ² ν΄μ€λλ€.
const defaultValuesHandler = {
get: function(target, prop, receiver) {
if (!(prop in target)) {
console.log(`Property ${prop} not found, returning default value.`);
return 'Default Value'; // Or any other appropriate default
}
return Reflect.get(target, prop, receiver);
}
};
let config = { apiUrl: 'https://api.example.com' };
let configWithDefaults = new Proxy(config, defaultValuesHandler);
console.log(configWithDefaults.apiUrl); // Output: https://api.example.com
console.log(configWithDefaults.timeout); // Output: Property timeout not found, returning default value. Default Value
μμ: κ΅¬μ± κ΄λ¦¬ μμ€ν μμ νλ‘μλ λλ½λ μ€μ μ λν κΈ°λ³Έκ°μ μ 곡ν μ μμ΅λλ€. μλ₯Ό λ€μ΄, κ΅¬μ± νμΌμ λ°μ΄ν°λ² μ΄μ€ μ°κ²° μκ° μ΄κ³Όκ° μ§μ λμ§ μμ κ²½μ° νλ‘μλ 미리 μ μλ κΈ°λ³Έκ°μ λ°νν μ μμ΅λλ€.
6. λ©νλ°μ΄ν° λ° μ΄λ Έν μ΄μ
νλ‘μλ μλ³Έ κ°μ²΄λ₯Ό μμ νμ§ μκ³ λ μΆκ° μ 보λ₯Ό μ 곡νλ λ©νλ°μ΄ν°λ μ΄λ Έν μ΄μ μ κ°μ²΄μ 첨λΆν μ μμ΅λλ€.
const metadataHandler = {
get: function(target, prop, receiver) {
if (prop === '__metadata__') {
return { description: 'This is metadata for the object' };
}
return Reflect.get(target, prop, receiver);
}
};
let article = { title: 'Introduction to Proxies', content: '...' };
let articleWithMetadata = new Proxy(article, metadataHandler);
console.log(articleWithMetadata.title); // Output: Introduction to Proxies
console.log(articleWithMetadata.__metadata__.description); // Output: This is metadata for the object
μμ: μ½ν μΈ κ΄λ¦¬ μμ€ν μμ νλ‘μλ κΈ°μ¬μ μμ±μ μ 보, λ°νμΌ, ν€μλμ κ°μ λ©νλ°μ΄ν°λ₯Ό 첨λΆν μ μμ΅λλ€. μ΄ λ©νλ°μ΄ν°λ μ½ν μΈ κ²μ, νν°λ§ λ° λΆλ₯μ μ¬μ©λ μ μμ΅λλ€.
7. ν¨μ κ°λ‘μ±κΈ°
νλ‘μλ ν¨μ νΈμΆμ κ°λ‘μ± μ μμ΄ λ‘κΉ , μ ν¨μ± κ²μ¬ λλ κΈ°ν μ μ²λ¦¬/νμ²λ¦¬ λ‘μ§μ μΆκ°ν μ μμ΅λλ€.
const functionInterceptor = {
apply: function(target, thisArg, argumentsList) {
console.log('Calling function with arguments:', argumentsList);
const result = target.apply(thisArg, argumentsList);
console.log('Function returned:', result);
return result;
}
};
function add(a, b) {
return a + b;
}
let proxiedAdd = new Proxy(add, functionInterceptor);
let sum = proxiedAdd(5, 3); // Output: Calling function with arguments: [5, 3], Function returned: 8
console.log(sum); // Output: 8
μμ: μν μ ν리μΌμ΄μ μμ νλ‘μλ κ±°λ ν¨μ νΈμΆμ κ°λ‘μ± κ° κ±°λλ₯Ό κΈ°λ‘νκ³ κ±°λλ₯Ό μ€ννκΈ° μ μ μ¬κΈ° νμ§ κ²μ¬λ₯Ό μνν μ μμ΅λλ€.
8. μμ±μ κ°λ‘μ±κΈ°
νλ‘μλ μμ±μ νΈμΆμ κ°λ‘μ± μ μμ΄ κ°μ²΄ μμ±μ μ¬μ©μ μ μν μ μμ΅λλ€.
const constructorInterceptor = {
construct: function(target, argumentsList, newTarget) {
console.log('Creating a new instance of', target.name, 'with arguments:', argumentsList);
const obj = new target(...argumentsList);
console.log('New instance created:', obj);
return obj;
}
};
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let ProxiedPerson = new Proxy(Person, constructorInterceptor);
let person = new ProxiedPerson('Alice', 28); // Output: Creating a new instance of Person with arguments: ['Alice', 28], New instance created: Person { name: 'Alice', age: 28 }
console.log(person);
μμ: κ²μ κ°λ° νλ μμν¬μμ νλ‘μλ κ²μ κ°μ²΄ μμ±μ κ°λ‘μ± κ³ μ IDλ₯Ό μλμΌλ‘ ν λΉνκ³ , κΈ°λ³Έ κ΅¬μ± μμλ₯Ό μΆκ°νλ©°, κ²μ μμ§μ λ±λ‘ν μ μμ΅λλ€.
κ³ κΈ κ³ λ € μ¬ν
- μ±λ₯: νλ‘μλ μ μ°μ±μ μ 곡νμ§λ§ μ±λ₯ μ€λ²ν€λλ₯Ό μ λ°ν μ μμ΅λλ€. νΉν μ±λ₯μ΄ μ€μν μ ν리μΌμ΄μ μμλ νλ‘μ μ¬μ©μ μ΄μ μ΄ μ±λ₯ λΉμ©μ μννλμ§ νμΈνκΈ° μν΄ μ½λλ₯Ό λ²€μΉλ§νΉνκ³ νλ‘νμΌλ§νλ κ²μ΄ μ€μν©λλ€.
- νΈνμ±: νλ‘μλ μλ°μ€ν¬λ¦½νΈμ λΉκ΅μ μ΅κ·Όμ μΆκ°λμμΌλ―λ‘ μ΄μ λ²μ μ λΈλΌμ°μ μμλ μ§μνμ§ μμ μ μμ΅λλ€. μ΄μ νκ²½κ³Όμ νΈνμ±μ 보μ₯νκΈ° μν΄ κΈ°λ₯ κ°μ§ λλ ν΄λ¦¬νμ μ¬μ©νμΈμ.
- μ·¨μ κ°λ₯ν νλ‘μ:
Proxy.revocable()
λ©μλλ μ·¨μν μ μλ νλ‘μλ₯Ό μμ±ν©λλ€. νλ‘μλ₯Ό μ·¨μνλ©΄ λ μ΄μ μμ μ΄ κ°λ‘μ±μ§μ§ μμ΅λλ€. μ΄λ 보μ λλ 리μμ€ κ΄λ¦¬ λͺ©μ μ μ μ©ν μ μμ΅λλ€. - Reflect API: Reflect APIλ νλ‘μ νΈλ©μ κΈ°λ³Έ λμμ μννλ λ©μλλ₯Ό μ 곡ν©λλ€.
Reflect
λ₯Ό μ¬μ©νλ©΄ νλ‘μ μ½λκ° μΈμ΄ μ¬μκ³Ό μΌκ΄λκ² λμνλλ‘ λ³΄μ₯ν μ μμ΅λλ€.
κ²°λ‘
μλ°μ€ν¬λ¦½νΈ νλ‘μλ κ°μ²΄ λμμ μ¬μ©μ μ μνκΈ° μν κ°λ ₯νκ³ λ€μ¬λ€λ₯ν λ©μ»€λμ¦μ μ 곡ν©λλ€. λ€μν νλ‘μ ν¨ν΄μ λ§μ€ν°ν¨μΌλ‘μ¨ λ κ²¬κ³ νκ³ μ μ§λ³΄μνκΈ° μ¬μ°λ©° ν¨μ¨μ μΈ μ½λλ₯Ό μμ±ν μ μμ΅λλ€. μ ν¨μ± κ²μ¬, κ°μν, μΆμ λλ κΈ°ν κ³ κΈ κΈ°μ μ ꡬννλ , νλ‘μλ κ°μ²΄ μ κ·Ό λ° μ‘°μ λ°©λ²μ μ μ΄νκΈ° μν μ μ°ν μ루μ μ μ 곡ν©λλ€. νμ μ±λ₯ μν₯μ κ³ λ €νκ³ λμ νκ²½κ³Όμ νΈνμ±μ νμΈνμΈμ. νλ‘μλ νλ μλ°μ€ν¬λ¦½νΈ κ°λ°μμ λ¬΄κΈ°κ³ μμ κ°λ ₯ν λ©ννλ‘κ·Έλλ° κΈ°μ μ κ°λ₯νκ² νλ ν΅μ¬ λꡬμ λλ€.
λ μμ보기
- Mozilla Developer Network (MDN): μλ°μ€ν¬λ¦½νΈ νλ‘μ
- Exploring JavaScript Proxies: Smashing Magazine κΈ°μ¬