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



javascript เบื้องต้น บทที่ ๔๐: พร็อกซี
เขียนเมื่อ 2020/02/08 07:01
แก้ไขล่าสุด 2021/09/28 16:42


ในบทนี้จะพูดถึงสิ่งที่เรียกว่าพร็อกซี (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



ทั้งหมดนี้ก็คือตัวอย่างส่วนหนึ่งของการใช้พร็อกซี หากเข้าใจแล้วก็สามารถทำให้ออบเจ็กต์มีพฤติกรรมเปลี่ยนไป หรือเพิ่มความสามารถให้ออบเจ็กต์ได้ตามที่ต้องการ






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

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

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

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> 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月

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

ไทย

日本語

中文