จะเกิดอะไรขึ้นเมื่อมีการเพิ่มวัตถุ obj1 + obj2
ลบ obj1 - obj2
หรือพิมพ์โดยใช้ alert(obj)
?
JavaScript ไม่อนุญาตให้คุณปรับแต่งวิธีการทำงานของตัวดำเนินการกับออบเจ็กต์ ไม่เหมือนกับภาษาโปรแกรมอื่นๆ บางภาษา เช่น Ruby หรือ C++ เราไม่สามารถใช้วิธีการพิเศษของอ็อบเจ็กต์เพื่อจัดการกับการเพิ่ม (หรือตัวดำเนินการอื่นๆ)
ในกรณีของการดำเนินการดังกล่าว ออบเจ็กต์จะถูกแปลงเป็นค่าพื้นฐานโดยอัตโนมัติ จากนั้นการดำเนินการจะดำเนินการกับค่าพื้นฐานเหล่านี้และส่งผลให้เกิดค่าดั้งเดิม
นั่นเป็นข้อจำกัดที่สำคัญ: ผลลัพธ์ของ obj1 + obj2
(หรือการดำเนินการทางคณิตศาสตร์อื่น) ไม่สามารถเป็นวัตถุอื่นได้!
เช่น เราไม่สามารถสร้างวัตถุที่แสดงถึงเวกเตอร์หรือเมทริกซ์ (หรือความสำเร็จหรืออะไรก็ตาม) เพิ่มเข้าไปและคาดหวังให้วัตถุ "สรุป" เป็นผลลัพธ์ ความสำเร็จทางสถาปัตยกรรมดังกล่าวจะ "หลุดลอยไป" โดยอัตโนมัติ
ดังนั้น เนื่องจากเราไม่สามารถทำอะไรได้มากในทางเทคนิคที่นี่ จึงไม่มีคณิตศาสตร์กับวัตถุในโครงการจริง เมื่อมันเกิดขึ้น โดยมีข้อยกเว้นที่หายาก นั่นเป็นเพราะข้อผิดพลาดในการเขียนโค้ด
ในบทนี้เราจะกล่าวถึงวิธีการแปลงอ็อบเจ็กต์เป็นวัตถุดั้งเดิม และวิธีการปรับแต่งมัน
เรามีจุดประสงค์สองประการ:
Date
) เราจะเจอพวกเขาในภายหลังในบท Type Conversions เราได้เห็นกฎสำหรับการแปลงตัวเลข สตริง และบูลีนของค่าพื้นฐาน แต่เราทิ้งช่องว่างไว้สำหรับวัตถุ ตอนนี้เมื่อเรารู้เกี่ยวกับวิธีการและสัญลักษณ์แล้ว ก็เป็นไปได้ที่จะเติมเข้าไป
true
ในบริบทบูลีน ง่ายๆ อย่างนั้น มีเพียงการแปลงตัวเลขและสตริงเท่านั้นDate
(ที่จะกล่าวถึงในบทวันที่และเวลา) ได้ และผลลัพธ์ของ date1 - date2
คือผลต่างของเวลาระหว่างวันที่สองวันalert(obj)
และในบริบทที่คล้ายกันเราสามารถใช้การแปลงสตริงและตัวเลขได้ด้วยตัวเอง โดยใช้วิธีอ็อบเจ็กต์พิเศษ
ตอนนี้เรามาดูรายละเอียดทางเทคนิคกันดีกว่า เพราะนี่เป็นวิธีเดียวที่จะครอบคลุมหัวข้อนี้ในเชิงลึกได้
JavaScript ตัดสินใจว่าจะใช้ Conversion ใด
การแปลงประเภทมีสามรูปแบบที่เกิดขึ้นในสถานการณ์ต่างๆ สิ่งเหล่านี้เรียกว่า "คำแนะนำ" ตามที่อธิบายไว้ในข้อกำหนด:
"string"
สำหรับการแปลงออบเจ็กต์เป็นสตริง เมื่อเราดำเนินการกับออบเจ็กต์ที่คาดหวังสตริง เช่น alert
:
// output alert(obj); // using object as a property key anotherObj[obj] = 123;
"number"
สำหรับการแปลงอ็อบเจ็กต์เป็นตัวเลข เช่น เมื่อเราทำคณิตศาสตร์:
// explicit conversion let num = Number(obj); // maths (except binary plus) let n = +obj; // unary plus let delta = date1 - date2; // less/greater comparison let greater = user1 > user2;
ฟังก์ชันทางคณิตศาสตร์ในตัวส่วนใหญ่ยังรวมการแปลงดังกล่าวด้วย
"default"
เกิดขึ้นไม่บ่อยนักเมื่อผู้ปฏิบัติงาน “ไม่แน่ใจ” ว่าจะคาดหวังประเภทใด
ตัวอย่างเช่น binary plus +
สามารถทำงานได้ทั้งกับสตริง (เชื่อมเข้าด้วยกัน) และตัวเลข (บวกเข้าด้วยกัน) ดังนั้นหากไบนารีบวกได้รับอ็อบเจ็กต์เป็นอาร์กิวเมนต์ จะใช้คำใบ้ "default"
ในการแปลง
นอกจากนี้ หากเปรียบเทียบวัตถุโดยใช้ ==
กับสตริง ตัวเลข หรือสัญลักษณ์ ก็ยังไม่ชัดเจนว่าควรทำการแปลงใด ดังนั้นจึงใช้คำใบ้ "default"
// binary plus uses the "default" hint let total = obj1 + obj2; // obj == number uses the "default" hint if (user == 1) { ... };
ตัวดำเนินการเปรียบเทียบที่มากหรือน้อย เช่น <
>
สามารถใช้ได้กับทั้งสตริงและตัวเลขเช่นกัน ถึงกระนั้น พวกเขาใช้คำใบ้ "number"
ไม่ใช่ "default"
นั่นเป็นเหตุผลทางประวัติศาสตร์
แต่ในทางปฏิบัติ สิ่งต่างๆ จะง่ายกว่าเล็กน้อย
ออบเจ็กต์บิวท์อินทั้งหมดยกเว้นกรณีเดียว (ออบเจ็กต์ Date
เราจะเรียนรู้ในภายหลัง) ใช้การแปลง "default"
ในลักษณะเดียวกับ "number"
และเราก็น่าจะทำเช่นเดียวกัน
อย่างไรก็ตาม สิ่งสำคัญคือต้องรู้คำแนะนำทั้ง 3 ข้อ เร็วๆ นี้เราจะมาดูเหตุผลกัน
เพื่อทำการแปลง JavaScript จะพยายามค้นหาและเรียกใช้วิธีอ็อบเจ็กต์สามวิธี:
obj[Symbol.toPrimitive](hint)
– วิธีการที่มีคีย์สัญลักษณ์ Symbol.toPrimitive
(สัญลักษณ์ระบบ) หากมีวิธีการดังกล่าว"string"
obj.toString()
หรือ obj.valueOf()
สิ่งที่มีอยู่"number"
หรือ "default"
obj.valueOf()
หรือ obj.toString()
สิ่งที่มีอยู่ เริ่มจากวิธีแรกกันก่อน มีสัญลักษณ์ในตัวชื่อ Symbol.toPrimitive
ซึ่งควรใช้ตั้งชื่อวิธีการแปลง เช่นนี้
obj[Symbol.toPrimitive] = function(hint) { // here goes the code to convert this object to a primitive // it must return a primitive value // hint = one of "string", "number", "default" };
หากมีวิธีการ Symbol.toPrimitive
อยู่ วิธีการนั้นจะใช้สำหรับคำแนะนำทั้งหมด และไม่จำเป็นต้องใช้วิธีการอีกต่อไป
ตัวอย่างเช่น วัตถุ user
ที่นี่นำไปใช้:
let user = { name: "John", money: 1000, [Symbol.toPrimitive](hint) { alert(`hint: ${hint}`); return hint == "string" ? `{name: "${this.name}"}` : this.money; } }; // conversions demo: alert(user); // hint: string -> {name: "John"} alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500
ดังที่เราเห็นจากโค้ด user
จะกลายเป็นสตริงที่สื่อความหมายด้วยตนเองหรือเป็นจำนวนเงิน ขึ้นอยู่กับการแปลง user[Symbol.toPrimitive]
จัดการกรณีการแปลงทั้งหมด
หากไม่มี Symbol.toPrimitive
แสดงว่า JavaScript จะพยายามค้นหาวิธี toString
และ valueOf
:
"string"
ให้เรียกเมธอด toString
และหากไม่มีอยู่หรือส่งคืนออบเจ็กต์แทนที่จะเป็นค่าดั้งเดิม ให้เรียก valueOf
(ดังนั้น toString
จึงมีลำดับความสำคัญสำหรับการแปลงสตริง)valueOf
และหากไม่มีอยู่หรือหากส่งคืนอ็อบเจ็กต์แทนที่จะเป็นค่าดั้งเดิม ให้เรียก toString
(ดังนั้น valueOf
จึงมีลำดับความสำคัญสำหรับคณิตศาสตร์) วิธี toString
และ valueOf
มาจากสมัยโบราณ ไม่ใช่สัญลักษณ์ (สัญลักษณ์ไม่มีมานานแล้ว) แต่เป็นวิธีการชื่อสตริง "ปกติ" พวกเขาให้ทางเลือก "แบบเก่า" ทางเลือกในการดำเนินการแปลง
วิธีการเหล่านี้จะต้องส่งกลับค่าดั้งเดิม หาก toString
หรือ valueOf
ส่งคืนอ็อบเจ็กต์ ก็จะถูกละเว้น (เหมือนกับไม่มีเมธอด)
ตามค่าเริ่มต้น วัตถุธรรมดาจะมีวิธี toString
และ valueOf
ดังต่อไปนี้:
toString
ส่งกลับสตริง "[object Object]"
valueOf
ส่งคืนออบเจ็กต์เองนี่คือการสาธิต:
let user = {name: "John"}; alert(user); // [object Object] alert(user.valueOf() === user); // true
ดังนั้นหากเราพยายามใช้วัตถุเป็นสตริง เช่น ในการ alert
เราจะเห็น [object Object]
ตามค่าเริ่มต้น
ค่าเริ่มต้นของ valueOf
ถูกกล่าวถึงที่นี่เพื่อความสมบูรณ์เท่านั้น เพื่อหลีกเลี่ยงความสับสน อย่างที่คุณเห็น มันจะส่งคืนออบเจ็กต์เอง และจะถูกละเว้นเช่นกัน อย่าถามฉันว่าทำไม นั่นเป็นเหตุผลทางประวัติศาสตร์ เราก็เลยสรุปได้ว่ามันไม่มีอยู่จริง
ลองใช้วิธีการเหล่านี้เพื่อปรับแต่งการแปลง
ตัวอย่างเช่น user
ที่นี่ทำเช่นเดียวกับด้านบนโดยใช้การรวมกันของ toString
และ valueOf
แทน Symbol.toPrimitive
:
let user = { name: "John", money: 1000, // for hint="string" toString() { return `{name: "${this.name}"}`; }, // for hint="number" or "default" valueOf() { return this.money; } }; alert(user); // toString -> {name: "John"} alert(+user); // valueOf -> 1000 alert(user + 500); // valueOf -> 1500
ดังที่เราเห็นพฤติกรรมจะเหมือนกับตัวอย่างก่อนหน้าของ Symbol.toPrimitive
บ่อยครั้งที่เราต้องการพื้นที่ "ที่รวบรวมทั้งหมด" แห่งเดียวเพื่อจัดการกับ Conversion ดั้งเดิมทั้งหมด ในกรณีนี้ เราสามารถใช้ toString
เท่านั้น เช่นนี้
let user = { name: "John", toString() { return this.name; } }; alert(user); // toString -> John alert(user + 500); // toString -> John500
ในกรณีที่ไม่มี Symbol.toPrimitive
และ valueOf
toString
จะจัดการการแปลงดั้งเดิมทั้งหมด
สิ่งสำคัญที่ต้องรู้เกี่ยวกับวิธีการแปลงแบบดั้งเดิมทั้งหมดคือไม่จำเป็นต้องส่งคืนวิธีดั้งเดิมที่ "บอกใบ้"
ไม่มีการควบคุมว่า toString
จะส่งกลับสตริงทุกประการ หรือว่าเมธอด Symbol.toPrimitive
จะส่งกลับตัวเลขสำหรับคำใบ้ "number"
หรือไม่
สิ่งเดียวที่จำเป็น: วิธีการเหล่านี้จะต้องส่งคืนค่าดั้งเดิม ไม่ใช่วัตถุ
ด้วยเหตุผลทางประวัติศาสตร์ หาก toString
หรือ valueOf
ส่งคืนอ็อบเจ็กต์ ก็ไม่มีข้อผิดพลาด แต่ค่าดังกล่าวจะถูกละเว้น (เช่น ถ้าไม่มีเมธอด) นั่นเป็นเพราะว่าในสมัยโบราณไม่มีแนวคิด "ข้อผิดพลาด" ที่ดีใน JavaScript
ในทางตรงกันข้าม Symbol.toPrimitive
จะเข้มงวดกว่า โดย จะต้อง ส่งคืนค่าดั้งเดิม มิฉะนั้นจะเกิดข้อผิดพลาด
ดังที่เราทราบอยู่แล้ว โอเปอเรเตอร์และฟังก์ชันจำนวนมากทำการแปลงประเภท เช่น การคูณ *
แปลงตัวถูกดำเนินการเป็นตัวเลข
หากเราส่งวัตถุเป็นอาร์กิวเมนต์ การคำนวณจะมีสองขั้นตอน:
ตัวอย่างเช่น:
let obj = { // toString handles all conversions in the absence of other methods toString() { return "2"; } }; alert(obj * 2); // 4, object converted to primitive "2", then multiplication made it a number
obj * 2
จะแปลงวัตถุเป็นแบบดั้งเดิมก่อน (นั่นคือสตริง "2"
)"2" * 2
จะกลายเป็น 2 * 2
(สตริงจะถูกแปลงเป็นตัวเลข)Binary plus จะเชื่อมสตริงเข้าด้วยกันในสถานการณ์เดียวกัน เนื่องจากยินดีรับสตริง:
let obj = { toString() { return "2"; } }; alert(obj + 2); // "22" ("2" + 2), conversion to primitive returned a string => concatenation
การแปลงออบเจ็กต์เป็นดั้งเดิมจะถูกเรียกโดยอัตโนมัติโดยฟังก์ชันและตัวดำเนินการในตัวจำนวนมากที่คาดหวังค่าดั้งเดิมเป็นค่า
มี 3 ประเภท (คำแนะนำ) ของมัน:
"string"
(สำหรับ alert
และการดำเนินการอื่นๆ ที่ต้องใช้สตริง)"number"
(สำหรับคณิตศาสตร์)"default"
(ตัวดำเนินการไม่กี่ตัว ซึ่งโดยปกติแล้วออบเจ็กต์จะนำไปใช้ในลักษณะเดียวกับ "number"
)ข้อมูลจำเพาะอธิบายอย่างชัดเจนว่าตัวดำเนินการใดใช้คำใบ้ใด
อัลกอริธึมการแปลงคือ:
obj[Symbol.toPrimitive](hint)
หากมีวิธีการอยู่"string"
obj.toString()
หรือ obj.valueOf()
สิ่งที่มีอยู่"number"
หรือ "default"
obj.valueOf()
หรือ obj.toString()
สิ่งที่มีอยู่วิธีการทั้งหมดนี้จะต้องคืนค่าดั้งเดิมให้ใช้งานได้ (หากกำหนดไว้)
ในทางปฏิบัติ บ่อยครั้งก็เพียงพอแล้วที่จะใช้เฉพาะ obj.toString()
เป็นเมธอด "catch-all" สำหรับการแปลงสตริงที่ควรส่งคืนการแสดงออบเจ็กต์ "ที่มนุษย์อ่านได้" เพื่อวัตถุประสงค์ในการบันทึกหรือแก้ไขจุดบกพร่อง