ตามข้อกำหนดแล้ว มีเพียงสองประเภทดั้งเดิมเท่านั้นที่อาจทำหน้าที่เป็นคีย์คุณสมบัติของอ็อบเจ็กต์:
ประเภทสตริงหรือ
ประเภทสัญลักษณ์
มิฉะนั้น หากใช้ประเภทอื่น เช่น ตัวเลข ระบบจะแปลงเป็นสตริงโดยอัตโนมัติ ดังนั้น obj[1]
จึงเหมือนกับ obj["1"]
และ obj[true]
ก็เหมือนกับ obj["true"]
จนถึงตอนนี้เราใช้เพียงสตริงเท่านั้น
ตอนนี้เรามาสำรวจสัญลักษณ์กัน ดูว่าพวกมันสามารถทำอะไรให้เราได้บ้าง
“สัญลักษณ์” แสดงถึงตัวระบุที่ไม่ซ้ำใคร
สามารถสร้างค่าประเภทนี้ได้โดยใช้ Symbol()
:
ให้ id = สัญลักษณ์();
เมื่อสร้างแล้ว เราสามารถให้คำอธิบายแก่สัญลักษณ์ได้ (หรือที่เรียกว่าชื่อสัญลักษณ์) ซึ่งส่วนใหญ่มีประโยชน์สำหรับวัตถุประสงค์ในการแก้ไขจุดบกพร่อง:
// id เป็นสัญลักษณ์ที่มีคำอธิบาย "id" ให้ id = Symbol("id");
สัญลักษณ์รับประกันว่าจะไม่ซ้ำกัน แม้ว่าเราจะสร้างสัญลักษณ์จำนวนมากที่มีคำอธิบายเหมือนกันทุกประการ แต่ค่าเหล่านั้นก็ต่างกัน คำอธิบายเป็นเพียงป้ายกำกับที่ไม่มีผลกระทบใดๆ
ตัวอย่างเช่น นี่คือสัญลักษณ์สองตัวที่มีคำอธิบายเหมือนกัน ซึ่งไม่เท่ากัน:
ให้ id1 = สัญลักษณ์("id"); ให้ id2 = สัญลักษณ์("id"); การแจ้งเตือน (id1 == id2); // เท็จ
หากคุณคุ้นเคยกับ Ruby หรือภาษาอื่นที่มี "สัญลักษณ์" อยู่ด้วย โปรดอย่าเข้าใจผิด สัญลักษณ์ JavaScript จะแตกต่างกัน
โดยสรุป สัญลักษณ์คือ "ค่าเฉพาะดั้งเดิม" พร้อมคำอธิบายเพิ่มเติม มาดูกันว่าเราจะนำไปใช้ได้ที่ไหน
สัญลักษณ์ไม่แปลงเป็นสตริงโดยอัตโนมัติ
ค่าส่วนใหญ่ใน JavaScript รองรับการแปลงโดยนัยเป็นสตริง ตัวอย่างเช่น เราสามารถ alert
เกือบทุกค่า และมันจะได้ผล สัญลักษณ์มีความพิเศษ พวกเขาไม่ได้แปลงอัตโนมัติ
ตัวอย่างเช่น alert
นี้จะแสดงข้อผิดพลาด:
ให้ id = Symbol("id"); การแจ้งเตือน (รหัส); // TypeError: ไม่สามารถแปลงค่าสัญลักษณ์เป็นสตริงได้
นั่นเป็น “การป้องกันภาษา” เพื่อไม่ให้เกิดความสับสน เนื่องจากสตริงและสัญลักษณ์มีความแตกต่างกันโดยพื้นฐาน และไม่ควรแปลงเป็นอีกรายการโดยไม่ได้ตั้งใจ
หากเราต้องการแสดงสัญลักษณ์จริงๆ เราจำเป็นต้องเรียก .toString()
อย่างชัดเจน ดังตัวอย่างนี้:
ให้ id = Symbol("id"); การแจ้งเตือน(id.toString()); // Symbol(id) ตอนนี้ใช้งานได้แล้ว
หรือรับคุณสมบัติ symbol.description
เพื่อแสดงคำอธิบายเท่านั้น:
ให้ id = Symbol("id"); การแจ้งเตือน (id.คำอธิบาย); // รหัส
สัญลักษณ์ช่วยให้เราสามารถสร้างคุณสมบัติ "ซ่อน" ของออบเจ็กต์ได้ โดยที่ส่วนอื่นของโค้ดไม่สามารถเข้าถึงหรือเขียนทับโดยไม่ได้ตั้งใจ
ตัวอย่างเช่น หากเรากำลังทำงานกับออบเจ็กต์ user
ที่เป็นของโค้ดของบุคคลที่สาม เราต้องการเพิ่มตัวระบุให้กับพวกเขา
ลองใช้คีย์สัญลักษณ์แทน:
ให้ผู้ใช้ = { // เป็นของรหัสอื่น ชื่อ: "จอห์น" - ให้ id = Symbol("id"); ผู้ใช้ [id] = 1; การแจ้งเตือน( ผู้ใช้[id] ); // เราสามารถเข้าถึงข้อมูลโดยใช้สัญลักษณ์เป็นกุญแจ
ประโยชน์ของการใช้ Symbol("id")
เหนือสตริง "id"
คืออะไร
เนื่องจากอ็อบเจ็กต์ user
เป็นของโค้ดเบสอื่น การเพิ่มฟิลด์ลงไปจึงไม่ปลอดภัย เนื่องจากเราอาจส่งผลต่อพฤติกรรมที่กำหนดไว้ล่วงหน้าในโค้ดเบสอื่นนั้น อย่างไรก็ตาม ไม่สามารถเข้าถึงสัญลักษณ์ได้โดยไม่ตั้งใจ รหัสบุคคลที่สามจะไม่ทราบถึงสัญลักษณ์ที่กำหนดใหม่ ดังนั้นจึงปลอดภัยที่จะเพิ่มสัญลักษณ์ให้กับออบเจ็กต์ user
นอกจากนี้ ลองจินตนาการว่าสคริปต์อื่นต้องการมีตัวระบุของตัวเองภายใน user
เพื่อจุดประสงค์ของตัวเอง
จากนั้นสคริปต์นั้นสามารถสร้าง Symbol("id")
ของตัวเองได้เช่นนี้
- ให้ id = Symbol("id"); user[id] = "ค่ารหัสของพวกเขา";
จะไม่มีข้อขัดแย้งระหว่างเราและตัวระบุ เนื่องจากสัญลักษณ์จะแตกต่างกันเสมอ แม้ว่าจะมีชื่อเหมือนกันก็ตาม
…แต่ถ้าเราใช้สตริง "id"
แทนสัญลักษณ์เพื่อจุดประสงค์เดียวกัน ก็ จะ มีข้อขัดแย้ง:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" }; // สคริปต์ของเราใช้คุณสมบัติ "id" user.id = "ค่ารหัสของเรา"; // ...สคริปต์อื่นต้องการ "id" ตามวัตถุประสงค์ด้วย... user.id = "ค่ารหัสของพวกเขา" //บูม! ถูกเขียนทับด้วยสคริปต์อื่น!
หากเราต้องการใช้สัญลักษณ์ในวัตถุตามตัวอักษร {...}
เราจำเป็นต้องมีวงเล็บเหลี่ยมล้อมรอบ
แบบนี้:
ให้ id = Symbol("id"); ให้ผู้ใช้ = { ชื่อ: "จอห์น" [id]: 123 // ไม่ใช่ "id": 123 -
นั่นเป็นเพราะเราต้องการค่าจากตัวแปร id
เป็นคีย์ ไม่ใช่สตริง “id”
คุณสมบัติเชิงสัญลักษณ์ไม่มีส่วนร่วมใน for..in
loop
ตัวอย่างเช่น:
ให้ id = Symbol("id"); ให้ผู้ใช้ = { ชื่อ: "จอห์น" อายุ: 30, [รหัส]: 123 - สำหรับ (ให้ป้อนผู้ใช้) การแจ้งเตือน (คีย์); // ชื่อ อายุ (ไม่มีสัญลักษณ์) // การเข้าถึงโดยตรงด้วยสัญลักษณ์ใช้งานได้ การแจ้งเตือน ( "โดยตรง:" + ผู้ใช้ [id] ); // โดยตรง: 123
Object.keys(user) ก็ละเว้นพวกเขาเช่นกัน นั่นเป็นส่วนหนึ่งของหลักการทั่วไป "การซ่อนคุณสมบัติเชิงสัญลักษณ์" หากสคริปต์หรือไลบรารีอื่นวนซ้ำอ็อบเจ็กต์ของเรา ก็จะไม่เข้าถึงคุณสมบัติสัญลักษณ์โดยไม่คาดคิด
ในทางตรงกันข้าม Object.assign จะคัดลอกทั้งคุณสมบัติสตริงและสัญลักษณ์:
ให้ id = Symbol("id"); ให้ผู้ใช้ = { [รหัส]: 123 - ให้ clone = Object.assign({}, ผู้ใช้); การแจ้งเตือน (โคลน [id] ); // 123
ไม่มีความขัดแย้งที่นี่ นั่นเป็นเพราะการออกแบบ แนวคิดก็คือเมื่อเราโคลนวัตถุหรือรวมวัตถุ เรามักจะต้องการให้คัดลอกคุณสมบัติ ทั้งหมด (รวมถึงสัญลักษณ์เช่น id
)
ดังที่เราได้เห็นแล้วว่าสัญลักษณ์ทั้งหมดมักจะแตกต่างกันแม้ว่าจะมีชื่อเหมือนกันก็ตาม แต่บางครั้งเราต้องการให้สัญลักษณ์ที่มีชื่อเดียวกันเป็นเอนทิตีเดียวกัน ตัวอย่างเช่น ส่วนต่างๆ ของแอปพลิเคชันของเราต้องการเข้าถึงสัญลักษณ์ "id"
ซึ่งหมายถึงคุณสมบัติเดียวกันทุกประการ
เพื่อให้บรรลุเป้าหมายดังกล่าว จึงได้มีการ ลงทะเบียนสัญลักษณ์สากล เราสามารถสร้างสัญลักษณ์ในนั้นและเข้าถึงได้ในภายหลัง และรับประกันว่าการเข้าถึงซ้ำด้วยชื่อเดียวกันจะส่งคืนสัญลักษณ์เดียวกันทุกประการ
หากต้องการอ่าน (สร้างหากไม่มี) สัญลักษณ์จากรีจิสตรี ให้ใช้ Symbol.for(key)
การเรียกนั้นจะตรวจสอบรีจิสทรีส่วนกลาง และหากมีสัญลักษณ์ที่อธิบายว่าเป็น key
ให้ส่งคืนสัญลักษณ์นั้น หรือไม่เช่นนั้นจะสร้างสัญลักษณ์ใหม่ Symbol(key)
และจัดเก็บไว้ในรีจิสทรีด้วย key
ที่กำหนด
ตัวอย่างเช่น:
// อ่านจากรีจิสทรีส่วนกลาง ให้ id = Symbol.for("id"); // หากไม่มีสัญลักษณ์ก็จะถูกสร้างขึ้น // อ่านอีกครั้ง (อาจมาจากส่วนอื่นของโค้ด) ให้ idAgain = Symbol.for("id"); //สัญลักษณ์เดียวกัน การแจ้งเตือน ( id === idAgain ); // จริง
สัญลักษณ์ภายในรีจิสทรีเรียกว่า สัญลักษณ์สากล หากเราต้องการสัญลักษณ์ทั่วทั้งแอปพลิเคชัน ซึ่งสามารถเข้าถึงได้ทุกที่ในโค้ด นั่นคือสิ่งที่มีไว้สำหรับ
เสียงเหมือนรูบี้เลย
ในภาษาโปรแกรมบางภาษา เช่น Ruby จะมีสัญลักษณ์เดียวต่อชื่อ
อย่างที่เราเห็นใน JavaScript นั่นเป็นเรื่องจริงสำหรับสัญลักษณ์ส่วนกลาง
เราได้เห็นแล้วว่าสำหรับสัญลักษณ์สากล Symbol.for(key)
ส่งคืนสัญลักษณ์ตามชื่อ หากต้องการทำสิ่งที่ตรงกันข้าม – ส่งคืนชื่อด้วยสัญลักษณ์สากล – เราสามารถใช้: Symbol.keyFor(sym)
:
ตัวอย่างเช่น:
// รับสัญลักษณ์ตามชื่อ ให้ sym = Symbol.for("name"); ให้ sym2 = Symbol.for("id"); // รับชื่อด้วยสัญลักษณ์ การแจ้งเตือน( Symbol.keyFor(sym) ); // ชื่อ การแจ้งเตือน( Symbol.keyFor(sym2) ); // รหัส
Symbol.keyFor
ใช้รีจิสทรีสัญลักษณ์สากลภายในเพื่อค้นหาคีย์สำหรับสัญลักษณ์ ดังนั้นจึงใช้ไม่ได้กับสัญลักษณ์ที่ไม่ใช่สากล หากสัญลักษณ์ไม่ใช่แบบสากล ก็จะค้นหาไม่พบและส่งคืน undefined
ที่กล่าวว่าสัญลักษณ์ทั้งหมดมีคุณสมบัติ description
ตัวอย่างเช่น:
ให้ globalSymbol = Symbol.for("name"); ให้ localSymbol = Symbol("ชื่อ"); การแจ้งเตือน ( Symbol.keyFor (globalSymbol) ); // ชื่อสัญลักษณ์สากล การแจ้งเตือน ( Symbol.keyFor (localSymbol) ); // ไม่ได้กำหนด ไม่ใช่แบบโกลบอล การแจ้งเตือน ( localSymbol.description ); // ชื่อ
มีสัญลักษณ์ “ระบบ” มากมายที่ JavaScript ใช้เป็นการภายใน และเราสามารถใช้เพื่อปรับแต่งแง่มุมต่างๆ ของออบเจ็กต์ของเราได้
มีการระบุไว้ในข้อกำหนดในตารางสัญลักษณ์ที่รู้จักกันดี:
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
…และอื่นๆ
ตัวอย่างเช่น Symbol.toPrimitive
ช่วยให้เราสามารถอธิบายวัตถุเป็นการแปลงแบบดั้งเดิมได้ เราจะได้เห็นการใช้งานของมันเร็วๆ นี้
สัญลักษณ์อื่นๆ ก็จะคุ้นเคยเมื่อเราศึกษาคุณลักษณะของภาษาที่เกี่ยวข้อง
Symbol
เป็นประเภทดั้งเดิมสำหรับตัวระบุที่ไม่ซ้ำกัน
สัญลักษณ์ถูกสร้างขึ้นด้วยการเรียก Symbol()
พร้อมคำอธิบายเพิ่มเติม (ชื่อ)
สัญลักษณ์จะมีค่าต่างกันเสมอ แม้ว่าจะมีชื่อเหมือนกันก็ตาม หากเราต้องการให้สัญลักษณ์ที่มีชื่อเดียวกันเท่ากัน เราก็ควรใช้รีจิสทรีส่วนกลาง: Symbol.for(key)
ส่งคืน (สร้างหากจำเป็น) สัญลักษณ์ส่วนกลางที่มี key
เป็นชื่อ การเรียก Symbol.for
หลายครั้งด้วย key
เดียวกันจะส่งคืนสัญลักษณ์เดียวกันทุกประการ
สัญลักษณ์มีกรณีการใช้งานหลักสองกรณี:
คุณสมบัติวัตถุ "ซ่อน"
หากเราต้องการเพิ่มคุณสมบัติลงในวัตถุที่ “เป็น” ของสคริปต์หรือไลบรารีอื่น เราสามารถสร้างสัญลักษณ์และใช้เป็นคีย์คุณสมบัติได้ คุณสมบัติเชิงสัญลักษณ์ไม่ปรากฏใน for..in
ดังนั้นจึงไม่ถูกประมวลผลร่วมกับคุณสมบัติอื่นๆ โดยไม่ได้ตั้งใจ นอกจากนี้จะไม่สามารถเข้าถึงได้โดยตรง เนื่องจากสคริปต์อื่นไม่มีสัญลักษณ์ของเรา ดังนั้นทรัพย์สินจะได้รับการคุ้มครองจากการใช้งานโดยไม่ได้ตั้งใจหรือเขียนทับ
ดังนั้นเราจึงสามารถ "ซ่อนเร้น" ซ่อนบางสิ่งบางอย่างไว้ในวัตถุที่เราต้องการ แต่คนอื่นไม่ควรเห็นโดยใช้คุณสมบัติเชิงสัญลักษณ์
มีสัญลักษณ์ระบบมากมายที่ใช้โดย JavaScript ซึ่งสามารถเข้าถึงได้ในรูปแบบ Symbol.*
เราสามารถใช้มันเพื่อปรับเปลี่ยนพฤติกรรมในตัวบางอย่างได้ ตัวอย่างเช่น ภายหลังในบทช่วยสอน เราจะใช้ Symbol.iterator
สำหรับการทำซ้ำได้, Symbol.toPrimitive
เพื่อตั้งค่าการแปลงแบบออบเจ็กต์เป็นแบบดั้งเดิม และอื่นๆ
ในทางเทคนิคแล้ว สัญลักษณ์ไม่ได้ถูกซ่อนไว้ 100% มีเมธอดในตัว Object.getOwnPropertySymbols(obj) ที่ช่วยให้เราได้รับสัญลักษณ์ทั้งหมด นอกจากนี้ยังมีเมธอดชื่อ Reflect.ownKeys(obj) ที่ส่งคืนคีย์ ทั้งหมด ของอ็อบเจ็กต์รวมถึงคีย์สัญลักษณ์ด้วย แต่ไลบรารี่ ฟังก์ชันบิวท์อิน และโครงสร้างไวยากรณ์ส่วนใหญ่ไม่ได้ใช้วิธีการเหล่านี้