การใส่พรอเพอร์ตีพร้อมกำหนดคุณสมบัติ
พรอเพอร์ตีในตัวออบเจ็กต์นั้นโดยทั่วไปที่สร้างโดยวิธีทั่วไปจะสามารถแก้ไขค่าได้
ลบทิ้งได้ และสามารถถูกวนซ้ำเมื่อใช้ 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 |
แสดงโพรโทไทป์ของออบเจ็กต์นั้น |