ประเภทคืออะไร? พูดง่ายๆ ก็คือ ประเภทคือการกำหนดความหมายบางอย่างให้กับลำดับไบนารี่ในหน่วยความจำ ตัวอย่างเช่น ลำดับไบนารี 0100 0000 0111 0000 0001 0101 0100 1011 1100 0110 1010 0111 1110 1111 1001 1110 คือ 4643234631018606494 หากมองว่าไม่ได้ลงชื่อเข้าใช้แบบ 64 บิต ประเภท teger 54 กฎสำหรับการแทนเลขฐานสองของเลขทศนิยม (ดูภาคผนวก 1) สองเท่า ความแม่นยำ ประเภทจุดลอยตัวคือ 257.331
ภาษาคอมพิวเตอร์ส่วนใหญ่ใช้ตัวแปรในการจัดเก็บและแสดงข้อมูล บางภาษาระบุประเภทของตัวแปรนี้ไม่สามารถเปลี่ยนแปลงได้ตลอดทั้งโปรแกรม (ไม่ว่าจะในเวลาคอมไพล์หรือรันไทม์) ในทางตรงกันข้าม ตัวแปรใน JavaScript และภาษาอื่นบางภาษาสามารถจัดเก็บประเภทใดก็ได้ และใช้ตัวแปรที่ไม่ได้พิมพ์ ไม่ว่าประเภทตัวแปรจะมีอยู่หรือไม่นั้นไม่เกี่ยวข้องกับไวยากรณ์ ตัวอย่างเช่น C# ยังมีตัวแปรประเภท var อย่างไรก็ตาม คำสั่งต่อไปนี้จะทำให้เกิดข้อผิดพลาดใน C#:
วาร์ a=1;
มี = "สตริง";
เหตุผลก็คือ คีย์เวิร์ด var ของ C# ละเว้นการประกาศประเภทตัวแปรเท่านั้น และอนุมานประเภทตัวแปรโดยอัตโนมัติตามนิพจน์การเริ่มต้น ดังนั้นตัวแปร var ของ C# ยังคงมีประเภทอยู่ ใน JavaScript คุณสามารถกำหนดค่าใดๆ ให้กับตัวแปรเฉพาะได้ตลอดเวลา ดังนั้นตัวแปร JavaScript จึงไม่ถูกพิมพ์
ตามวิธีการออกแบบระบบประเภทภาษาคอมพิวเตอร์ แบ่งได้เป็น 2 ประเภท คือ ชนิดรุนแรงและชนิดอ่อน ความแตกต่างระหว่างทั้งสองอยู่ที่ว่าการแปลงโดยนัยระหว่างประเภทต่างๆ สามารถโปร่งใสต่อผู้ใช้ในระหว่างการคำนวณหรือไม่ จากมุมมองของผู้ใช้ หากภาษาสามารถแปลงทุกประเภทโดยปริยายได้ เมื่อตัวแปร นิพจน์ ฯลฯ มีส่วนเกี่ยวข้องในการดำเนินการ แม้ว่าประเภทจะไม่ถูกต้อง พวกเขายังสามารถได้รับประเภทที่ถูกต้องผ่านการแปลงโดยนัยไปยังผู้ใช้ เหมือนกับว่าทุกประเภทสามารถดำเนินการทุกอย่างได้ ดังนั้น ภาษาดังกล่าวจึงเรียกว่าการพิมพ์แบบอ่อน ในทางตรงกันข้าม อาจไม่จำเป็นต้องมีการแปลงโดยนัยระหว่างประเภทต่างๆ ในภาษาที่พิมพ์อย่างรุนแรง (เช่น C++ เป็นภาษาที่พิมพ์อย่างรุนแรง แต่ double และ int สามารถแปลงเป็นกันในภาษา C++ ได้ แต่จำเป็นต้องมีการร่ายระหว่าง double และ any ประเภทของตัวชี้ )
ประเภทสามารถช่วยให้โปรแกรมเมอร์เขียนโปรแกรมที่ถูกต้องได้ และประเภทเหล่านั้นทำหน้าที่เป็นข้อจำกัดในกระบวนการเขียนโปรแกรมจริง กฎทั่วไปคือ ยิ่งข้อจำกัดเข้มงวดมากเท่าใด ข้อผิดพลาดก็จะยิ่งน้อยลงเท่านั้น แต่การเขียนโปรแกรมจะยุ่งยากมากขึ้นเท่านั้น ภาษาที่พิมพ์อย่างเข้มงวดพร้อมตัวแปรที่มีประเภทมีข้อจำกัดที่เข้มงวดที่สุด และภาษาที่พิมพ์ไม่ชัดเจนซึ่งมีตัวแปรที่ไม่ได้พิมพ์มีข้อจำกัดที่อ่อนแอที่สุด โดยที่ JavaScript เป็นตัวแทนโดยทั่วไป ใน JavaScript เนื่องจากข้อจำกัดค่อนข้างอ่อนแอ ข้อผิดพลาดนี้จึงมีแนวโน้มที่จะเกิดขึ้น:
วา =200;
var b="1";
var c= ก + ข;
คุณอาจคาดหวังว่า c จะเป็น 201 แต่จริงๆ แล้วมันคือ "2001" ซึ่งเป็นข้อผิดพลาดที่ไม่เคยเกิดขึ้นในภาษาที่พิมพ์อย่างรุนแรง อย่างไรก็ตาม เนื่องจาก JavaScript ไม่มีข้อจำกัดเหล่านี้ จึงสามารถเชื่อมประเภทตัวเลขและสตริงเข้าด้วยกันได้อย่างง่ายดาย ดังนั้นข้อจำกัดและความยืดหยุ่นจึงเป็นชุดคุณลักษณะที่จำเป็นต้องมีความสมดุลสำหรับนักออกแบบภาษาเสมอ
ประเภทคือข้อจำกัดที่ทำงานผ่านการตรวจสอบประเภท ในภาษาต่างๆ การตรวจสอบประเภททำงานในขั้นตอนที่แตกต่างกัน ซึ่งสามารถแบ่งออกเป็นการตรวจสอบเวลาคอมไพล์และการตรวจสอบรันไทม์ สำหรับภาษาที่แปลเช่น JavaScript มีขั้นตอนที่คล้ายกับกระบวนการคอมไพล์คือการวิเคราะห์คำศัพท์และการวิเคราะห์ไวยากรณ์หากการตรวจสอบประเภทของภาษาที่แปลเสร็จสิ้นในระหว่างการวิเคราะห์ไวยากรณ์หรือขั้นตอนก่อนหน้าก็สามารถพิจารณาได้เช่นกัน คล้ายกับการตรวจสอบเวลาคอมไพล์ ดังนั้นข้อความที่สมเหตุสมผลกว่าคือการตรวจสอบประเภทคงที่และการตรวจสอบประเภทแบบไดนามิก
สิ่งที่น่าสนใจคือแม้ว่าการตรวจสอบหลายภาษาในขณะคอมไพล์ แต่ข้อมูลประเภทดังกล่าวยังคงสามารถรับได้ในขณะรันไทม์ ตัวอย่างเช่น C# ใช้ข้อมูลเมตาเพื่อบันทึกข้อมูลประเภท ในระหว่างรันไทม์ ผู้ใช้สามารถรับและใช้ประเภทต่างๆ ผ่านการสะท้อนกลับ
JavaScript ให้ความสำคัญกับความยืดหยุ่นในทุกแง่มุมของการออกแบบ ดังนั้นจึงใช้การตรวจสอบประเภทไดนามิกและไม่ตรวจสอบประเภทอย่างจริงจัง ยกเว้นเมื่อดำเนินการเฉพาะน้อยมาก คุณสามารถรับข้อมูลประเภทของตัวแปรหรือนิพจน์ใดๆ ขณะรันไทม์ และตรวจสอบความถูกต้องผ่านลอจิกของโปรแกรม
มี 9 ประเภทที่ระบุไว้ในมาตรฐาน JavaScript: Unknown Null Boolean String Number Object Reference List Completion
ในบรรดาสิ่งเหล่านั้น การเสร็จสิ้นรายการอ้างอิงทั้งสามประเภทจะใช้เฉพาะระหว่างรันไทม์การแยกวิเคราะห์ภาษาเท่านั้น และไม่สามารถเข้าถึงได้โดยตรงจากโปรแกรม ด้านล่างนี้เราสามารถเรียนรู้เกี่ยวกับหกประเภทเหล่านี้:
ประเภทที่ไม่ได้กำหนดมีเพียงค่าเดียวที่ไม่ได้กำหนดซึ่งเป็นค่าเมื่อตัวแปรไม่ได้กำหนดค่า ใน JS วัตถุส่วนกลางมีคุณสมบัติที่ไม่ได้กำหนดซึ่งแสดงถึงไม่ได้กำหนดไว้ อันที่จริง undefed ไม่ใช่คำหลักใน JavaScript กำหนดค่าให้กับคุณสมบัติที่ไม่ได้กำหนดทั่วโลกเพื่อเปลี่ยนค่า
ประเภท Null ยังมีค่าเพียงค่าเดียวเท่านั้น นั่นคือ null แต่ JavaScript จะให้คีย์เวิร์ดเป็น null เพื่อแสดงค่าเฉพาะนี้ ความหมายของประเภท Null คือ "การอ้างอิงวัตถุที่ว่างเปล่า"
บูลีนมีสองค่า: จริงและเท็จ
การตีความอย่างเป็นทางการของประเภท String คือลำดับของประเภทจำนวนเต็ม 16 บิตที่ไม่ได้ลงนาม ซึ่งจริงๆ แล้วใช้เพื่อแสดงข้อมูลข้อความที่เข้ารหัสใน UTF-16
Number ของ JavaScript มีค่าทั้งหมด 18437736874454810627 (นั่นคือ 264-253 +3) หมายเลขของ JavaScript ถูกจัดเก็บในรูปแบบจุดทศนิยมที่มีความแม่นยำสองเท่า ยกเว้นว่า 9007199254740990 แสดงถึง NaN ซึ่งสอดคล้องกับ IEEE 754 (ดูภาคผนวก 1) และใช้พื้นที่ 64 บิตและ 8 ไบต์
ประเภทที่ซับซ้อนที่สุดใน JavaScript คือ Object ซึ่งเป็นคอลเลกชันที่ไม่เรียงลำดับของชุดคุณสมบัติ ฟังก์ชั่นคือ Object ที่ใช้คุณสมบัติส่วนตัว [[call]] โฮสต์ JavaScript ยังสามารถจัดเตรียมอ็อบเจ็กต์พิเศษบางอย่างได้
ฉันเคยพูดถึงประเภทที่ระบุไว้ในมาตรฐาน JS แล้ว อย่างไรก็ตาม ปัญหาที่ไม่สามารถละเลยได้คือมาตรฐาน JS ถูกเขียนขึ้นสำหรับผู้ใช้ JS ไม่จำเป็นต้องกำหนดประเภทตามมาตรฐาน เนื่องจาก JS คือ เมื่อดำเนินการ . ประเภทที่ไม่ใช่วัตถุจะถูกแปลงเป็นวัตถุที่สอดคล้องกันโดยอัตโนมัติ ดังนั้น "str".length จึงเทียบเท่ากับ (new String("str")).length จากมุมมองนี้ ไม่ใช่ความคิดที่ดีที่ทั้งคู่จะเป็นประเภทเดียวกัน เราใช้คุณสมบัติภาษาบางอย่างใน JS เพื่อทำการเลือกปฏิบัติประเภทรันไทม์ แต่ผลลัพธ์ของวิธีการเหล่านี้แตกต่างออกไป คุณต้องตัดสินใจว่าวิธีใดดีกว่าหรือแย่กว่า
Typeof เป็นตัวดำเนินการในภาษา JS จากมุมมองที่แท้จริง เห็นได้ชัดว่าใช้เพื่อรับประเภท ตามมาตรฐาน JavaScript typeof ได้รับการแสดงสตริงของชื่อประเภทตัวแปร บูล, ตัวเลข, ไม่ได้กำหนด, อ็อบเจ็กต์, ฟังก์ชัน และมาตรฐาน JavaScript ช่วยให้ผู้นำไปใช้ปรับแต่งค่าประเภทของอ็อบเจ็กต์บางตัวได้
มีรายการคำอธิบายดังกล่าวในมาตรฐาน JS:
พิมพ์ | ผลลัพธ์ |
ไม่ได้กำหนด | "ไม่ได้กำหนด" |
โมฆะ | "วัตถุ" |
บูลีน | "บูลีน" |
ตัวเลข | "ตัวเลข" |
สตริง | "สตริง" |
วัตถุ (เนทิฟและไม่ได้ใช้ [[โทร]]) | "วัตถุ" |
วัตถุ (เนทีฟและนำไปใช้ [[โทร]]) | "การทำงาน" |
วัตถุ (โฮสต์) | ขึ้นอยู่กับการนำไปปฏิบัติ |
ตัวอย่างต่อไปนี้มาจาก Rimifon ของ 51js ซึ่งแสดงสถานการณ์ที่ผลลัพธ์ของ typeof ใน IE สร้าง "date" และ "unknown":
var xml=document.createElement("xml");
var rs=xml.บันทึก;
rs.Fields.Append("วันที่", 7, 1);
rs.Fields.Append("bin", 205, 1);
rs.เปิด();
rs.AddNew();
rs.Fields.Item("วันที่").Value = 0;
rs.Fields.Item("bin").Value = 21704;
rs.ปรับปรุง();
วันที่ var = rs.Fields.Item("date").Value;
var bin = rs.Fields.Item("bin").ค่า;
rs.ปิด();
การแจ้งเตือน (วันที่);
การแจ้งเตือน (ถังขยะ);
alert([ประเภทวันที่, ประเภทถัง]);
ลอง{alert(date.getDate())}จับ(ผิดพลาด){alert(err.message)}
มีการวิพากษ์วิจารณ์มากมายเกี่ยวกับวิธีการตัดสินนี้ซึ่งใกล้เคียงกับซีแมนทิกส์ "ประเภท" มากที่สุด หนึ่งในนั้นคือไม่สามารถแยกแยะวัตถุที่แตกต่างกันได้ สตริงใหม่ ("abc") และหมายเลขใหม่ (123) ไม่สามารถแยกแยะได้โดยใช้ typeof การเขียนโปรแกรม JS มักใช้อ็อบเจ็กต์ต่าง ๆ จำนวนมาก และ typeof สามารถให้ผลลัพธ์ "อ็อบเจ็กต์" ที่คลุมเครือสำหรับอ็อบเจ็กต์ทั้งหมด ซึ่งจะลดการใช้งานจริงลงอย่างมาก
ความหมายของ instanceof ถูกแปลเป็นภาษาจีนว่า "is an instance of..." เข้าใจตามตัวอักษร มันเป็นคำที่อิงตามการเขียนโปรแกรมเชิงวัตถุแบบคลาส และ JS ไม่ได้ให้การสนับสนุนการเขียนโปรแกรมแบบคลาสที่ ระดับภาษา แม้ว่ามาตรฐานจาวาสคริปต์ไม่ได้กล่าวถึงคำใดคำหนึ่ง แต่ในความเป็นจริงแล้ว การออกแบบออบเจ็กต์ในตัวและการตั้งค่าตัวดำเนินการทั้งหมดบ่งบอกถึงวิธีที่ "เป็นทางการ" ในการใช้คลาส กล่าวคือ จากการใช้ฟังก์ชันเป็นคลาส เมื่อตัวดำเนินการใหม่ทำหน้าที่ บนฟังก์ชัน คุณลักษณะต้นแบบของฟังก์ชันถูกตั้งค่าเป็นต้นแบบของวัตถุที่สร้างขึ้นใหม่ และฟังก์ชันนั้นถูกใช้เป็นตัวสร้าง
ดังนั้นวัตถุที่สร้างขึ้นจากการดำเนินการใหม่ของฟังก์ชันเดียวกันจึงถือเป็นอินสแตนซ์ของคลาส สิ่งที่วัตถุเหล่านี้มีเหมือนกันคือ: 1. พวกมันมีต้นแบบเดียวกัน และ 2. พวกมันถูกประมวลผลโดยคอนสตรัคเตอร์ตัวเดียวกัน และ instanceof เป็นตัวดำเนินการที่ตรวจสอบว่า "อินสแตนซ์นั้นเป็นของคลาสหรือไม่" ร่วมกับวิธีการนำคลาสไปใช้วิธีนี้ คุณสามารถเดาได้ว่าเป็นเรื่องยากมากที่จะตรวจสอบว่าวัตถุได้รับการประมวลผลโดย Constructor หรือไม่ แต่จะง่ายกว่ามากในการตรวจสอบว่าต้นแบบของมันคืออะไร ดังนั้นการนำ instanceof ไปใช้จึงเป็นที่เข้าใจจากมุมมองของต้นแบบ ซึ่งก็คือ ตรวจสอบว่าแอตทริบิวต์ [ [ต้นแบบ]] สอดคล้องกับต้นแบบของฟังก์ชันเฉพาะ โปรดทราบว่า [[prototype]] ที่นี่เป็นทรัพย์สินส่วนตัว ซึ่งสามารถเข้าถึงได้โดยใช้ __proto__ ใน SpiderMonkey (ซึ่งเป็นกลไก JS ของ Firefox)
ต้นแบบมีความหมายสำหรับประเภทวัตถุที่อธิบายไว้ในมาตรฐานเท่านั้น ดังนั้น instanceof จะได้รับค่าเท็จสำหรับวัตถุที่ไม่ใช่วัตถุทั้งหมด และ instanceof สามารถระบุได้ว่าเป็นของประเภทใดประเภทหนึ่งเท่านั้น แต่ไม่สามารถรับประเภทนั้นได้ instanceof ก็ชัดเจนเช่นกัน มันสามารถแยกแยะตัวเองได้
ในความเป็นจริง instanceof สามารถถูกหลอกได้ แม้ว่าแอตทริบิวต์ส่วนตัว [[prototype]] ของออบเจ็กต์ที่ใช้นั้นไม่สามารถเปลี่ยนแปลงได้ แต่ต้นแบบของฟังก์ชันนั้นเป็นคุณลักษณะสาธารณะ รหัสต่อไปนี้แสดงวิธีการหลอกลวง instanceof
ฟังก์ชั่นคลาสA(){};
ฟังก์ชั่นคลาสB(){};
var o = new ClassA();//สร้างอ็อบเจ็กต์ของคลาส A
ClassB.prototype = ClassA.prototype; // แทนที่ ClassB.prototype
alert(o ตัวอย่างของ ClassB)//การหลอกลวงสำเร็จ - -!
Object.prototype.toString เดิมนั้นยากต่อการเรียก คลาสในตัว JavaScript ทั้งหมดครอบคลุมวิธีการ toString สำหรับวัตถุที่สร้างโดยคลาสที่ไม่ใช่บิวท์อิน Object.prototype.toString สามารถรับเฉพาะ [object Object ] ที่ไม่มีความหมายเท่านั้น ผลลัพธ์. ดังนั้นเป็นเวลานานแล้วที่ยังไม่มีการค้นพบเอฟเฟกต์เวทย์มนตร์ของฟังก์ชันนี้
ตามมาตรฐานคำอธิบายของ Object.prototype.toString มีเพียง 3 ประโยคเท่านั้น
1. รับแอตทริบิวต์ [[class]] ของวัตถุนี้
2. คำนวณสตริงโดยการต่อสามสตริงเข้าด้วยกัน "[object ", result(1) และ "]"
3. ส่งคืนผลลัพธ์ (2)
เห็นได้ชัดว่า Object.prototype.toString จริง ๆ แล้วเพิ่งได้รับแอตทริบิวต์ [[class]] ของวัตถุ แต่ฉันไม่รู้ว่ามันเป็นความตั้งใจหรือไม่ วัตถุฟังก์ชันในตัว JS ทั้งหมด String Number Array RegExp... จะถูกใช้ทั้งหมด เมื่อใช้ new เพื่อสร้างวัตถุ ตั้งค่าแอตทริบิวต์ [[class]] เพื่อให้แอตทริบิวต์ [[class]] สามารถใช้เป็นพื้นฐานที่ดีในการตัดสินประเภท
เนื่องจาก Object.prototype.toString รับคุณสมบัติของอ็อบเจ็กต์นี้ คุณสามารถระบุอ็อบเจ็กต์นี้ และจากนั้น รับชนิดโดยใช้ Object.prototype.toString.call หรือ Object.prototype.toString.apply
แม้ว่า Object.prototype.toString จะฉลาด แต่ก็ไม่สามารถรับประเภทของอ็อบเจ็กต์ที่สร้างโดยฟังก์ชันแบบกำหนดเองได้ เนื่องจากฟังก์ชันแบบกำหนดเองไม่ได้ตั้งค่า [[คลาส]] และไม่สามารถเข้าถึงคุณสมบัติส่วนตัวนี้ได้ในโปรแกรม ข้อได้เปรียบที่ใหญ่ที่สุดของ Object.prototype.toString คือมันสามารถสร้าง 1 และ new Number(1) ให้เป็นอ็อบเจ็กต์ประเภทเดียวกัน โดยส่วนใหญ่ทั้งสองถูกใช้ในลักษณะเดียวกัน
อย่างไรก็ตาม เป็นที่น่าสังเกตว่าเมื่อ new Boolean(false) เข้าร่วมในการดำเนินการ bool ผลลัพธ์จะตรงกันข้ามกับ false หากทั้งสองถือเป็นประเภทเดียวกันในเวลานี้ จะทำให้เกิดข้อผิดพลาดที่ยากต่อการแก้ไขได้อย่างง่ายดาย ตรวจสอบ.
เพื่อเปรียบเทียบวิธีการตัดสินสามประเภทข้างต้น ฉันจึงจัดทำตารางเพื่อให้ทุกคนสามารถเปรียบเทียบวิธีการต่างๆ โดยรวมได้ เพื่อความสะดวกในการเปรียบเทียบ ฉันได้รวมผลลัพธ์ที่ได้รับจากวิธีการตัดสินหลายวิธี:
วัตถุ | ประเภทของ | อินสแตนซ์ของ | Object.prototype.toString | มาตรฐาน |
"เอบีซี" | สตริง | - | สตริง | สตริง |
สตริงใหม่ ("abc") | วัตถุ | สตริง | สตริง | วัตถุ |
ฟังก์ชั่นสวัสดี(){} | การทำงาน | การทำงาน | การทำงาน | วัตถุ |
123 | ตัวเลข | - | ตัวเลข | ตัวเลข |
ใหม่จำนวน(123) | วัตถุ | ตัวเลข | ตัวเลข | วัตถุ |
อาร์เรย์ใหม่ (1,2,3) | วัตถุ | อาร์เรย์ | อาร์เรย์ | วัตถุ |
ใหม่MyType() | วัตถุ | ประเภทของฉัน | วัตถุ | วัตถุ |
โมฆะ | วัตถุ | - | วัตถุ | โมฆะ |
ไม่ได้กำหนด | ไม่ได้กำหนด | - | วัตถุ | ไม่ได้กำหนด |
ในความเป็นจริง เป็นการยากที่จะบอกว่าวิธีใดข้างต้นสมเหตุสมผลกว่า แม้แต่ข้อกำหนดในมาตรฐานก็สะท้อนถึงกลไกรันไทม์ของ JS เท่านั้นมากกว่าแนวทางปฏิบัติที่ดีที่สุด ความเห็นส่วนตัวของฉันคือการมองข้ามแนวคิดของ "ประเภท" และมุ่งเน้นไปที่ข้อ จำกัด ของ "ฉันต้องการใช้วัตถุนี้อย่างไร" การใช้ typeof และ instanceof เพื่อตรวจสอบสามารถบรรลุผลเช่นเดียวกับภาษาที่พิมพ์อย่างรุนแรงเมื่อจำเป็น
บิตเครื่องหมาย: ใช้เพื่อแสดงสัญญาณบวกและลบ
เลขยกกำลัง: ใช้แทนเลขยกกำลัง
mantissa (mantissa): ใช้เพื่อระบุความถูกต้อง