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



javascript เบื้องต้น บทที่ ๒๖: การสร้างและกำหนดรายละเอียดพรอเพอร์ตีในออบเจ็กต์
เขียนเมื่อ 2019/08/07 07:24
แก้ไขล่าสุด 2021/09/28 16:42


การใส่พรอเพอร์ตีพร้อมกำหนดคุณสมบัติ

พรอเพอร์ตีในตัวออบเจ็กต์นั้นโดยทั่วไปที่สร้างโดยวิธีทั่วไปจะสามารถแก้ไขค่าได้ ลบทิ้งได้ และสามารถถูกวนซ้ำเมื่อใช้ for๛in ได้

แต่ถ้าสร้างพรอเพอร์ตีโดยใช้ฟังก์ชัน Object.defineProperty หรือ Object.defineProperties สามารถได้พรอเพอร์ตีที่มีคุณสมบัติต่างกันออกไปจากนี้ได้

เมื่อสร้างด้วยวิธีนี้ มีค่าที่กำหนดให้ตัวพรอเพอร์ตีได้ดังนี้

  ความหมาย ค่าตั้งต้น
value ค่า undefined
writable แก้ค่าได้หรือไม่ false
enumerable ไล่ใน for๛in ได้หรือไม่ false
configurable ลบได้หรือไม่ false

ในการตั้งค่าตัวพรอเพอร์ตีตัวเดียวให้ออบเจ็กต์จะใช้ Object.defineProperty

ตัวอย่าง ลองสร้างออบเจ็กต์ krapaotang (กระเป๋าตังค์) มีพรอเพอร์ตีคือ ngoen (เงิน)
var krapaotang = {};

Object.defineProperty(krapaotang, "ngoen", {
  value: 0,
  writable: true,
  enumerable: false,
  configurable: false
});


แบบนี้ก็จะได้พรอเพอร์ตี ngoen ที่ปรับแก้ค่าได้แต่ไม่สามารถลบได้ และไม่ปรากฏใน for๛in

ค่า value หากไม่ใส่จะได้ undefined ส่วนค่านอกจาก value นั้นถ้าหากไม่ใส่ค่าอะไรไปก็จะเป็น false ซึ่งเป็นค่าตั้งต้น

ดังนั้นเขียนแบบนี้ก็ได้ผลเหมือนกัน
Object.defineProperty(krapaotang, "ngoen", {
  value: 0,
  writable: true
});

หากต้องการตั้งค่าพร้อมกันหลายๆตัวอาจใช้ Object.defineProperties เช่นคราวนี้ใส่พรอเพอร์ตีเพิ่มให้กระเป๋าตังค์ไปอีก ๒ ตัวคือ kwang (กว้าง) และ sung (สูง) ให้ enumerable เป็น true
Object.defineProperties(krapaotang, {
  kwang: {
    value: 10,
    enumerable: true
  },
  sung: {
    value: 5,
    enumerable: true
  }
});

ลองทดสอบโดยใช้ for๛in ดู
s = [];
for (var p in krapaotang) {
  s.push(p + "=" + krapaotang[p]);
}
alert(s); // ได้ kwang=10,sung=5

จะเห็นว่าออกมาแค่ kwang กับ sung ส่วน ngoen ตั้ง enumerable เป็น false เลยไม่ออกมา

จากนั้นลองแก้ค่าและลบค่าดูจะเห็นว่า kwang กับ sung แก้ไม่ได้ ลบไม่ได้เพราะตั้ง writable กับ configurable เป็น false (ไม่ได้ตั้ง จึงเป็นค่าตั้งต้น คือ false)
krapaotang.kwang = 12;
alert(krapaotang.kwang); // ได้ 10
delete krapaotang.sung;
alert(krapaotang.sung); // ได้ 5
krapaotang.ngoen = 588;
alert(krapaotang.ngoen); // ได้ 588


การดูคำอธิบายคุณสมบัติพรอเพอร์ตี

หากใช้ฟังก์ชัน Object.getOwnPropertyDescriptor จะได้ออบเจ็กต์ที่มีพรอเพอร์ตีเป็นค่าทั้ง ๔ ที่อธิบายตัวฟังก์ชัน
krapaotang.ngoen = 588;
var desc = Object.getOwnPropertyDescriptor(krapaotang, "sung");
alert(desc.writable); // ได้ false

หากนำมาไล่เรียงด้วย for ก็จะสามารถรู้สมบัติในพรอเพอร์ตีทั้งหมดที่ตั้งไว้ได้
var s = ["==ngoen=="];
var desc = Object.getOwnPropertyDescriptor(krapaotang, "ngoen");
for (var d in desc) {
  s.push("  " + d + ": " + desc[d]);
}
s.push("", "==kwang==");
var desc = Object.getOwnPropertyDescriptor(krapaotang, "kwang");
for (var d in desc) {
  s.push("  " + d + ": " + desc[d]);
}
alert(s.join("\n"));

ได้
==ngoen==
  value: 588
  writable: true
  enumerable: false
  configurable: false

==kwang==
  value: 10
  writable: false
  enumerable: true
  configurable: false



การสร้างออบเจ็กต์พร้อมกำหนดคุณสมบัติของพรอเพอร์ตี

หากต้องการสร้างพรอเพอร์ตีขึ้นมาพร้อมกับกำหนดลักษณะทั้ง ๔ นี้ไปด้วยอาจใช้ฟังก์ชัน Object.create

รูปแบบการใช้
var ชื่อตัวแปรออบเจ็กต์ = Object.create(
  โพรโทไทป์,
  {
    คีย์ 1: {
      value: ...,
      writable: ...,
      configurable, ...,
      enumerable: ...
    },
    คีย์ 2: {
      value: ...,
      writable: ...,
      configurable, ...,
      enumerable: ...
    },
    ...
  }
);

เช่น
var kratiknam = Object.create(
  { yiho: "zojirushi" },
  {
    khwamchu: {
      value: 1500,
      enumerable: true
    },
    minam: {
      value: 1100,
      writable: true
    }
  }
);

ตรงส่วนโพรโทไทป์นั้นคือออบเจ็กต์ที่เดิมมีอยู่แล้ว เอาไว้ใส่พรอเพอร์ตีที่ไม่ได้ต้องการตั้งค่าอะไรเป็นพิเศษ คือต้องการให้แก้ค่าได้ ลบค่าได้ วนใน for ได้ตามปกติ

เพียงแต่ว่าค่าที่ใส่ตรงนี้จะถือว่าเป็นพรอเพอร์ตีในโพรโทไทป์ ไม่ใช่พรอเพอร์ตีในตัวออบเจ็กต์นั้นเอง ทำให้หากค้นหาพรอเพอร์ตีในตัวออบเจ็กต์ก็จะไม่ปรากฏ เช่นเวลาใช้เมธอด .hasOwnProperty หรือฟังก์ชัน Object.getOwnPropertyNames กับ Object.keys ซึ่งกำลังจะเขียนถึงต่อไป



การดูคีย์ของพรอเพอร์ตีที่มีอยู่ในออบเจ็กต์ และโพรโทไทป์

สามารถใช้ฟังก์ชัน Object.getOwnPropertyNames หรือ Object.keys เพื่อแสดงคีย์ทั้งหมดของพรอเพอร์ตี

ข้อแตกต่างคือ Object.getOwnPropertyNames จะแสดงพรอเพอร์ตีทั้งหมด ส่วน Object.keys จะแสดงเฉพาะที่ enumerable เป็น true

เพียงแต่ว่าไม่ว่าจะเป็นฟังก์ชันไหนก็จะแสดงได้แค่พรอเพอร์ตีในตัวออบเจ็กต์นั้นเอง จะไม่แสดงพรอเพอร์ตีของโพรโทไทป์

ขอยกตัวอย่างด้วยออบเจ็กต์ที่สร้างจากตัวอย่างที่แล้ว
alert(Object.keys(kratiknam)); // ได้ khwamchu
alert(Object.getOwnPropertyNames(kratiknam)); // ได้ khwamchu,minam

จะเห็นว่า yiho ไม่ปรากฎ เพราะถือว่าเป็นพรอเพอร์ตีของโพรโทไทป์

หากต้องการหาพรอเพอร์ตีในโพรโทไทป์ก็อาจต้องใช้ฟังก์ชันนี้กับตัวโพรโทไทป์อีกที

การเข้าถึงตัวโพรโทไทป์อาจใช้ฟังก์ชัน Object.getPrototypeOf

var prot = Object.getPrototypeOf(kratiknam);
alert(prot); // ได้ [object Object]
alert(Object.keys(prot)); // ได้ yiho
alert(Object.getOwnPropertyNames(prot)); // ได้ yiho
alert(prot.yiho); // ได้ zojirushi



การสร้างออบเจ็กต์ที่มีพรอเพอร์ตีแบบ set และ get

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

การดูค่าจะเกิดเมื่อมีการเรียกชื่อตัวแปรนั้น แต่ถ้าตามด้วยเท่ากับ = จะกลายเป็นการตั้งค่า เช่น
var thaengmai = { yao: 100 };
alert(thaengmai.yao); // ดูค่า ได้ 100
thaengmai.yao = 80; // ตั้งค่า
alert(thaengmai.yao); // ดูค่า ได้ 80

แต่ว่ามีวิธีที่ทำให้พรอเพอร์ตีนั้นมีพฤติกรรมการตั้งค่าและดูค่าที่ต่างออกไปจากเดิมโดยตั้งในรูปแบบ get, set

หากต้องการสร้างพรอเพอร์ตีแบบนั้นในตอนที่สร้างออบเจ็กต์ให้เขียนแบบนี้
var ชื่อตัวแปรออบเจ็กต์ = {
  get คีย์() {
    return ค่าพรอเพอร์ตี;
  },
  set คีย์(ตัวแปรแทนค่าที่รับ) {
    สิ่งที่จะทำเมื่อถูกตั้งค่า
  }
};

รูปแบบอาจดูแล้วซับซ้อนกว่าเดิมพอสมควร เข้าใจยากสักหน่อย มาดูตัวอย่างดีกว่า
var thaengmai = {
  get yao() {
    return 100;
  },
  set yao(x) {
    alert("ห้ามตั้งค่า ค่าจะไม่กลายเป็น "+x);
  }
};
alert(thaengmai.yao); // ได้ 100
thaengmai.yao = 80; // ได้ ห้ามตั้งค่า ค่าจะไม่กลายเป็น 80

แบบนี้จะได้พรอเพอร์ตีชื่อ yao ซึ่งเมื่อดูค่าก็จะได้ 100 แต่เมื่อพยายามจะตั้งค่าโดยใช้ = จะพบว่ามีข้อความเด้งขึ้นมาว่า "ห้ามตั้งค่า"

และจะเห็นว่าเมื่อใช้ = คำสั่งที่ตั้งไว้ใน set จะทำงาน และพารามิเตอร์ x ในที่นี้จะแทนค่าที่ใส่เข้าไปหลัง =

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

กรณีที่ต้องการให้เป็นพรอเพอร์ตีที่อ่านอย่างเดียวเขียนไม่ได้นั้นตรงส่วน set อาจละได้ จะตั้ง get อย่างเดียวก็ได้ เช่น
var thaengmai = {
  get yao() {
    return 100;
  }
};
alert(thaengmai.yao); // ได้ 100
thaengmai.yao = 80; // ไม่เกิดอะไรขึ้น
alert(thaengmai.yao); // ได้ 100

สามารถสร้างพรอเพอร์ตีเข้ามาอีกตัวเพื่อรับค่าที่ป้อนเข้าไป พรอเพอร์ตีนั้นจะเป็นตัวที่เก็บข้อมูลจริงๆ

เช่นแบบนี้
var thaengmai = {
  _yao: 100,
  get yao() {
    return this._yao;
  },
  set yao(x) {
    this._yao = x;
  }
};
alert(thaengmai.yao); // ได้ 100
thaengmai.yao = 80;
alert(thaengmai.yao); // ได้ 80

พอทำแบบนี้แล้ว ดูเผินๆจะกลายเป็นเหมือนกับว่า yao เป็นพรอเพอร์ตีทั่วไปที่สามารถตั้งค่าและดูค่าได้ตามปกติ

แต่แบบนี้การทำงานที่เกิดขึ้นภายในจะต่างกัน ความจริงเมื่อตั้งค่า ค่าจะไปถูกเก็บที่พรอเพอร์ตี _yao และเมื่อดูค่า จริงๆจะเป็นการไปเอาค่าจากพรอเพอร์ตี _yao มาอีกที แบบนี้ yao จึงไม่ใช่ตัวเก็บค่าที่แท้จริง

ทำแค่นี้อาจดูเหมือนยังไม่เห็นประโยชน์ มีแต่จะทำให้ซับซ้อนขึ้น

แต่ประโยชน์ของการที่ทำแบบนี้ก็คือจะทำให้ได้พรอเพอร์ตีที่การรับค่ากับแสดงค่าไม่จำเป็นต้องเหมือนกัน

ตัวอย่างเช่น
var thaengmai = {
  _yao: 100,
  get yao() {
    return "แท่งไม้ยาว " + this._yao;
  },
  set yao(x) {
    this._yao = x * 2;
  }
};
alert(thaengmai.yao); // ได้ แท่งไม้ยาว 100
thaengmai.yao = 80.5;
alert(thaengmai.yao); // ได้ แท่งไม้ยาว 161
alert(thaengmai._yao); // ได้ 161

แบบนี้จะได้พรอเพอร์ตีแปลกๆที่เมื่อตั้งค่ากลับเก็บค่าที่เป็นสองเท่าของค่านั้น และเวลาที่ดูค่าจะมีคำว่า "แท่งไม้ยาว" นำหน้า

ในทางปฏิบัติ ชื่อตัวแปรที่ขึ้นต้นด้วย _ มักหมายถึงตัวแปรที่มีความพิเศษบางอย่าง ในที่นี้ใช้ _ นำหน้าเพื่อสื่อว่าพรอเพอร์ตีนี้ไม่ได้ให้เข้ามาดูค่าโดยตรง ให้ดูผ่านช่องทางอื่น เพียงแต่ว่าพรอเพอร์ตีนี้ก็ไม่ได้ถูกซ่อนจริงๆ ถ้าจะเข้ามาดูค่าหรือแก้ค่าโดยตรงก็ทำได้อยู่ดี

หากต้องการให้พรอเพอร์ตีใช้เขียนค่าได้อย่างเดียวแต่อ่านไม่ได้ แบบนี้จะละส่วนของ get ก็ได้
var thaengmai = {
  _yao: 100,
  set yao(x) {
    this._yao = x * 2;
  }
};
alert(thaengmai.yao); // ได้ undefined
thaengmai.yao = 80.5;
alert(thaengmai._yao); // ได้ 161

กรณีที่ต้องการเพิ่มพรอเพอร์ตีด้วย set get ให้กับออบเจ็กต์ที่มีอยู่แล้วก็ทำได้โดยใช้ฟังก์ชัน Object.defineProperty หรือ Object.defineProperties ได้
var thaengmai = {};

Object.defineProperties(thaengmai, {
  _yao: { value: 91.5, writable: true },
  yao: {
    get: function() {
      return "แท่งไม้ยาว " + this._yao;
    },
    set: function(x) {
      this._yao = x * 10;
    }
  }
});

thaengmai.yao = 8.5;
alert(thaengmai.yao); // ได้ แท่งไม้ยาว 85



คำอธิบายคุณสมบัติของตัวพรอเพอร์ตีที่ใช้ set และ get

ส่วนคำอธิบายตัวพรอเพอร์ตีที่ตั้งขึ้นมาด้วย set get นั้นก็จะไม่มี value และ writable แต่กลายเป็น set กับ get แทน
var desc = Object.getOwnPropertyDescriptor(thaengmai, "yao");
s = [];
for (var d in desc) {
  s.push(d + ": " + desc[d]);
}
alert(s.join("\n"));
ได้
get: function() {
      return "แท่งไม้ยาว " + this._yao;
    }
set: function(x) {
      this._yao = x * 10;
    }
enumerable: false
configurable: false

นั่นเป็นเพราะว่าตอนนี้พรอเพอร์ yao เมื่อถูกตั้งด้วย set get แบบนี้ก็ไม่ใช่ฟังก์ชันที่มีค่าในตัวเองแล้ว จึงไม่มี value และ writable แต่จะยังคงมี enumerable และ configurable อยู่



สรุปฟังก์ชันในบทนี้

ฟังก์ชันทั้งหมดที่แนะนำไปในบทนี้ทั้งหมดเป็นฟังก์ชันที่ติดมากับออบเจ็กต์ Object ฟังก์ชันเหล่านี้เพิ่มเข้ามาใน ES5

Object.create สร้างออบเจ็กต์พร้อมใส่พรอเพอร์ตีที่กำหนดคุณสมบัติ
Object.defineProperty ใส่พรอเพอร์ตีที่มีการกำหนดคุณสมบัติ
Object.defineProperties ใส่พรอเพอร์ตีที่มีการกำหนดคุณสมบัติลงไปพร้อมกันหลายตัว
Object.keys แสดงคีย์ทั้งหมดของออบเจ็กต์ที่ enumerable
Object.getOwnPropertyNames แสดงคีย์ทั้งหมดของออบเจ็กต์
Object.getOwnPropertyDescriptor แสดงคำอธิบายคุณสมบัติของพรอเพอร์ตี
Object.getPrototypeOf แสดงโพรโทไทป์ของออบเจ็กต์นั้น




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

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

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

หมวดหมู่

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

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

สารบัญ

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

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

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



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

  ค้นหาบทความ

  บทความแนะนำ

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

ไทย

日本語

中文