ดังที่เราทราบ วัตถุสามารถเก็บคุณสมบัติได้
จนถึงขณะนี้ พร็อพเพอร์ตี้เป็นเพียงคู่ "คีย์-ค่า" ธรรมดาๆ สำหรับเรา แต่คุณสมบัติของวัตถุนั้นจริงๆ แล้วเป็นสิ่งที่ยืดหยุ่นและทรงพลังมากกว่า
ในบทนี้ เราจะศึกษาตัวเลือกการกำหนดค่าเพิ่มเติม และในบทต่อไป เราจะดูวิธีการเปลี่ยนให้เป็นฟังก์ชัน getter/setter แบบมองไม่เห็น
คุณสมบัติของวัตถุ นอกเหนือจาก value
มีคุณลักษณะพิเศษสามประการ (เรียกว่า "ธง"):
writable
– หากเป็น true
ค่าสามารถเปลี่ยนแปลงได้ ไม่เช่นนั้นจะเป็นแบบอ่านอย่างเดียว
enumerable
- หากเป็น true
รายการนั้นจะอยู่ในลูป มิฉะนั้นจะไม่อยู่ในรายการ
configurable
- หากเป็น true
คุณสมบัติก็สามารถลบออกได้ และแอตทริบิวต์เหล่านี้สามารถแก้ไขได้ มิเช่นนั้นจะแก้ไขไม่ได้
เรายังไม่เห็นพวกเขาเพราะโดยทั่วไปแล้วพวกเขาจะไม่ปรากฏตัว เมื่อเราสร้างคุณสมบัติ “แบบปกติ” ทุกอย่างล้วนเป็น true
แต่เราก็สามารถเปลี่ยนได้ตลอดเวลาเช่นกัน
ก่อนอื่นเรามาดูวิธีรับธงเหล่านั้นกันก่อน
เมธอด Object.getOwnPropertyDescriptor อนุญาตให้สืบค้นข้อมูล ทั้งหมด เกี่ยวกับคุณสมบัติ
ไวยากรณ์คือ:
ให้คำอธิบาย = Object.getOwnPropertyDescriptor (obj, propertyName);
obj
วัตถุที่จะรับข้อมูลจาก
propertyName
ชื่อของคุณสมบัติ
ค่าที่ส่งคืนเป็นสิ่งที่เรียกว่าวัตถุ "ตัวอธิบายคุณสมบัติ" ซึ่งมีค่าและแฟล็กทั้งหมด
ตัวอย่างเช่น:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" - ให้ descriptor = Object.getOwnPropertyDescriptor (ผู้ใช้ 'ชื่อ'); การแจ้งเตือน ( JSON.stringify (ตัวอธิบาย, null, 2 ) ); /* ตัวอธิบายคุณสมบัติ: - "value": "จอห์น", "เขียนได้": จริง "นับได้": จริง, "กำหนดค่าได้": จริง - -
หากต้องการเปลี่ยนแฟล็ก เราสามารถใช้ Object.defineProperty
ไวยากรณ์คือ:
Object.defineProperty (obj, propertyName, descriptor)
obj
propertyName
วัตถุและคุณสมบัติที่จะใช้อธิบาย
descriptor
วัตถุตัวอธิบายคุณสมบัติที่จะใช้
หากคุณสมบัติมีอยู่ defineProperty
จะอัพเดตแฟล็ก มิฉะนั้นจะสร้างคุณสมบัติด้วยค่าและแฟล็กที่กำหนด ในกรณีนั้น หากไม่ได้ระบุแฟล็ก จะถือว่า false
ตัวอย่างเช่น ที่นี่ name
คุณสมบัติถูกสร้างขึ้นพร้อมกับแฟล็กเท็จทั้งหมด:
ให้ผู้ใช้ = {}; Object.defineProperty (ผู้ใช้ "ชื่อ", { มูลค่า: "จอห์น" - ให้ descriptor = Object.getOwnPropertyDescriptor (ผู้ใช้ 'ชื่อ'); การแจ้งเตือน ( JSON.stringify (ตัวอธิบาย, null, 2 ) ); - - "value": "จอห์น", "เขียนได้": เท็จ "นับได้": เท็จ "กำหนดค่าได้": เท็จ - -
เปรียบเทียบกับ user.name
“สร้างตามปกติ” ด้านบน: ตอนนี้แฟล็กทั้งหมดเป็นเท็จ หากนั่นไม่ใช่สิ่งที่เราต้องการ เราควรตั้งค่าให้เป็น true
ใน descriptor
ดีกว่า
ทีนี้เรามาดูเอฟเฟกต์ของแฟล็กตามตัวอย่างกันดีกว่า
มาทำให้ user.name
ไม่สามารถเขียนได้ (ไม่สามารถกำหนดใหม่ได้) โดยการเปลี่ยนค่าสถานะ writable
:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" - Object.defineProperty (ผู้ใช้ "ชื่อ", { เขียนได้: เท็จ - user.name = "พีท"; // ข้อผิดพลาด: ไม่สามารถกำหนดให้ 'ชื่อ' คุณสมบัติอ่านอย่างเดียว
ตอนนี้ไม่มีใครสามารถเปลี่ยนชื่อผู้ใช้ของเราได้ เว้นแต่พวกเขาจะใช้ defineProperty
ของตนเองเพื่อแทนที่ชื่อของเรา
ข้อผิดพลาดปรากฏเฉพาะในโหมดเข้มงวดเท่านั้น
ในโหมดไม่เข้มงวด จะไม่เกิดข้อผิดพลาดเมื่อเขียนไปยังคุณสมบัติที่ไม่สามารถเขียนได้และอื่นๆ แต่การดำเนินการก็ยังไม่ประสบผลสำเร็จ การกระทำที่ละเมิดการตั้งค่าสถานะจะถูกเพิกเฉยอย่างเงียบ ๆ ในรูปแบบที่ไม่เข้มงวด
นี่เป็นตัวอย่างเดียวกัน แต่คุณสมบัติถูกสร้างขึ้นตั้งแต่ต้น:
ให้ผู้ใช้ = { }; Object.defineProperty (ผู้ใช้ "ชื่อ", { ค่า: "จอห์น", // สำหรับคุณสมบัติใหม่ เราจำเป็นต้องแสดงรายการสิ่งที่เป็นจริงอย่างชัดเจน นับได้: จริง, กำหนดค่าได้: จริง - การแจ้งเตือน (ชื่อผู้ใช้); // จอห์น user.name = "พีท"; // ข้อผิดพลาด
ตอนนี้เรามาเพิ่ม toString
ที่กำหนดเองให้กับ user
โดยปกติแล้ว toString
ในตัวสำหรับอ็อบเจ็กต์จะไม่สามารถนับได้ แต่จะไม่แสดงใน for..in
แต่ถ้าเราเพิ่ม toString
ของเราเอง ตามค่าเริ่มต้นมันจะปรากฏใน for..in
เช่นนี้:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" toString() { ส่งคืน this.name; - - // ตามค่าเริ่มต้น คุณสมบัติของเราทั้งสองจะแสดงอยู่ในรายการ: สำหรับ (ให้ป้อนผู้ใช้) การแจ้งเตือน (คีย์); // ชื่อ toString
หากเราไม่ชอบมัน เราก็สามารถตั้งค่า enumerable:false
ได้ จากนั้นมันจะไม่ปรากฏในลูป for..in
เช่นเดียวกับลูปในตัว:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" toString() { ส่งคืน this.name; - - Object.defineProperty (ผู้ใช้ "toString", { นับได้: เท็จ - // ตอนนี้ toString ของเราหายไป: สำหรับ (ให้ป้อนผู้ใช้) การแจ้งเตือน (คีย์); // ชื่อ
คุณสมบัติที่ไม่สามารถนับได้ก็แยกออกจาก Object.keys
:
การแจ้งเตือน (Object.keys (ผู้ใช้)); // ชื่อ
บางครั้งแฟล็กที่ไม่สามารถกำหนดค่าได้ ( configurable:false
) จะถูกตั้งค่าล่วงหน้าสำหรับอ็อบเจ็กต์และคุณสมบัติในตัว
ไม่สามารถลบคุณสมบัติที่ไม่สามารถกำหนดค่าได้ และคุณสมบัติไม่สามารถแก้ไขได้
ตัวอย่างเช่น Math.PI
ไม่สามารถเขียนได้ ไม่สามารถนับได้ และกำหนดค่าไม่ได้:
ให้ descriptor = Object.getOwnPropertyDescriptor (คณิตศาสตร์ 'PI'); การแจ้งเตือน ( JSON.stringify (ตัวอธิบาย, null, 2 ) ); - - "มูลค่า": 3.141592653589793, "เขียนได้": เท็จ "นับได้": เท็จ "กำหนดค่าได้": เท็จ - -
ดังนั้นโปรแกรมเมอร์จึงไม่สามารถเปลี่ยนค่าของ Math.PI
หรือเขียนทับได้
คณิต.PI = 3; // เกิดข้อผิดพลาด เนื่องจากมีการเขียนได้: false // ลบ Math.PI ก็ใช้ไม่ได้เช่นกัน
เราไม่สามารถเปลี่ยน Math.PI
ให้ writable
อีกครั้ง:
// เกิดข้อผิดพลาดเนื่องจากกำหนดค่าได้: false Object.defineProperty (คณิตศาสตร์ "PI", { เขียนได้: true });
เราไม่สามารถทำอะไรกับ Math.PI
ได้อย่างแน่นอน
การทำให้ทรัพย์สินไม่สามารถกำหนดค่าได้นั้นเป็นถนนเดินรถทางเดียว เราไม่สามารถเปลี่ยนกลับด้วย defineProperty
ได้
โปรดทราบ: configurable: false
ป้องกันการเปลี่ยนแปลงแฟล็กคุณสมบัติและการลบ ในขณะเดียวกันก็อนุญาตให้เปลี่ยนค่าได้
ที่นี่ user.name
ไม่สามารถกำหนดค่าได้ แต่เรายังคงสามารถเปลี่ยนได้ (ตามที่เขียนได้):
ให้ผู้ใช้ = { ชื่อ: "จอห์น" - Object.defineProperty (ผู้ใช้ "ชื่อ", { กำหนดค่าได้: เท็จ - user.name = "พีท"; //ทำงานได้ดี ลบชื่อผู้ใช้.ชื่อ; // ข้อผิดพลาด
และที่นี่เราทำให้ user.name
เป็นค่าคงที่ "ปิดผนึกตลอดไป" เช่นเดียวกับ Math.PI
ในตัว :
ให้ผู้ใช้ = { ชื่อ: "จอห์น" - Object.defineProperty (ผู้ใช้ "ชื่อ", { เขียนได้: เท็จ, กำหนดค่าได้: เท็จ - // จะไม่สามารถเปลี่ยนชื่อผู้ใช้หรือค่าสถานะได้ // ทั้งหมดนี้ใช้ไม่ได้: user.name = "พีท"; ลบชื่อผู้ใช้.ชื่อ; Object.defineProperty (ผู้ใช้ "ชื่อ", { ค่า: "พีท" });
การเปลี่ยนแปลงแอตทริบิวต์เดียวที่เป็นไปได้: เขียนได้จริง → เท็จ
มีข้อยกเว้นเล็กน้อยเกี่ยวกับการเปลี่ยนแฟล็ก
เราสามารถเปลี่ยน writable: true
เป็น false
สำหรับคุณสมบัติที่ไม่สามารถกำหนดค่าได้ ดังนั้นจึงป้องกันการแก้ไขค่า (เพื่อเพิ่มการป้องกันอีกชั้นหนึ่ง) ไม่ใช่วิธีอื่นแม้ว่า
มีเมธอด Object.defineProperties(obj, descriptors) ที่อนุญาตให้กำหนดคุณสมบัติหลายอย่างพร้อมกัน
ไวยากรณ์คือ:
Object.defineProperties (obj, { ข้อเสนอ 1: คำอธิบาย 1, Prop2: ตัวอธิบาย2 - -
ตัวอย่างเช่น:
Object.defineProperties (ผู้ใช้ { ชื่อ: { ค่า: "จอห์น" เขียนได้: false }, นามสกุล: { ค่า: "Smith" เขียนได้: false }, - -
ดังนั้นเราจึงสามารถตั้งค่าคุณสมบัติหลายอย่างพร้อมกันได้
ในการรับคำอธิบายคุณสมบัติทั้งหมดพร้อมกัน เราสามารถใช้เมธอด Object.getOwnPropertyDescriptors(obj)
เมื่อใช้ร่วมกับ Object.defineProperties
สามารถใช้เป็นวิธีการโคลนวัตถุแบบ "รับรู้สถานะธง" ได้:
ให้โคลน = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
โดยปกติเมื่อเราโคลนวัตถุ เราจะใช้การมอบหมายเพื่อคัดลอกคุณสมบัติ เช่นนี้:
สำหรับ (ให้ป้อนผู้ใช้) { โคลน [คีย์] = ผู้ใช้ [คีย์] -
…แต่นั่นไม่ได้คัดลอกแฟล็ก ดังนั้นหากเราต้องการโคลนที่ "ดีกว่า" Object.defineProperties
ก็เป็นที่ต้องการมากกว่า
ข้อแตกต่างอีกประการหนึ่งคือ for..in
ละเว้นคุณสมบัติเชิงสัญลักษณ์และไม่สามารถนับได้ แต่ Object.getOwnPropertyDescriptors
ส่งคืนตัวอธิบายคุณสมบัติ ทั้งหมด รวมถึงคุณสมบัติที่เป็นสัญลักษณ์และไม่สามารถนับได้
ตัวอธิบายคุณสมบัติทำงานในระดับของคุณสมบัติแต่ละรายการ
นอกจากนี้ยังมีวิธีการที่จำกัดการเข้าถึงวัตถุ ทั้งหมด :
Object.preventExtensions(obj)
ห้ามการเพิ่มคุณสมบัติใหม่ให้กับวัตถุ
Object.seal(obj)
ห้ามเพิ่ม/ลบคุณสมบัติ ตั้งค่า configurable: false
สำหรับคุณสมบัติที่มีอยู่ทั้งหมด
Object.freeze (obj)
ห้ามเพิ่ม/ถอด/เปลี่ยนแปลงคุณสมบัติ ตั้งค่า configurable: false, writable: false
สำหรับคุณสมบัติที่มีอยู่ทั้งหมด
และยังมีการทดสอบสำหรับพวกเขาด้วย:
วัตถุ isExtensible (obj)
คืนค่า false
หากไม่อนุญาตให้เพิ่มคุณสมบัติ มิฉะนั้นจะ true
Object.isSealed (obj)
คืนค่า true
หากไม่อนุญาตให้เพิ่ม/ลบคุณสมบัติ และคุณสมบัติที่มีอยู่ทั้งหมด configurable: false
Object.isFrozen(obj)
คืนค่า true
หากไม่อนุญาตให้เพิ่ม/ลบ/เปลี่ยนแปลงคุณสมบัติ และคุณสมบัติปัจจุบันทั้งหมด configurable: false, writable: false
วิธีการเหล่านี้ไม่ค่อยได้ใช้ในทางปฏิบัติ