คุณสมบัติของวัตถุมีสองประเภท
ประเภทแรกคือ คุณสมบัติของข้อมูล เรารู้วิธีทำงานร่วมกับพวกเขาแล้ว คุณสมบัติทั้งหมดที่เราใช้มาจนถึงตอนนี้เป็นคุณสมบัติของข้อมูล
ทรัพย์สินประเภทที่สองเป็นสิ่งใหม่ มันเป็น คุณสมบัติ accessor โดยพื้นฐานแล้วเป็นฟังก์ชันที่ดำเนินการเพื่อรับและตั้งค่า แต่ดูเหมือนคุณสมบัติปกติของโค้ดภายนอก
คุณสมบัติตัวเข้าถึงแสดงโดยวิธี "getter" และ "setter" ในวัตถุตามตัวอักษรพวกมันจะแสดงด้วย get
และ set
:
ให้ obj = { รับ propName() { // getter โค้ดที่ดำเนินการเมื่อรับ obj.propName - ตั้งค่า propName (ค่า) { // setter รหัสที่ดำเนินการเมื่อตั้งค่า obj.propName = value - -
getter ทำงานเมื่อมีการอ่าน obj.propName
ตัวตั้งค่า – เมื่อถูกกำหนด
ตัวอย่างเช่น เรามีวัตถุ user
ที่มี name
และ surname
:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" นามสกุล: "สมิธ" -
ตอนนี้เราต้องการเพิ่มคุณสมบัติ fullName
ซึ่งควรเป็น "John Smith"
แน่นอนว่าเราไม่ต้องการคัดลอกและวางข้อมูลที่มีอยู่ ดังนั้นเราจึงสามารถนำไปใช้ในฐานะผู้เข้าถึงได้:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" นามสกุล: "สมิธ", รับชื่อเต็ม () { ส่งคืน `${this.name} ${this.surname}`; - - การแจ้งเตือน (user.fullName); // จอห์น สมิธ
จากภายนอก คุณสมบัติ accessor ดูเหมือนคุณสมบัติทั่วไป นั่นคือแนวคิดของคุณสมบัติตัวเข้าถึง เราไม่ เรียก user.fullName
เป็นฟังก์ชัน แต่เรา อ่าน ได้ตามปกติ: getter ทำงานเบื้องหลัง
ณ ตอนนี้ fullName
มีเพียงทะเยอทะยานเท่านั้น หากเราพยายามกำหนด user.fullName=
จะมีข้อผิดพลาด:
ให้ผู้ใช้ = { รับชื่อเต็ม () { กลับ `...`; - - user.fullName = "ทดสอบ"; // ข้อผิดพลาด (คุณสมบัติมีเพียงทะเยอทะยาน)
มาแก้ไขด้วยการเพิ่ม setter สำหรับ user.fullName
:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" นามสกุล: "สมิธ", รับชื่อเต็ม () { ส่งคืน `${this.name} ${this.surname}`; - ตั้งชื่อเต็ม (ค่า) { [this.name, this.surname] = value.split(" "); - - // set fullName จะถูกดำเนินการด้วยค่าที่กำหนด user.fullName = "อลิซคูเปอร์"; การแจ้งเตือน (ชื่อผู้ใช้); //อลิซ การแจ้งเตือน(user.surname); //คูเปอร์
ด้วยเหตุนี้ เราจึงมีคุณสมบัติ "เสมือน" fullName
สามารถอ่านและเขียนได้
ตัวอธิบายสำหรับคุณสมบัติตัวเข้าถึงจะแตกต่างจากคุณสมบัติข้อมูล
สำหรับคุณสมบัติ accessor นั้นไม่มี value
หรือ writable
แต่มีฟังก์ชัน get
และ set
แทน
นั่นคือ accessor descriptor อาจมี:
get
– ฟังก์ชันที่ไม่มีอาร์กิวเมนต์ ซึ่งทำงานเมื่ออ่านคุณสมบัติ
set
– ฟังก์ชันที่มีหนึ่งอาร์กิวเมนต์ ที่ถูกเรียกเมื่อมีการตั้งค่าคุณสมบัติ
enumerable
- เช่นเดียวกับคุณสมบัติข้อมูล
configurable
- เช่นเดียวกับคุณสมบัติข้อมูล
ตัวอย่างเช่น ในการสร้าง accessor fullName
ด้วย defineProperty
เราสามารถส่ง descriptor ด้วย get
และ set
:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" นามสกุล: "สมิธ" - Object.defineProperty (ผู้ใช้ 'ชื่อเต็ม', { รับ() { ส่งคืน `${this.name} ${this.surname}`; - ชุด (ค่า) { [this.name, this.surname] = value.split(" "); - - การแจ้งเตือน (user.fullName); // จอห์น สมิธ สำหรับ (ให้ป้อนผู้ใช้) การแจ้งเตือน (คีย์); //ชื่อนามสกุล
โปรดทราบว่าคุณสมบัติสามารถเป็นได้ทั้ง accessor (มีวิธี get/set
) หรือคุณสมบัติข้อมูล (มี value
) ไม่ใช่ทั้งสองอย่าง
หากเราพยายามระบุทั้ง get
และ value
ในตัวอธิบายเดียวกัน จะเกิดข้อผิดพลาด:
// ข้อผิดพลาด: ตัวอธิบายคุณสมบัติไม่ถูกต้อง Object.defineProperty({}, 'เสา', { รับ() { กลับ 1 - ค่า: 2 -
Getters/setters สามารถใช้เป็น wrappers เหนือค่าคุณสมบัติ "ของจริง" เพื่อให้สามารถควบคุมการดำเนินการกับสิ่งเหล่านั้นได้มากขึ้น
ตัวอย่างเช่น หากเราต้องการห้ามไม่ให้ user
ชื่อสั้นเกินไป เราก็สามารถมี name
ผู้ตั้งค่าและเก็บค่าไว้ในคุณสมบัติแยกต่างหาก _name
:
ให้ผู้ใช้ = { รับชื่อ () { ส่งคืนสิ่งนี้._name; - ตั้งชื่อ (ค่า) { ถ้า (value.length < 4) { alert("ชื่อสั้นเกินไป ต้องมีความยาวอย่างน้อย 4 ตัวอักษร"); กลับ; - this._name = ค่า; - - user.name = "พีท"; การแจ้งเตือน (ชื่อผู้ใช้); //พีท ชื่อผู้ใช้ = ""; // ชื่อสั้นเกินไป...
ดังนั้นชื่อจะถูกจัดเก็บไว้ในคุณสมบัติ _name
และการเข้าถึงทำได้ผ่าน getter และ setter
ในทางเทคนิค รหัสภายนอกสามารถเข้าถึงชื่อได้โดยตรงโดยใช้ user._name
แต่มีแบบแผนที่รู้จักกันดีว่าคุณสมบัติที่ขึ้นต้นด้วยขีดล่าง "_"
ถือเป็นคุณสมบัติภายในและไม่ควรสัมผัสจากภายนอกวัตถุ
การใช้งานที่ยอดเยี่ยมอย่างหนึ่งของ accessor คืออนุญาตให้ควบคุมคุณสมบัติข้อมูล "ปกติ" ได้ตลอดเวลาโดยแทนที่ด้วย getter และ setter และปรับแต่งพฤติกรรมของมัน
ลองนึกภาพเราเริ่มใช้งานออบเจ็กต์ผู้ใช้โดยใช้ name
คุณสมบัติข้อมูลและ age
:
ผู้ใช้ฟังก์ชั่น (ชื่อ, อายุ) { this.name = ชื่อ; this.age = อายุ; - ให้ john = ผู้ใช้ใหม่ ("John", 25); การแจ้งเตือน( john.age ); // 25
…แต่ไม่ช้าก็เร็ว สิ่งต่างๆ อาจจะเปลี่ยนไป แทนที่จะเลือก age
เราอาจตัดสินใจเก็บ birthday
เพราะมันแม่นยำและสะดวกกว่า:
ผู้ใช้ฟังก์ชัน (ชื่อ, วันเกิด) { this.name = ชื่อ; this.birthday = วันเกิด; - ให้ john = ผู้ใช้ใหม่ ("John", วันที่ใหม่ (1992, 6, 1));
ทีนี้จะทำอย่างไรกับโค้ดเก่าที่ยังคงใช้คุณสมบัติ age
อยู่?
เราสามารถลองค้นหาตำแหน่งดังกล่าวทั้งหมดและแก้ไขได้ แต่อาจต้องใช้เวลาและอาจทำได้ยากหากมีผู้อื่นใช้โค้ดนั้นเป็นจำนวนมาก นอกจากนี้ age
ยังเป็นสิ่งที่ดีที่จะมีอยู่ใน user
ใช่ไหม?
เก็บไว้เลย..
การเพิ่มทะเยอทะยานสำหรับ age
ช่วยแก้ปัญหา:
ผู้ใช้ฟังก์ชัน (ชื่อ, วันเกิด) { this.name = ชื่อ; this.birthday = วันเกิด; // อายุคำนวณจากวันที่และวันเกิดปัจจุบัน Object.defineProperty (นี่คือ "อายุ", { รับ() { ให้ todayYear = new Date().getFullYear(); กลับมา todayYear - this.birthday.getFullYear(); - - - ให้ john = ผู้ใช้ใหม่ ("John", วันที่ใหม่ (1992, 6, 1)); การแจ้งเตือน( john.birthday ); //วันเกิดสามารถใช้ได้ การแจ้งเตือน( john.age ); // ...รวมทั้งอายุด้วย
ตอนนี้โค้ดเก่าก็ใช้งานได้เช่นกัน และเรามีคุณสมบัติเพิ่มเติมที่ดี