ตัวดำเนินการ instanceof
อนุญาตให้ตรวจสอบว่าวัตถุอยู่ในคลาสใดคลาสหนึ่งหรือไม่ นอกจากนี้ยังคำนึงถึงมรดกด้วย
การตรวจสอบดังกล่าวอาจจำเป็นในหลายกรณี ตัวอย่างเช่น สามารถใช้ในการสร้างฟังก์ชัน polymorphic ซึ่งเป็นฟังก์ชันที่ปฏิบัติต่ออาร์กิวเมนต์แตกต่างกันไปตามประเภทของฟังก์ชัน
ไวยากรณ์คือ:
obj อินสแตนซ์ของคลาส
มันจะคืนค่า true
หาก obj
อยู่ใน Class
หรือคลาสที่สืบทอดมาจากคลาสนั้น
ตัวอย่างเช่น:
คลาสแรบบิท {} ให้ rabbit = new Rabbit(); // มันเป็นวัตถุของคลาส Rabbit หรือไม่? การแจ้งเตือน ( อินสแตนซ์กระต่ายของ Rabbit ); // จริง
มันยังใช้งานได้กับฟังก์ชันคอนสตรัคเตอร์ด้วย:
// แทนชั้นเรียน ฟังก์ชั่นกระต่าย() {} การแจ้งเตือน ( กระต่ายใหม่ () อินสแตนซ์ของ Rabbit ); // จริง
…และด้วยคลาสในตัวเช่น Array
:
ให้ arr = [1, 2, 3]; การแจ้งเตือน ( arr อินสแตนซ์ของ Array ); // จริง การแจ้งเตือน ( arr อินสแตนซ์ของวัตถุ ); // จริง
โปรดทราบว่า arr
ยังเป็นของคลาส Object
อีกด้วย นั่นเป็นเพราะว่า Array
ได้รับต้นแบบมาจาก Object
โดยปกติแล้ว instanceof
จะตรวจสอบห่วงโซ่ต้นแบบเพื่อตรวจสอบ นอกจากนี้เรายังสามารถตั้งค่าตรรกะที่กำหนดเองในวิธีคงที่ Symbol.hasInstance
อัลกอริธึมของ obj instanceof Class
ทำงานโดยประมาณดังนี้:
หากมีวิธีการคงที่ Symbol.hasInstance
ให้เรียกมันว่า: Class[Symbol.hasInstance](obj)
ควรส่งคืน true
หรือ false
เท่านี้เราก็เสร็จแล้ว นั่นคือวิธีที่เราสามารถปรับแต่งพฤติกรรมของ instanceof
ได้
ตัวอย่างเช่น:
// ตั้งค่า instanceOf check ที่ถือว่าเป็นเช่นนั้น // สิ่งใดก็ตามที่มีคุณสมบัติ canEat นั้นเป็นสัตว์ สัตว์ชั้น { คงที่ [Symbol.hasInstance] (obj) { ถ้า (obj.canEat) คืนค่าจริง; - - ให้ obj = { canEat: จริง }; การแจ้งเตือน (obj อินสแตนซ์ของสัตว์); // จริง: เรียกว่า Animal[Symbol.hasInstance](obj)
คลาสส่วนใหญ่ไม่มี Symbol.hasInstance
ในกรณีนั้น ตรรกะมาตรฐานจะถูกใช้: obj instanceOf Class
ตรวจสอบว่า Class.prototype
เท่ากับหนึ่งในต้นแบบในห่วงโซ่ต้นแบบ obj
หรือไม่
กล่าวอีกนัยหนึ่งให้เปรียบเทียบกัน:
obj.__proto__ === คลาสต้นแบบ? obj.__proto__.__proto__ === คลาสต้นแบบ? obj.__proto__.__proto__.__proto__ === คลาสต้นแบบ? - // ถ้าคำตอบใดเป็นจริง ให้คืนค่าเป็นจริง // มิฉะนั้น ถ้าเราไปถึงจุดสิ้นสุดของ chain ให้คืนค่า false
ในตัวอย่างข้างต้น rabbit.__proto__ === Rabbit.prototype
ดังนั้นจึงให้คำตอบทันที
ในกรณีที่เป็นมรดก การแข่งขันจะอยู่ที่ขั้นตอนที่สอง:
สัตว์ชั้น {} คลาส Rabbit ขยายสัตว์ {} ให้ rabbit = new Rabbit(); การแจ้งเตือน (ตัวอย่างกระต่ายของสัตว์); // จริง // rabbit.__proto__ === Animal.prototype (ไม่ตรงกัน) // rabbit.__proto__.__proto__ === Animal.prototype (ตรงกัน!)
นี่คือภาพประกอบของสิ่งที่ rabbit instanceof Animal
เปรียบเทียบกับ Animal.prototype
:
ยังมีวิธีการ objA.isPrototypeOf(objB) ที่คืนค่า true
หาก objA
อยู่ที่ไหนสักแห่งในสายโซ่ของต้นแบบสำหรับ objB
ดังนั้นการทดสอบ obj instanceof Class
สามารถใช้ถ้อยคำใหม่เป็น Class.prototype.isPrototypeOf(obj)
มันตลกดี แต่ตัวสร้าง Class
เองไม่ได้มีส่วนร่วมในการตรวจสอบ! เฉพาะสายโซ่ของต้นแบบและ Class.prototype
เท่านั้นที่สำคัญ
ซึ่งอาจนำไปสู่ผลลัพธ์ที่น่าสนใจเมื่อคุณสมบัติ prototype
มีการเปลี่ยนแปลงหลังจากสร้างออบเจ็กต์
ชอบที่นี่:
ฟังก์ชั่นกระต่าย() {} ให้ rabbit = new Rabbit(); // เปลี่ยนต้นแบบ กระต่าย.ต้นแบบ = {}; // ...ไม่ใช่กระต่ายอีกต่อไป! การแจ้งเตือน ( อินสแตนซ์กระต่ายของ Rabbit ); // เท็จ
เรารู้อยู่แล้วว่าวัตถุธรรมดาถูกแปลงเป็นสตริงเป็น [object Object]
:
ให้ obj = {}; การแจ้งเตือน(obj); // [วัตถุวัตถุ] การแจ้งเตือน (obj.toString()); //เหมือนกัน
toString
นั่นคือการใช้งานของพวกเขา แต่มีคุณลักษณะที่ซ่อนอยู่ซึ่งทำให้ toString
มีประสิทธิภาพมากกว่านั้นมาก เราสามารถใช้เป็น typeof
และเป็นทางเลือกสำหรับ instanceof
ฟังดูแปลกเหรอ? อย่างแท้จริง. มาไขปริศนากันดีกว่า
ตามข้อกำหนดแล้ว toString
ในตัวสามารถแยกออกจากออบเจ็กต์และดำเนินการในบริบทของค่าอื่นได้ และผลลัพธ์ของมันขึ้นอยู่กับค่านั้น
สำหรับตัวเลข มันจะเป็น [object Number]
สำหรับบูลีน มันจะเป็น [object Boolean]
สำหรับ null
: [object Null]
สำหรับ undefined
: [object Undefined]
สำหรับอาร์เรย์: [object Array]
…ฯลฯ (ปรับแต่งได้)
มาสาธิตกัน:
// คัดลอกเมธอด toString ลงในตัวแปรเพื่อความสะดวก ให้ objectToString = Object.prototype.toString; //นี่คือประเภทไหนคะ? ให้ arr = []; การแจ้งเตือน ( objectToString.call (arr) ); // [อาร์เรย์วัตถุ]
ที่นี่เราใช้ call ตามที่อธิบายไว้ในบท Decorators และการส่งต่อ call/apply เพื่อดำเนินการฟังก์ชัน objectToString
ในบริบท this=arr
ภายในอัลกอริทึม toString
จะตรวจสอบ this
และส่งกลับผลลัพธ์ที่เกี่ยวข้อง ตัวอย่างเพิ่มเติม:
ให้ s = Object.prototype.toString; การแจ้งเตือน ( s.call(123) ); // [หมายเลขวัตถุ] การแจ้งเตือน ( s.call(null) ); // [วัตถุว่าง] alert( s.call(แจ้งเตือน) ); // [ฟังก์ชั่นวัตถุ]
ลักษณะการทำงานของ Object toString
สามารถปรับแต่งได้โดยใช้คุณสมบัติอ็อบเจ็กต์พิเศษ Symbol.toStringTag
ตัวอย่างเช่น:
ให้ผู้ใช้ = { [Symbol.toStringTag]: "ผู้ใช้" - การแจ้งเตือน ( {}.toString.call(ผู้ใช้) ); // [ผู้ใช้วัตถุ]
สำหรับอ็อบเจ็กต์เฉพาะสภาพแวดล้อมส่วนใหญ่ จะมีคุณสมบัติดังกล่าว ต่อไปนี้คือตัวอย่างเฉพาะของเบราว์เซอร์บางส่วน:
// toStringTag สำหรับวัตถุและคลาสเฉพาะสภาพแวดล้อม: การแจ้งเตือน ( หน้าต่าง [Symbol.toStringTag]); // หน้าต่าง การแจ้งเตือน ( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest การแจ้งเตือน ( {}.toString.call(หน้าต่าง) ); // [หน้าต่างวัตถุ] alert( {}.toString.call(ใหม่ XMLHttpRequest()) ); // [วัตถุ XMLHttpRequest]
อย่างที่คุณเห็น ผลลัพธ์ที่ได้คือ Symbol.toStringTag
(ถ้ามี) พอดี ซึ่งรวมอยู่ใน [object ...]
ในตอนท้าย เรามี "ประเภทของสเตียรอยด์" ที่ไม่เพียงแต่ใช้ได้กับประเภทข้อมูลดั้งเดิมเท่านั้น แต่ยังใช้ได้กับออบเจ็กต์ที่ฝังอยู่และยังสามารถปรับแต่งได้อีกด้วย
เราสามารถใช้ {}.toString.call
แทน instanceof
สำหรับอ็อบเจ็กต์บิวท์อินเมื่อเราต้องการรับประเภทเป็นสตริงแทนที่จะเพียงแค่ตรวจสอบ
เรามาสรุปวิธีการตรวจสอบประเภทที่เรารู้จักกันดีกว่า:
ทำงานเพื่อ | ผลตอบแทน | |
---|---|---|
typeof | ดั้งเดิม | เชือก |
{}.toString | วัตถุดั้งเดิม, วัตถุบิวท์อิน, วัตถุที่มี Symbol.toStringTag | เชือก |
instanceof | วัตถุ | จริง/เท็จ |
ดังที่เราเห็นแล้วว่า {}.toString
เป็น typeof
"ขั้นสูง" ในทางเทคนิค
และตัวดำเนินการ instanceof
ตัวดำเนินการจะโดดเด่นมากเมื่อเราทำงานกับลำดับชั้นของคลาสและต้องการตรวจสอบคลาสโดยคำนึงถึงการสืบทอด
ความสำคัญ: 5
ในโค้ดด้านล่าง เหตุใด instanceof
จึงส่งคืน true
? เราจะเห็นได้ง่ายว่า a
ไม่ได้ถูกสร้างโดย B()
ฟังก์ชัน ก() {} ฟังก์ชัน B() {} A.ต้นแบบ = B.ต้นแบบ = {}; ให้ a = ใหม่ A(); alert( ตัวอย่างของ B ); // จริง
ใช่ ดูแปลกจริงๆ
แต่ instanceof
ไม่สนใจเกี่ยวกับฟังก์ชัน แต่สนใจเกี่ยวกับ prototype
ที่ตรงกับ chain ต้นแบบมากกว่า
และที่นี่ a.__proto__ == B.prototype
ดังนั้น instanceof
จึงส่งคืน true
ดังนั้นตามตรรกะของ instanceof
prototype
จะกำหนดประเภทจริงๆ ไม่ใช่ฟังก์ชันตัวสร้าง