φυβλαςのβλογ
บล็อกของ phyblas



javascript เบื้องต้น บทที่ ๔๑: รีเฟล็กต์
เขียนเมื่อ 2020/04/20 00:05
แก้ไขล่าสุด 2021/09/28 16:42


ในบทที่แล้วได้กล่าวถึง 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






-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> javascript

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文