ในบทนี้จะพูดถึงสิ่งที่เรียกว่า
พร็อกซี (proxy) ในจาวาสคริปต์
การสร้างพร็อกซี
พร็อกซีเป็นออบเจ็กต์ชนิดหนึ่งที่เอาไว้ควบคุมพฤติกรรมต่างๆของออบเจ็กต์ในขณะที่ทำอะไรบางอย่าง เช่น ดูค่าพรอเพอร์ตี,
ตั้งค่าพรอเพอร์ตี, ลบพรอเพอร์ตี, ฯลฯ
เนื่องจากว่าพร็อกซีก็เป็นออบเจ็กต์ชนิดหนึ่ง วิธีการสร้างก็ใช้ new
let proxy = new Proxy(เป้าหมาย, ตัวรับมือ)
ค่าที่ต้องใส่ในวงเล็บมี ๒ อย่าง
- เป้าหมาย (target) คือออบเจ็กต์ที่ต้องการจะให้ออบเจ็กต์พร็อกซีตัวนี้ควบคุม
- ตัวรับมือ (handler) คือออบเจ็กต์ที่เขียนเมธอดไว้ เป็นตัวกำหนดว่าจะควบคุมอย่างไร
ชื่อเมธอดอะไรเอาไว้ควบคุมอะไรได้ถูกกำหนดไว้แล้ว ดังนี้
ชื่อเมธอด |
ค่าที่ต้องคืนกลับ |
จังหวะที่จะไปควบคุมเป้าหมาย |
get(trgt, prop) |
ค่าใดๆก็ได้ |
เมื่อดูค่าพรอเพอร์ตี |
set(trgt, prop, val) |
true / false |
เมื่อตั้งค่าพรอเพอร์ตี |
has(trgt, prop) |
true / false |
เมื่อใช้ตัวดำเนินการ in |
deleteProperty(trgt, prop) |
true / false |
เมื่อใช้ delete |
apply(trgt, this, args) |
ค่าใดๆก็ได้ |
เมื่อเรียกใช้แบบฟังก์ชัน |
construct(trgt, args) |
อ็อบเจ็กต์ |
เมื่อใช้ new สร้างอินสแตนซ์ |
ในที่นี้
- trgt: ออบเจ็กต์เป้าหมาย
- prop: ชื่อพรอเพอร์ตี
- val: ค่าที่จะตั้งให้
นอกจากนี้ก็มีเมธอดเหล่านี้ ซึ่งชื่อเหมือนกับเมธอดที่มีอยู่ในตัวออบเจ็กต์ ซึ่งเคยเขียนถึงไปแล้ว ถ้าใส่เมธอดชื่อเหล่านี้ลงไปในพร็อกซีก็จะเป็นการไปควบคุมเมธอดเหล่านี้ในออบเจ็กต์
isExtensible |
บทที่ ๒๕
|
preventExtensions |
getOwnPropertyDescriptor |
บทที่ ๒๖
|
defineProperty |
getPrototypeOf |
setPrototypeOf |
การสร้างตัวรับมือก็เขียนในลักษณะประมาณนี้
handler = {
get(target, prop){
//...
},
set(target, prop, val){
//...
},
has(target, prop){
//...
},
ฯลฯ(){
//...
},
//...
}
ต่อไปจะยกตัวอย่างการใช้กับแต่ละเมธอด
get
ปกติแล้วเวลาดูค่าของพรอเพอร์ตีไหนแล้วถ้าค่านั้นไม่มีอยู่ก็จะได้ undefined
แต่เราสามารถสร้างออบเจ็กต์ที่จะทำให้พฤติกรรมตรงนี้เปลี่ยนไปได้ เช่นให้คืนค่าอะไรบางอย่างมาแทน
let prox = new Proxy(
{},
{
get: function (trgt, prop) {
if(prop in trgt) return trgt[prop];
else return "ที่นี่ไม่มีสิ่งที่ท่านต้องการ ณ เวลานี้";
}
}
);
prox.a = "เธอมีฉันอยู่ข้างใน";
prox.b = "ฉันอยู่ตรงนี้เมื่อคุณต้องการ";
alert(prox.a); // ได้ เธอมีฉันอยู่ข้างใน
alert(prox.b); // ได้ ฉันอยู่ตรงนี้เมื่อคุณต้องการ
alert(prox.h); // ได้ ที่นี่ไม่มีสิ่งที่ท่านต้องการ ณ เวลานี้
เมธอด get ในที่นี้จะรับค่ามา ๒ อย่างคือ ออบเจ็กต์ตัวนั้น (trgt) และ ชื่อพรอเพอร์ตีที่ถูกดูค่า (prop)
prop in trgt ก็คือดูว่ามีพรอเพอร์ตีนี้อยู่ในออบเจ็กต์นี้หรือเปล่า ถ้ามีก็คืนค่าพรอเพอร์ตีนั้น trgt[prop] ไปตามปกติ
แต่ถ้าไม่มีก็จะคืนค่าที่กำหนดไว้ใน else ด้านล่าง
อีกตัวอย่างหนึ่ง เช่นถ้าต้องการให้ค่าที่ออกมามีกรอบสวยๆคร่อมอาจเขียนดังนี้
let prox = new Proxy(
{x: "XXX"},
{
get: function (trgt, prop) {
return "*@~" + trgt[prop] + "=$^"
}
}
);
alert(prox.x); // ได้ *@~XXX=$^
alert(prox.y); // ได้ *@~undefined=$^
set
เมธอด set จะทำงานเมื่อเราใช้ = เพื่อป้อนค่าให้ออบเจ็กต์ โดยปกติแล้วพอตั้งค่าให้
ค่าก็จะถูกป้อนให้กับพรอเพอร์ตีนั้นตามค่าที่ใส่ไป แต่แราสามารถเปลี่ยนแปลงพฤติกรรมตรงนี้ได้ เช่น
กำหนดข้อจำกัดให้ต้องไม่เกิน 100 ไม่งั้นจะเกิดข้อผิดพลาด
let pokemon = new Proxy(
{},
{
set: function (trgt, prop, val) {
if(val>100) return false;
else {
trgt[prop] = val;
return true;
}
}
}
);
pokemon.lv = 99;
alert(pokemon.lv); // ได้ 99
pokemon.lv = 101; // ได้ TypeError: proxy set handler returned false for property '"lv"'
ในที่นี้ val คือค่าที่กำลังจะถูกตั้งให้ (ค่าที่อยู่ข้างหลังเครื่องหมาย =) และเมธอด set จะต้องคืนค่า true
ไม่เช่นนั้นก็จะเกิดข้อผิดพลาดขึ้นเป็น TypeError ดังที่เห็น
ในที่นี้กำหนดให้ถ้า val เกิน 100 ก็คืนค่า false ออกไป ก็จะทำให้เกิดข้อผิดพลาดขึ้น แต่ถ้าไม่เกินก็ตั้งค่าให้ตามปกติ
แล้วคืนค่า true
หรือเช่นถ้าต้องการให้เวลาตั้งพรอเพอร์ตีการแปลงข้อมูลให้เป็นไปตามที่ต้องการก่อนเก็บ เช่น
ถ้าจะแปลงค่าที่ใส่เข้ามาเป็นจำนวนเต็มทั้งหมดก็อาจเขียนแบบนี้
let pikachu = new Proxy(
{},
{
set: function (trgt, prop, val) {
trgt[prop] = parseInt(val);
return true;
}
}
);
pikachu.lv = 77.777;
alert(pikachu.lv); // ได้ 77
pikachu.lv = "55 มั้ง";
alert(pikachu.lv); // ได้ 55
has
ตัวดำเนินการ in ปกติใช้สำหรับดูค่าว่ามีพรอเพอร์ตีที่ต้องการอยู่ในออบเจ็กต์นั้นหรือไม่ ถ้ามีก็จะได้ true ถ้าไม่ก้ได้
false
เมธอด has จะควบคุมออบเจ็กต์เวลาที่ใช้ in เราสามารถแก้ให้เป็นออบเจ็กต์ที่แปลกๆที่ให้ผลตรงกันข้าม เช่นให้ false เมื่อมี
ให้ true เมื่อไม่มี แบบนี้ก็ได้
let protozoa = new Proxy(
{cilia: 1000000},
{
has: function (trgt, prop) {
if(prop in trgt) return false
else return true;
}
}
);
alert("cilia" in protozoa); // ได้ false
alert("flagella" in protozoa); // ได้ true
deleteProperty
ปกติถ้าใช้คำสั่ง delete พรอเพอร์ตีถูกลบก็จะหายไป
เหมือนกับที่คนเราจะตายเมื่อถูกฆ่าและคนเราจะตายเมื่อถูกฆ่า
เราสามารถตั้งเมธอด deleteProperty เพื่อกำหนดพฤติกรรมเวลาทำการลบพรอเพอร์ตีให้แปลกไปได้
เช่นให้มีการเตือนโดยกล่องข้อความเมื่อทำการลบ
let proxa = new Proxy(
{propa: 555},
{
deleteProperty(trgt, prop) {
alert("จะลบละนะ")
return true;
}
}
);
delete proxa.propa;
delete ก็ต้องคืนค่า true หรือ false เช่นเดียวกับ set ถ้าไม่คืนค่า true ก็จะเกิดข้อผิดพลาด
เช่นอาจลองทำให้ออบเจ็กต์เกิดข้อผิดพลาดเสมอเมื่อทำการลบโดยเขียนแบบนี้
let proxa = new Proxy(
{ propa: 666 },
{
deleteProperty(trgt, prop) {
return false;
}
}
);
delete proxa.propa; // ได้ TypeError: can't delete property '"propa"': proxy deleteProperty handler returned false
apply
เมธอด apply จะควบคุมออบเจ็กต์เวลาที่ถูกเรียกในฐานะฟังก์ชัน
ตัวอย่างเช่น สร้างฟังก์ชันที่เดิมทีแค่ให้ผลออกมา จากนั้นใช้พร็อกซีเติมให้เด้งหน้าต่างแสดงผลออกมาในขณะเดียวกันด้วย
let funcprox = new Proxy(
function(a,b) { return a*b},
{
apply(trgt, thisarg, args) {
let phonkhun = trgt(...args);
alert("ผลคูณคือ " + phonkhun);
return phonkhun;
}
}
);
funcprox(5,7); // ได้ ผลคูณคือ 35
args คือแถวลำดับของอาร์กิวเมนต์ที่รับเข้ามา
ส่วน thisarg คือสิ่งที่แทนตัวออบเจ็กต์ ถ้าหากฟังก์ชันนั้นถูกเรียกในฐานะเมธอดของออบเจ็กต์
ตัวอย่างเช่น สร้างเมธอดที่เอาเลขมาคูณกัน แล้วก็ใช้พร็อกซีเติมให้ค่าที่ได้นั้นไปคูณกับพรอเพอร์ตี x ในตัวออบเจ็กต์นั้นอีกด้วยก่อนจะคืนเป็นผลลัพธ์ออกมา
let funcprox = new Proxy(
function (a, b) { return a * b },
{
apply(trgt, thisarg, args) {
let phonkhun = trgt(...args);
return phonkhun * thisarg.x;
}
}
);
let obj = { x: 10, funcprox };
alert(obj.funcprox(7, 9)); // ได้ ผลคูณคือ 630
construct
เมธอด construct จะควบคุมออบเจ็กต์ที่เป็นคอนสตรักเตอร์เวลาที่ใช้เมธอด new
ตัวอย่างเช่น เติมให้มีนกรอบข้อความเด้งขึ้นมาตอนที่สร้าง
let Phukla = new Proxy(
class {
constructor(chue, lv) {
this.chue = chue;
this.lv = lv;
}
},
{
construct(trgt, args) {
let obj = new trgt(...args);
alert("ผู้กล้า '" + obj.chue + "' lv " + obj.lv + " ได้ถือกำเนิดขึ้นแล้ว");
return obj;
}
}
);
let phukla = new Phukla("ได", 100); // ได้ ผู้กล้า 'ได' lv 100 ได้ถือกำเนิดขึ้นแล้ว
นอกจากนี้ ยังอาจทำเหมือนเป็นการสร้าง constructor ใหม่ให้คลาสเปล่าๆได้ เช่น
let ProxoPokemon = new Proxy(
class { },
{
construct(trgt, args) {
let [chue, lv] = args;
return { chue, lv };
}
}
);
let ponyta = new ProxoPokemon("โพนีตา", 21);
alert(ponyta.chue); // ได้ โพนีตา
alert(ponyta.lv); // ได้ 21
ทั้งหมดนี้ก็คือตัวอย่างส่วนหนึ่งของการใช้พร็อกซี หากเข้าใจแล้วก็สามารถทำให้ออบเจ็กต์มีพฤติกรรมเปลี่ยนไป หรือเพิ่มความสามารถให้ออบเจ็กต์ได้ตามที่ต้องการ