φυβλαςのβλογ
บล็อกของ 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)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

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

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月

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

ไทย

日本語

中文