JavaScript Proxyãã¿ãŒã³ã§ãªããžã§ã¯ãã®æ¯ãèãã倿Žããæ¹æ³ãæ¢æ±ããŸããæ€èšŒãä»®æ³åã远跡ããã®ä»ã®é«åºŠãªãã¯ããã¯ãã³ãŒãäŸãšå ±ã«åŠã³ãŸãããã
JavaScript Proxyãã¿ãŒã³ïŒãªããžã§ã¯ãã®æ¯ãèããèªåšã«æã
JavaScriptã®Proxyãªããžã§ã¯ãã¯ããªããžã§ã¯ãã«å¯Ÿããåºæ¬çãªæäœãååããã«ã¹ã¿ãã€ãºããããã®åŒ·åãªã¡ã«ããºã ãæäŸããŸãããã®æ©èœã¯ããªããžã§ã¯ãã®æ¯ãèããå¶åŸ¡ããããã®å¹ åºããã¶ã€ã³ãã¿ãŒã³ãé«åºŠãªãã¯ããã¯ãžã®æãéããŸãããã®å æ¬çãªã¬ã€ãã§ã¯ãæ§ã ãªProxyãã¿ãŒã³ãæ¢æ±ããå®è·µçãªã³ãŒãäŸãçšããŠãã®äœ¿ãæ¹ã解説ããŸãã
JavaScript Proxyãšã¯äœãïŒ
Proxyãªããžã§ã¯ãã¯ãå¥ã®ãªããžã§ã¯ãïŒã¿ãŒã²ããïŒãã©ãããããã®æäœãååããŸãããããã®æäœã¯ãã©ãããšããŠç¥ãããããããã£ã®åç §ãä»£å ¥ãåæã颿°åŒã³åºããªã©ãå«ãŸããŸããProxyã䜿çšãããšããããã®æäœã®åãåŸããŸãã¯ä»£ãããšããŠå®è¡ãããã«ã¹ã¿ã ããžãã¯ãå®çŸ©ã§ããŸããProxyã®æ žãšãªãæŠå¿µã¯ãã¡ã¿ããã°ã©ãã³ã°ãã§ãããããã«ããJavaScriptèšèªèªäœã®æ¯ãèããæäœããããšãå¯èœã«ãªããŸãã
Proxyãäœæããããã®åºæ¬çãªæ§æã¯æ¬¡ã®ãšããã§ãã
const proxy = new Proxy(target, handler);
- target: ãããã·åãããå ã®ãªããžã§ã¯ãã
- handler: Proxyãã¿ãŒã²ããã«å¯Ÿããæäœãã©ã®ããã«ååããããå®çŸ©ããã¡ãœããïŒãã©ããïŒãå«ããªããžã§ã¯ãã
äžè¬çãªProxyãã©ãã
ãã³ãã©ãªããžã§ã¯ãã¯ãããã€ãã®ãã©ãããå®çŸ©ã§ããŸãã以äžã¯ãæãäžè¬çã«äœ¿çšããããã®ã§ãã
- 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()
ã®åŒã³åºããååããŸãã
Proxyãã¿ãŒã³ãšãŠãŒã¹ã±ãŒã¹
äžè¬çãªProxyãã¿ãŒã³ãšãããããå®éã®ã·ããªãªã§ã©ã®ããã«é©çšã§ããããæ¢ã£ãŠã¿ãŸãããã
1. æ€èšŒïŒValidationïŒ
æ€èšŒãã¿ãŒã³ã¯ãProxyã䜿çšããŠããããã£ä»£å ¥ã«å¶çŽã匷å¶ããŸããããã¯ããŒã¿ã®æŽåæ§ã確ä¿ããã®ã«åœ¹ç«ã¡ãŸãã
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');
}
}
// å€ãä¿åããããã©ã«ãã®æ¯ãèã
obj[prop] = value;
// æåã瀺ã
return true;
}
};
let person = {};
let proxy = new Proxy(person, validator);
proxy.age = 25; // æå¹
console.log(proxy.age); // åºå: 25
try {
proxy.age = 'young'; // TypeErrorãã¹ããŒ
} catch (e) {
console.log(e); // åºå: TypeError: Age is not an integer
}
try {
proxy.age = -10; // RangeErrorãã¹ããŒ
} catch (e) {
console.log(e); // åºå: RangeError: Age must be a non-negative integer
}
äŸïŒ ãŠãŒã¶ãŒããŒã¿ã®æ€èšŒãå¿ èŠãªeã³ããŒã¹ãã©ãããã©ãŒã ãèããŠã¿ãŸããããProxyã¯ã幎霢ãã¡ãŒã«åœ¢åŒããã¹ã¯ãŒãã®åŒ·åºŠããã®ä»ã®ãã£ãŒã«ãã«å¯Ÿããã«ãŒã«ã匷å¶ããç¡å¹ãªããŒã¿ãä¿åãããã®ãé²ãããšãã§ããŸãã
2. ä»®æ³åïŒé å»¶èªã¿èŸŒã¿ïŒ
ä»®æ³åã¯ãé å»¶èªã¿èŸŒã¿ïŒlazy loadingïŒãšãåŒã°ããã³ã¹ãã®ããããªãœãŒã¹ã®èªã¿èŸŒã¿ãå®éã«å¿ èŠã«ãªããŸã§é å»¶ãããŸããProxyã¯ãå®éã®ãªããžã§ã¯ãã®ãã¬ãŒã¹ãã«ããŒãšããŠæ©èœããããããã£ãã¢ã¯ã»ã¹ããããšãã«ã®ã¿ãããèªã¿èŸŒã¿ãŸãã
const expensiveData = {
load: function() {
console.log('Loading expensive data...');
// æéã®ãããæäœãã·ãã¥ã¬ãŒãïŒäŸïŒããŒã¿ããŒã¹ããã®ãã§ããïŒ
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; // èªã¿èŸŒãã ããŒã¿ãä¿å
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); // åºå: Data: This is the expensive data
});
console.log('Subsequent access...');
lazyData.data.then(data => {
console.log('Data:', data); // åºå: Data: This is the expensive data (ãã£ãã·ã¥ããèªã¿èŸŒã¿)
});
äŸïŒ 倿°ã®è©³çްæ å ±ãé¢é£ã¡ãã£ã¢ãå«ããŠãŒã¶ãŒãããã¡ã€ã«ãæã€å€§èŠæš¡ãªãœãŒã·ã£ã«ã¡ãã£ã¢ãã©ãããã©ãŒã ãæ³åããŠã¿ãŠãã ããããã¹ãŠã®ãããã¡ã€ã«ããŒã¿ãããã«èªã¿èŸŒãã®ã¯éå¹ççã§ããProxyã«ããä»®æ³åã䜿çšãããšãæåã«åºæ¬çãªãããã¡ã€ã«æ å ±ãèªã¿èŸŒã¿ããŠãŒã¶ãŒããããã®ã»ã¯ã·ã§ã³ã«ç§»åãããšãã«ã®ã¿è¿œå ã®è©³çްãã¡ãã£ã¢ã³ã³ãã³ããèªã¿èŸŒãããšãã§ããŸãã
3. ãã°èšé²ãšè¿œè·¡
Proxyã¯ããããã£ã®ã¢ã¯ã»ã¹ã倿Žã远跡ããããã«äœ¿çšã§ããŸããããã¯ãããã°ãç£æ»ãããã©ãŒãã³ã¹ç£èŠã«åœ¹ç«ã¡ãŸãã
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); // åºå: GET name, Alice
proxy.age = 30; // åºå: SET age to 30
äŸïŒ å ±åããã¥ã¡ã³ãç·šéã¢ããªã±ãŒã·ã§ã³ã§ã¯ãProxyã¯ããã¥ã¡ã³ãã³ã³ãã³ãã«å ãããããã¹ãŠã®å€æŽã远跡ã§ããŸããããã«ãããç£æ»èšŒè·¡ãäœæããå ã«æ»ã/ããçŽãæ©èœãæå¹ã«ãããŠãŒã¶ãŒã®è²¢ç®ã«é¢ããæŽå¯ãæäŸã§ããŸãã
4. èªã¿åãå°çšãã¥ãŒ
Proxyã¯ãªããžã§ã¯ãã®èªã¿åãå°çšãã¥ãŒãäœæããå¶çºçãªå€æŽãé²ãããšãã§ããŸããããã¯æ©å¯ããŒã¿ãä¿è·ããã®ã«åœ¹ç«ã¡ãŸãã
const readOnlyHandler = {
set: function(target, prop, value) {
console.error(`Cannot set property ${prop}: object is read-only`);
return false; // setæäœã倱æããããšã瀺ã
},
deleteProperty: function(target, prop) {
console.error(`Cannot delete property ${prop}: object is read-only`);
return false; // deleteæäœã倱æããããšã瀺ã
}
};
let data = { name: 'Bob', age: 40 };
let readOnlyData = new Proxy(data, readOnlyHandler);
try {
readOnlyData.age = 41; // ãšã©ãŒãã¹ããŒ
} catch (e) {
console.log(e); // 'set'ãã©ãããfalseãè¿ãããããšã©ãŒã¯ã¹ããŒãããªãã
}
try {
delete readOnlyData.name; // ãšã©ãŒãã¹ããŒ
} catch (e) {
console.log(e); // 'deleteProperty'ãã©ãããfalseãè¿ãããããšã©ãŒã¯ã¹ããŒãããªãã
}
console.log(data.age); // åºå: 40 (倿Žãªã)
äŸïŒ äžéšã®ãŠãŒã¶ãŒãå£åº§æ å ±ãžã®èªã¿åãå°çšã¢ã¯ã»ã¹æš©ãæã€éèã·ã¹ãã ãèããŠã¿ãŸããããProxyã䜿çšããŠããããã®ãŠãŒã¶ãŒãå£åº§æ®é«ããã®ä»ã®éèŠãªããŒã¿ã倿Žããã®ãé²ãããšãã§ããŸãã
5. ããã©ã«ãå€
Proxyã¯ãååšããªãããããã£ã«å¯ŸããŠããã©ã«ãå€ãæäŸã§ããŸããããã«ãããã³ãŒããç°¡çŽ åããã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'; // ãŸãã¯ä»ã®é©åãªããã©ã«ãå€
}
return Reflect.get(target, prop, receiver);
}
};
let config = { apiUrl: 'https://api.example.com' };
let configWithDefaults = new Proxy(config, defaultValuesHandler);
console.log(configWithDefaults.apiUrl); // åºå: https://api.example.com
console.log(configWithDefaults.timeout); // åºå: Property timeout not found, returning default value. Default Value
äŸïŒ èšå®ç®¡çã·ã¹ãã ã§ã¯ãProxyã¯æ¬ èœããŠããèšå®ã«å¯ŸããŠããã©ã«ãå€ãæäŸã§ããŸããããšãã°ãèšå®ãã¡ã€ã«ãããŒã¿ããŒã¹æ¥ç¶ã®ã¿ã€ã ã¢ãŠããæå®ããŠããªãå ŽåãProxyã¯äºåã«å®çŸ©ãããããã©ã«ãå€ãè¿ãããšãã§ããŸãã
6. ã¡ã¿ããŒã¿ãšã¢ãããŒã·ã§ã³
Proxyã¯ãå ã®ãªããžã§ã¯ãã倿Žããããšãªããã¡ã¿ããŒã¿ãã¢ãããŒã·ã§ã³ããªããžã§ã¯ãã«æ·»ä»ã§ããŸãã
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); // åºå: Introduction to Proxies
console.log(articleWithMetadata.__metadata__.description); // åºå: This is metadata for the object
äŸïŒ ã³ã³ãã³ã管çã·ã¹ãã ã§ã¯ãProxyã¯èšäºã«èè æ å ±ãå ¬éæ¥ãããŒã¯ãŒããªã©ã®ã¡ã¿ããŒã¿ãæ·»ä»ã§ããŸãããã®ã¡ã¿ããŒã¿ã¯ãã³ã³ãã³ãã®æ€çŽ¢ããã£ã«ã¿ãªã³ã°ãåé¡ã«äœ¿çšã§ããŸãã
7. 颿°ã®åå
Proxyã¯é¢æ°åŒã³åºããååãããã°èšé²ãæ€èšŒããŸãã¯ãã®ä»ã®ååŠç/åŸåŠçããžãã¯ã远å ã§ããŸãã
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); // åºå: Calling function with arguments: [5, 3], Function returned: 8
console.log(sum); // åºå: 8
äŸïŒ éè¡ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãProxyã¯ãã©ã³ã¶ã¯ã·ã§ã³é¢æ°ãžã®åŒã³åºããååããåãã©ã³ã¶ã¯ã·ã§ã³ããã°ã«èšé²ãããã©ã³ã¶ã¯ã·ã§ã³ãå®è¡ããåã«äžæ£æ€åºãã§ãã¯ãå®è¡ã§ããŸãã
8. ã³ã³ã¹ãã©ã¯ã¿ã®åå
Proxyã¯ã³ã³ã¹ãã©ã¯ã¿åŒã³åºããååãããªããžã§ã¯ãã®äœæãã«ã¹ã¿ãã€ãºã§ããŸãã
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); // åºå: Creating a new instance of Person with arguments: ['Alice', 28], New instance created: Person { name: 'Alice', age: 28 }
console.log(person);
äŸïŒ ã²ãŒã éçºãã¬ãŒã ã¯ãŒã¯ã§ã¯ãProxyã¯ã²ãŒã ãªããžã§ã¯ãã®äœæãååããäžæã®IDãèªåçã«å²ãåœãŠãããã©ã«ãã®ã³ã³ããŒãã³ãã远å ããã²ãŒã ãšã³ãžã³ã«ç»é²ããããšãã§ããŸãã
é«åºŠãªèæ ®äºé
- ããã©ãŒãã³ã¹ïŒ Proxyã¯æè»æ§ãæäŸããŸãããããã©ãŒãã³ã¹ã®ãªãŒããŒããããçºçãããå¯èœæ§ããããŸããç¹ã«ããã©ãŒãã³ã¹ãéèŠãªã¢ããªã±ãŒã·ã§ã³ã§ã¯ãProxyã䜿çšããå©ç¹ãããã©ãŒãã³ã¹ã³ã¹ããäžåãããšã確èªããããã«ãã³ãŒãã®ãã³ãããŒã¯ãšãããã¡ã€ãªã³ã°ãè¡ãããšãéèŠã§ãã
- äºææ§ïŒ Proxyã¯JavaScriptã«æ¯èŒçæ°ãã远å ãããæ©èœã§ãããããå€ããã©ãŠã¶ã§ã¯ãµããŒããããŠããªãå ŽåããããŸããå€ãç°å¢ãšã®äºææ§ã確ä¿ããããã«ãæ©èœæ€åºãããªãã£ã«ã䜿çšããŠãã ããã
- åãæ¶ãå¯èœãªProxyïŒ
Proxy.revocable()
ã¡ãœããã¯ãåãæ¶ãããšãã§ããProxyãäœæããŸããProxyãåãæ¶ããšããã以äžã®æäœãååãããã®ãé²ããŸããããã¯ãã»ãã¥ãªãã£ããªãœãŒã¹ç®¡çã®ç®çã§åœ¹ç«ã¡ãŸãã - Reflect APIïŒ Reflect APIã¯ãProxyãã©ããã®ããã©ã«ãã®æ¯ãèããå®è¡ããããã®ã¡ãœãããæäŸããŸãã
Reflect
ã䜿çšããããšã§ãProxyã³ãŒããèšèªä»æ§ãšäžè²«ããŠåäœããããšãä¿èšŒãããŸãã
çµè«
JavaScriptã®Proxyã¯ããªããžã§ã¯ãã®æ¯ãèããã«ã¹ã¿ãã€ãºããããã®åŒ·åã§å€çšéãªã¡ã«ããºã ãæäŸããŸããæ§ã ãªProxyãã¿ãŒã³ãç¿åŸããããšã§ãããå ç¢ã§ãä¿å®æ§ãé«ããå¹ççãªã³ãŒããæžãããšãã§ããŸããæ€èšŒãä»®æ³åã远跡ããã®ä»ã®é«åºŠãªãã¯ããã¯ãå®è£ ããå Žåã§ããProxyã¯ãªããžã§ã¯ããžã®ã¢ã¯ã»ã¹ãšæäœãå¶åŸ¡ããããã®æè»ãªãœãªã¥ãŒã·ã§ã³ãæäŸããŸããåžžã«ããã©ãŒãã³ã¹ãžã®åœ±é¿ãèæ ®ããã¿ãŒã²ããç°å¢ãšã®äºææ§ã確ä¿ããŠãã ãããProxyã¯ãçŸä»£ã®JavaScriptéçºè ã®æŠåšåº«ã«ãããéèŠãªããŒã«ã§ããã匷åãªã¡ã¿ããã°ã©ãã³ã°æè¡ãå¯èœã«ããŸãã
ãããªãæ¢æ±
- Mozilla Developer Network (MDN): JavaScript Proxy
- Exploring JavaScript Proxies: Smashing Magazineã®èšäº