ในบทที่แล้วได้กล่าวถึง Proxy ไปแล้ว สำหรับบทนี้จะกล่าวถึงออบเจ็กต์อีกตัวที่มีความเกี่ยวข้องกัน คือ
Reflect
ภาพรวม
Reflect เป็นออบเจ็กต์ชนิดหนึ่งที่ถูกเพิ่มเข้ามาใน ES6 ควบคู่ไปกับ Proxy บางทีก็ถูกใช้คู่กันกับ Proxy
ออบเจ็กต์ Reflect นั้นคล้ายกับ Math ตรงที่เอาไว้เก็บฟังก์ชันเฉยๆ ไม่ได้เอาไว้สร้างอินสแตนซ์ขึ้นมาโดยใช้ new
เหมือนอย่าง Proxy
ประโยชน์ของการใช้ฟังก์ชันใน Reflect นั้นอาจจะนึกภาพยากสักหน่อย เพราะโดยรวมๆแล้วก็คือ
แค่แทนรูปแบบไวยากรณ์เดิมที่ใช้อยู่เดิมตามปกติ แค่เพิ่มความสามารถหรือทำให้ผลต่างกันออกไป
ฟังก์ชันใน Reflect มีดังนี้
ฟังก์ชัน |
ความสามารถ |
Reflect.get(trgt, prop, receiver) |
ดูค่าพรอเพอร์ตี |
Reflect.set(trgt, prop, val, receiver) |
ตั้งค่าพรอเพอร์ตี |
Reflect.has(trgt, prop) |
เหมือนใช้ in เพื่อดูว่ามีพรอเพอร์ตีนี้อยู่หรือเปล่า |
Reflect.deleteProperty(trgt, prop) |
เหมือนใช้ delete เพื่อลบ |
Reflect.apply(trgt, this, args) |
เรียกใช้เป็นฟังก์ชัน |
Reflect.construct(trgt, args) |
สร้างอินสแตนซ์ใหม่ขึ้นโดยใช้ new |
Reflect.isExtensible(trgt) |
เหมือนใช้ Object.isExtensible() |
Reflect.preventExtensions(trgt) |
เหมือนใช้ Object.preventExtensions() |
Reflect.getOwnPropertyDescriptor(trgt, prop) |
เหมือนใช้ Object.getOwnPropertyDescriptor() |
Reflect.defineProperty(trgt, prop, desc) |
เหมือนใช้ Object.defineProperty() |
Reflect.getPrototypeOf(trgt) |
เหมือนใช้ Object.getPrototypeOf() |
Reflect.setPrototypeOf(trgt) |
เหมือนใช้ Object.setPrototypeOf() |
Reflect.ownKeys(trgt) |
เหมือนใช้ Object.keys() หรือ Object.getOwnPropertyNames() หรือ Object.getOwnPropertySymbols() |
ในที่นี้
- trgt: ตัวออบเจ็กต์
- prop: พรอเพอร์ตี
- val: ค่าที่ป้อนให้
- receiver: ตัวออบเจ็กต์ตัวรับ
- args: อาร์กิวเมนต์
จะเห็นว่าโดยรวมแล้วก็เหมือนกับ Proxy เทียบเคียงเข้าคู่กันได้
และความสามารถของฟังก์ชันเหล่านี้คล้ายกับฟังก์ชันที่เดิมมีอยู่ก่อนแล้ว หรือรูปแบบไวยากรณ์การเขียนบางอย่าง
เช่น Reflect.get ก็เหมือนเวลาที่เราดูค่าของพรอเพอร์ตีในออบเจ็กต์ trgt[prop]
alert(Reflect.get(rotbanthuk,"namman"));
เหมือนกับ
alert(rotbanthuk["namman"]);
หรืออย่าง Reflect.set ก็เหมือนเวลาแทนค่าเข้าไปให้พรอเพอร์ตี trgt[prop] = val
Reflect.set(rotbanthuk,"namman",100)
เหมือนกับ
rotbanthuk["namman"] = 100;
แต่จะเห็นว่าใน Reflect.get กับ Reflect.set มีพารามิเตอร์อีกตัวหนึ่ง คือ receiver
ซึ่งตรงนี้เป็นจุดที่จะทำให้เกิดผลที่ต่างไปจากตอนใช้แค่ trgt[prop] ธรรมดาได้
รายละเอียดตรงนี้จะมาพูดถึงในส่วนที่อธิบายฟังก์ชัน Reflect.get
Reflect.get
Reflect.get ใช้สำหรับตั้งค่าพรอเพอร์ตีให้ออบเจ็กต์ ค่านั้นอาจเป็นพรอเพอร์ตีที่มีค่าอยู่ธรรมดา
หรือพรอเพอร์ตีที่กำหนดค่าด้วย get ก็ได้ ตัวอย่างการใช้เช่น
let senbei = {
nori: "โนริ",
get maki(){
return "มากิ"
}
}
alert(Reflect.get(senbei,"nori")) // เหมือน senbei["nori"]
// ได้ โนริ
alert(Reflect.get(senbei,"maki")) // เหมือน senbei["maki"]
// ได้ มากิ
ถ้าดูจากแค่นี้ก็ไม่ต่างจากการใช้วิธีเดิมในการดูค่าในออบเจ็กต์
แต่ฟังก์ชัน Reflect.get() มีอาร์กิวเมนต์ตัวที่ ๓ เพิ่มเติมมานั่นคือ ตัวออบเจ็กต์ตัวรับ (receiver)
ตัวรับที่ว่านี้มีไว้ทำอะไร ที่จริงเป็นอะไรที่นึกภาพได้ยากสักหน่อย
ความแตกต่างจะเห็นได้ชัดกับฟังก์ชันที่มีพรอเพอร์ตีที่กำหนดค่าด้วย get
เริ่มจากลองดูจากตัวอย่างดังนี้
let ika = {
su: "อิ",
shi: "กะ",
get sushi() {
return "๛" + this.su + this.shi + "หมึก";
}
};
let ebi = {
su: "เอ",
shi: "บิ",
get sushi() {
return "๏" + this.su + this.shi + "กุ้ง";
}
};
alert(ebi["sushi"]) // ได้ ๏เอบิกุ้ง
alert(Reflect.get(ebi,"sushi",ika)) // ได้ ๏อิกะกุ้ง
alert(ika["sushi"]) // ได้ ๛อิกะหมึก
alert(Reflect.get(ika,"sushi",ebi)) // ได้ ๛เอบิหมึก
ในที่นี้จะเห็นว่าเมื่อใน Reflect.get ใส่ออบเจ็กต์หลัก (อาร์กิวเมนต์ตัวแรก) กับออบเจ็กต์ตัวรับ (อาร์กิวเมนต์ตัวที่ ๓)
ต่างกัน พรอเพอร์ตีที่ใช้ get จะใช้ฟังก์ชันในตัวหลัก (ในที่นี้คือ .sushi) แต่จะใช้ค่าพรอเพอร์ตีจากตัวรับ (ในที่นี้คือ
.su กับ .shi)
แต่ว่าโอกาสแบบไหนที่จะต้องมาทำเรื่องอะไรที่ยุ่งยากแบบนี้ ก็เป็นเรื่องที่น่าจะนึกภาพยากยิ่งไปอีก
ตัวอย่างการใช้ที่อาจเห็นได้ก็คือ กรณีที่ใช้กับ Proxy และจะเห็นผลที่ได้ที่เกิดต่างกันระหว่างใช้ Reflect.get
กับไม่ใช้
// ตัวออบเจ็กต์เดิม
let unagi = {
kaba: "คาบะ",
yaki: "ยากิ",
get kabayaki() {
return this.kaba + this.yaki;
}
};
// Proxy กรณีที่ใช้ Reflect.get และกำหนด receiver
let unagidon = new Proxy(unagi, {
get: function (trgt, prop, receiver) {
return "#*" + Reflect.get(trgt, prop, receiver) + "~!";
}
});
alert(unagidon.kabayaki); // ได้ #*#*คาบะ~!#*ยากิ~!~!
// Proxy กรณีที่ใช้การเข้าถึงพรอเพอร์ตีแบบธรรมดา
let unagipai = new Proxy(unagi, {
get: function (trgt, prop) {
return "#*" + trgt[prop] + "~!";
}
});
alert(unagipai.kabayaki); // ได้ #*คาบะยากิ~!
ในที่นี้ unagipai และ unagidon ต่างก็เป็น Proxy แต่ต่างกันตรงที่ภายใน unagipai ใช้วิธดั้งเดิม ในขณะที่ unagidonใน
Reflect.get
ที่จริงแล้ว get ใน Proxy เองก็มีออบเจ็กต์ receiver และในที่นี้ก็จะแทนตัวพร็อกซีเอง ดังนั้นเมื่อใส่ receiver นี้ให้
Reflect.get แบบนี้ออบเจ็กต์ตัวรับนี้ก็จะแทนตัวพร็อกซี
ในขณะที่กรณีที่ไม่กำหนด receiver ออบเจ็กต์ตัวรับก็จะเป็นตัวเดียวกับ trgt
ซึ่งในที่นี้คือออบเจ็กต์หลักคือตัวออบเจ็กต์เอง
ถ้าแทนตัวออบเจ็กต์เอง เวลาที่เอาค่าก็จะไม่มีการเติม "#*" และ "~!" หน้าหลัง แต่ถ้าแทนตัวพร็อกซี
จะมีการเติมไปก่อนที่จะมาถูกเติมอีกที ผลที่ได้ก็คือเห็น "#*" และ "~!" ซ้อนกัน ๒ ชั้นแบบนี้
จะใช้แบบไหนก็อาจขึ้นอยู่กับว่าต้องการให้ผลลัพธ์เป็นแบบไหน
ถ้าสามารถเข้าใจแล้วก็จะเลือกใช้ได้ถูกต้องและเข้าใจความแตกต่างที่จะเกิดขึ้น
Reflect.set
Reflect.set ใช้สำหรับป้อนค่าให้พรอเพอร์ตีในออบเจ็กต์ ตัวอย่างเช่น
let umi = {
saba: "o_o",
set same(val){
this.saba += val;
}
};
alert(umi.saba); // ได้ o_o
Reflect.set(umi,"saba","ซาบะ");
// เหมือน umi["saba"] = "ซาบะ"
alert(umi.saba); // ได้ ซาบะ
Reflect.set(umi,"same","ซาเมะ");
// เหมือน umi["sama"] = "ซาเมะ"
alert(umi.saba); // ได้ ซาบะซาเมะ
Reflect.set ก็มีออบเจ็กต์ receiver เช่นเดียวกับ Reflect.get ลองดูตัวอย่างการใช้กับ Proxy
let ramen = {
soba: "",
set udon(val) {
this.soba = "โซบะ" + val + "฿";
}
};
// Proxy กรณีที่ใช้ Reflect.set ในการป้อนค่าให้พรอเพอร์ตี โดยกำหนด receiver
let shouyuramen = new Proxy(ramen, {
set(trgt, prop, val, receiver) {
Reflect.set(trgt, prop, "~" + val + "~", receiver);
}
})
shouyuramen["udon"] = "อุดง";
alert(shouyuramen.soba); // ได้ ~โซบะ~อุดง~฿~
// Proxy กรณีที่ใช้การป้อนค่าให้พรอเพอร์ตีแบบธรรมดา
let shioramen = new Proxy(ramen, {
set(trgt, prop, val) {
trgt[prop] = "~" + val + "~";
}
})
shioramen["udon"] = "อุดง";
alert(shioramen.soba); // ได้ โซบะ~อุดง~฿
เช่นเดียวกับกรณี Reflect.get อาจดูเป็นความแตกต่างที่เข้าใจได้ยากสักหน่อย แต่ก็ทำให้เกิดความแตกต่างได้
Reflect.has
ฟังก์ชัน Reflect.has ใช้ดูว่ามีพรอเพอร์ตีชื่อนั้นอยู่ในออบเจ็กต์นั้นหรือไม่ เหมือนเวลาใช้ in
let umineko = {
umi: "อุมิ",
get neko() {
return "เนโกะ"
}
}
alert(Reflect.has(umineko,"umi")); // ได้ true
// เหมือน alert("umi" in umineko);
alert(Reflect.has(umineko,"neko")); // ได้ true
// เหมือน alert("neko" in umineko);
เช่นเดียวกับเมื่อใช้ in ถ้าใช้กับที่ไม่ใช่ออบเจ็กต์จะเกิดข้อผิดพลาด แม้ว่าจะขึ้นข้อความเตือนต่างกัน
alert("a" in "baka"); // ได้ TypeError: cannot use 'in' operator to search for "a" in "baka"
alert(Reflect.has("baka","a")); // ได้ TypeError: `target` argument of Reflect.has must be an object, got "baka"
Reflect.has นั้นไม่ได้มีพารามิเตอร์เพิ่มเติมอย่างเช่น receiver ให้เกิดความแตกต่างจากวิธีเดิมเหมือนอย่าง get หรือ set
แค่ทำให้รูปแบบการเขียนดูเป็นอยู่ในรูปแบบฟังก์ชัน
Reflect.deleteProperty
ฟังก์ชัน Reflect.deleteProperty ใช้ลบพรอเพอร์ตีออกจากออบเจ็กต์ เหมือนเวลาใช้ delete
ตัวอย่างการใช้
let kaki = { awabi: 'uni' };
alert(kaki.awabi); // ได้ uni
Reflect.deleteProperty(kaki, 'awabi');;
// เหมือนกับ delete kaki.awabi;
alert(kaki.awabi); // ได้ undefined
เช่นเดียวกับ Reflect.has เมื่อใช้แล้วทำให้การเขียนอยู่ในรูปของการใช้ฟังก์ชัน
Reflect.apply
ฟังก์ชัน Reflect.apply ใช้แทนเวลาเรียกใช้ตัวออบเจ็กต์ในฐานะฟังก์ชัน คือเหมือนการเติมวงเล็บ () หรือใช้เมธอด .apply
ในตัวออบเจ็กต์นั้น (เกี่ยวกับ apply มีเขียนถึงใน
บทที่ ๑๙)
ตัวอย่างเช่น
function kamaboko(kama, boko) {
alert(kama + boko);
}
Reflect.apply(kamaboko, null, ["คามะ", "โบโกะ"]); // ได้ คามะโบโกะ
// เหมือนกับ kamaboko("คามะ","โบโกะ");
// หรือ kamaboko.apply(null, ["คามะ", "โบโกะ"]);
อาร์กิวเมนต์ตัวที่ ๒ ของ Reflect.apply ปกติเป็นตัวที่ต้องการให้แทน this ในที่นี้ไม่มีจึงใส่ null
หรืออะไรไปก็ได้
ตัวอย่างที่มีการใส่อาร์กิวเมนต์ตัวที่ ๒ เช่น
function takoyaki(tako, yaki) {
alert(this + '~' + tako + '*' + yaki + '~');
}
Reflect.apply(takoyaki, "โคโนะ", ["ทาโกะ", "ยากิ"]); // ได้ โคโนะ~ทาโกะ*ยากิ~
// เหมือนกับ takoyaki.apply("โคโนะ", ["ทาโกะ", "ยากิ"]);
Reflect.construct
ฟังก์ชัน Reflect.construct ใช้สร้างอินสแตนซ์ของออบเจ็กต์ขึ้นมา เหมือนเวลาใช้ new เช่น
class Momo {
constructor(mochi, momiji) {
this.mochi = mochi;
this.momiji = momiji;
}
}
let momo = Reflect.construct(Momo, ["โมจิ", "โมมิจิ"]);
// เหมือนกับ let momo = new Momo("โมจิ", "โมมิจิ");
alert(momo.mochi); // ได้ โมจิ
สรุป
จากที่ยกตัวอย่างมาก็คงยังนึกภาพได้ยากว่า Reflect มีไว้ทำอะไร จะมีโอกาสได้ใช้ตอนไหน ส่วนมากวิธีที่มีอยู่เดิมก็ใช้การได้
แถมดูจะเขียนยาวขึ้นด้วย
การมีอยู่ของ Reflect อาจพอสรุปได้ประมาณนี้
- ทำให้คำสั่งบางอย่างที่เดิมทีอยู่ในรูปไวยากรณ์ภาษาสามารถเขียนให้อยู่ในรูปของฟังก์ชันได้ เช่น in ใช้ฟังก์ช้น
Reflect.has แทน และ delete ใช้ฟังก์ชัน Reflect.deleteProperty แทน
- ฟังก์ชันเช่น get กับ set มีพารามิเตอร์ออบเจ็กต์ตัวรับ (receiver) เข้ามา
เป็นความสามารถเพิ่มเติมที่เดิมที่วิธีดั้งเดิมไม่มี ทำให้เกิดผลที่ต่างออกไปได้
- ฟังก์ชันบางส่วนก็เทียบเคียงได้กับฟังก์ชันที่มีอยู่แล้วใน Object เช่น Reflect.getOwnPropertyDescriptor()
จะเหมือนกับ Object.getOwnPropertyDescriptor() ใช้แทนกันได้
- ส่วนที่ซ้ำกับฟังก์ชันที่เดิมมีอยู่แล้วก็อาจมีความสามารถเปลี่ยนแปลงต่างไปเล็กน้อย ไม่ได้เหมือนกันทีเดียว
เช่น ฟังก์ชัน Reflect.defineProperty นั้นเหมือนกับ Object.defineProperty แต่ข้อแตกต่างก็คือ Object.defineProperty
ถ้าไปตั้งค่าพรอเพอร์ตีที่ไม่สามารถตั้งได้จะทำให้เกิดข้อผิดพลาดขึ้น แต่ถ้า Reflect.defineProperty จะแค่คืนค่า false
- ฟังก์ชันใน Reflect ทั้งหมดจะเหมือนกับที่ตั้งได้ใน Proxy มักจะเอาไว้ใช้ควบคู่กันไป อย่างเช่นในตัวอย่างที่ใช้ใน
get กับ set