เหตุใด 0 [0] จึงถูกต้องตามหลักไวยากรณ์


119

เหตุใดบรรทัดนี้จึงถูกต้องในจาวาสคริปต์

var a = 0[0];

หลังจากนั้นaคือundefined.


4
เป็นtrue[0]หรือ ""[0]
Hacketo

24
@CodeAngry เพื่อความยุติธรรม JavaScript ถือกำเนิดขึ้นภายใน HTML และเป็น HTML ที่เริ่มต้น "โยนอะไรก็ได้ที่คุณชอบใส่ฉันแล้วฉันจะพยายามทำให้เข้าใจ"
Niet the Dark Absolute

8
@NiettheDarkAbsol เพื่อความเป็นธรรมคุณคิดผิดเพียงเพราะไวยากรณ์ไม่สมเหตุสมผล (ยังไม่มาก) นี่เป็นเพียงการรับคุณสมบัติที่ตั้งชื่อ"0"ของnew Number(0)วัตถุ
meandre

มันเป็นสมมติฐานที่ผิดที่จะไม่ได้กำหนดไว้เสมอ เป็นไปได้อย่างสมบูรณ์แบบที่จะ0[0]ส่งคืนค่า
Rune FS

@meandre 0["toString"]เยี่ยมมากขอบคุณที่ชี้ให้ดู
Jonathan

คำตอบ:


169

เมื่อคุณทำ0[0], ล่าม JS จะเปิดคนแรกที่0เป็นNumberวัตถุแล้วพยายามเข้าถึงคุณสมบัติของวัตถุที่มีอยู่[0]undefined

ไม่มีข้อผิดพลาดทางไวยากรณ์เนื่องจากไวยากรณ์การเข้าถึงคุณสมบัติ0[0]ได้รับอนุญาตโดยไวยากรณ์ภาษาในบริบทนี้ โครงสร้างนี้ (โดยใช้ข้อตกลงในไวยากรณ์ Javascript) NumericLiteral[NumericLiteral]เป็น

ส่วนที่เกี่ยวข้องของไวยากรณ์ภาษาจากส่วน A.3ของข้อกำหนด ES5 ECMAScript คือ:

Literal ::
    NullLiteral
    BooleanLiteral
    NumericLiteral
    StringLiteral
    RegularExpressionLiteral

PrimaryExpression :
    this
    Identifier
    Literal
    ArrayLiteral
    ObjectLiteral
    ( Expression )

MemberExpression :
    PrimaryExpression
    FunctionExpression
    MemberExpression [ Expression ]
    MemberExpression . IdentifierName
    new MemberExpression Arguments    

ดังนั้นเราสามารถติดตามตัวแกรมมาร์ผ่านความก้าวหน้านี้:

MemberExpression [ Expression ]
PrimaryExpression [ Expression ]
Literal [ Expression ]
NumericLiteral [ Expression ]

และในExpressionที่สุดก็สามารถเป็นNumericLiteralเช่นนั้นได้เช่นกันหลังจากทำตามไวยากรณ์แล้วเราจะเห็นว่าสิ่งนี้ได้รับอนุญาต:

NumericLiteral [ NumericLiteral ]

ซึ่งหมายความว่า0[0]เป็นส่วนที่อนุญาตของไวยากรณ์จึงไม่มี SyntaxError


จากนั้นในขณะรันไทม์คุณจะได้รับอนุญาตให้อ่านคุณสมบัติที่ไม่มีอยู่ (มันจะถูกอ่านเป็นundefined) ตราบเท่าที่แหล่งที่มาที่คุณกำลังอ่านอยู่เป็นวัตถุหรือมีการแปลงโดยนัยเป็นวัตถุ และลิเทอรัลตัวเลขจะมีการแปลงโดยปริยายเป็นอ็อบเจกต์ (อ็อบเจ็กต์ Number)

นี่เป็นหนึ่งในคุณสมบัติของ Javascript ที่มักไม่รู้จัก ประเภทNumber, BooleanและStringใน Javascript จะถูกเก็บไว้ภายในเป็นพื้นฐาน (ไม่ใช่วัตถุเต็มเป่า) สิ่งเหล่านี้เป็นการแสดงพื้นที่จัดเก็บที่กะทัดรัดและไม่เปลี่ยนรูป (อาจทำได้ด้วยวิธีนี้เพื่อประสิทธิภาพในการนำไปใช้งาน) แต่ Javascript ต้องการให้คุณสามารถปฏิบัติกับวัตถุดั้งเดิมเหล่านี้ได้เช่นเดียวกับวัตถุที่มีคุณสมบัติและวิธีการ ดังนั้นหากคุณพยายามเข้าถึงคุณสมบัติหรือวิธีการที่ไม่ได้รับการสนับสนุนโดยตรงบนดั้งเดิม Javascript จะบังคับให้วัตถุดั้งเดิมเป็นประเภทของออบเจ็กต์ที่เหมาะสมชั่วคราวโดยกำหนดค่าเป็นค่าดั้งเดิม

เมื่อคุณใช้ไวยากรณ์ที่เหมือนวัตถุบนพื้นฐานเช่น0[0]ล่ามจะรับรู้ว่าสิ่งนี้เป็นการเข้าถึงคุณสมบัติบนพื้นฐาน การตอบสนองต่อสิ่งนี้คือการใช้0ตัวเลขตัวแรกและบีบบังคับให้กลายเป็นNumberวัตถุที่เต็มไปด้วยวัตถุระเบิดซึ่งจะสามารถเข้าถึง[0]คุณสมบัติได้ ในกรณีนี้โดยเฉพาะที่[0]สถานที่ให้บริการบนวัตถุจำนวนเป็นซึ่งเป็นเหตุผลที่ว่าค่าที่คุณได้รับจากundefined0[0]

นี่คือบทความเกี่ยวกับการแปลงวัตถุดั้งเดิมเป็นวัตถุโดยอัตโนมัติเพื่อวัตถุประสงค์ในการจัดการกับคุณสมบัติ:

ชีวิตลับของ Javascript Primitives


นี่คือส่วนที่เกี่ยวข้องของข้อกำหนด ECMAScript 5.1:

9.10 CheckObjectCoercible

พ่น TypeError ถ้าค่าเป็นundefinedหรือมิฉะนั้นผลตอบแทนnulltrue

ใส่คำอธิบายภาพที่นี่

11.2.1 ผู้เข้าถึงทรัพย์สิน

  1. ให้ baseReference เป็นผลลัพธ์ของการประเมิน MemberExpression
  2. ให้ baseValue เป็น GetValue (baseReference)
  3. ให้ propertyNameReference เป็นผลลัพธ์ของการประเมิน Expression
  4. ให้ propertyNameValue เป็น GetValue (propertyNameReference)
  5. เรียก CheckObjectCoercible (baseValue)
  6. ให้ propertyNameString เป็น ToString (propertyNameValue)
  7. หากการผลิตทางวากยสัมพันธ์ที่กำลังประเมินอยู่ในโค้ดโหมดที่เข้มงวดให้เข้มงวดเป็นจริงมิฉะนั้นให้เข้มงวดเป็นเท็จ
  8. ส่งคืนค่าของชนิด Reference ที่มีค่าพื้นฐานเป็น baseValue และมีชื่อที่อ้างอิงคือ propertyNameString และมีแฟล็กโหมดเข้มงวดเข้มงวด

ส่วนการดำเนินการสำหรับคำถามนี้คือขั้นตอน # 5 ข้างต้น

8.7.1 GetValue (V)

สิ่งนี้อธิบายถึงวิธีการที่ค่าที่เข้าถึงเป็นการอ้างอิงคุณสมบัติจะเรียกToObject(base)รับเวอร์ชันอ็อบเจ็กต์ของดั้งเดิม

9.9 ToObject

นี้อธิบายวิธีBoolean, NumberและStringวิทยาการจะถูกแปลงเป็นรูปแบบวัตถุกับ [[PrimitiveValue]] ชุดคุณสมบัติภายในตาม


เป็นการทดสอบที่น่าสนใจหากโค้ดเป็นเช่นนี้:

var x = null;
var a = x[0];

มันจะยังคงไม่โยน SyntaxError ในเวลาแยกวิเคราะห์เนื่องจากเป็นไวยากรณ์ทางเทคนิคทางเทคนิค แต่จะส่ง TypeError ที่รันไทม์เมื่อคุณเรียกใช้รหัสเนื่องจากเมื่อตรรกะของ Property Accessors ข้างต้นถูกนำไปใช้กับค่าของxมันจะเรียกCheckObjectCoercible(x)หรือเรียกToObject(x)สิ่งที่ ทั้งสองจะโยน TypeError ถ้าxเป็นหรือnullundefined


0[1,2]ก็ใช้ได้หมายความว่าอย่างไร? (ฉันอัปเดตคำถาม)
Michael M.

และจะไม่ทำให้เกิดข้อผิดพลาดทางไวยากรณ์เนื่องจากการเข้าถึงคุณสมบัติในสิ่งใดก็ตามที่ไม่ใช่nullหรือไม่undefinedดีทั้งหมดแม้ว่าคุณสมบัติเหล่านั้นจะไม่มีอยู่ก็ตาม
user4642212

6
@ ไมเคิลไม่จำเป็นต้องอัปเดต นั่นคือตัวดำเนินการลูก0[2]
น้ำมัน

1
ตัวดำเนินการจุลภาค: ประเมินทั้ง 1 และ 2 ใน1,2แต่ส่งกลับ 2
user4642212

2
นั่นเป็นคำตอบที่ยอดเยี่ยมสำหรับความแตกต่างของคำถาม
tbh__

20

เช่นเดียวกับภาษาโปรแกรมส่วนใหญ่ JS ใช้ไวยากรณ์เพื่อแยกวิเคราะห์รหัสของคุณและแปลงเป็นรูปแบบปฏิบัติการ หากไม่มีกฎในไวยากรณ์ที่สามารถนำไปใช้กับโค้ดบางส่วนได้ก็จะพ่น SyntaxError มิฉะนั้นรหัสจะถือว่าถูกต้องไม่ว่าจะสมเหตุสมผลหรือไม่ก็ตาม

ส่วนที่เกี่ยวข้องของไวยากรณ์ JSคือ

Literal :: 
   NumericLiteral
   ...

PrimaryExpression :
   Literal
   ...

MemberExpression :
   PrimaryExpression
   MemberExpression [ Expression ]
   ...

เนื่องจาก0[0]เป็นไปตามกฎเหล่านี้จึงถือว่าเป็นนิพจน์ที่ถูกต้อง ไม่ว่าจะถูกต้องหรือไม่ (เช่นไม่ทำให้เกิดข้อผิดพลาดขณะทำงาน) เป็นอีกเรื่องหนึ่ง แต่ใช่ นี่คือวิธีที่ JS ประเมินนิพจน์เช่นsomeLiteral[someExpression]:

  1. ประเมินsomeExpression(ซึ่งอาจซับซ้อนโดยพลการ)
  2. แปลงลิเทอรัลเป็นประเภทอ็อบเจ็กต์ที่สอดคล้องกัน (ตัวเลขลิเทอรัล => Number, สตริง => Stringฯลฯ )
  3. เรียกการget propertyดำเนินการกับผลลัพธ์ (2) ด้วยผลลัพธ์ชื่อคุณสมบัติ (1)
  4. ทิ้งผลลัพธ์ (2)

ดังนั้นจึง0[0]ถูกตีความว่า

index = 0
temp = Number(0)
result = getproperty(temp, index) // it's undefined, but JS doesn't care
delete temp
return result

นี่คือตัวอย่างของความถูกต้องแต่ไม่ถูกต้องแสดงออก:

null[0]

แยกวิเคราะห์ได้ดี แต่ในขณะดำเนินการล่ามจะล้มเหลวในขั้นตอนที่ 2 (เนื่องจากnullไม่สามารถแปลงเป็นวัตถุได้) และแสดงข้อผิดพลาดขณะทำงาน


1
มีอะไรมากกว่านั้น var x = null; var a = x[0];ไม่สร้างข้อผิดพลาดทางไวยากรณ์ แต่จะส่ง TypeError ที่รันไทม์
jfriend00

@ jfriend00: นั่นไม่ใช่สิ่งที่เป็นคำถาม แต่เพิ่มเข้าไป
georg

ผลลัพธ์ไม่จำเป็นต้องไม่ถูกกำหนด เป็นไปได้ที่0[0]จะส่งคืนค่าแทนการไม่ได้กำหนด
Rune FS

9

มีสถานการณ์ที่คุณสามารถห้อยตัวเลขใน Javascript ได้อย่างถูกต้อง:

-> 0['toString']
function toString() { [native code] }

แม้ว่าจะไม่ชัดเจนในทันทีว่าทำไมคุณถึงต้องการทำสิ่งนี้การสมัครสมาชิกใน Javascript จะเทียบเท่ากับการใช้สัญกรณ์แบบจุด (แม้ว่าสัญกรณ์จุดจะ จำกัด ให้คุณใช้ตัวระบุเป็นคีย์)


@AmitJoki นี่เหมือนกับ(0).toString(โดยไม่เรียกใช้ฟังก์ชัน) เป็นคุณสมบัติของประเภทตัวเลข
user4642212

@AmitJoki เพราะมันตอบคำถามว่า "ทำไมบรรทัดนี้ถึงใช้ได้"
Duncan

@ ดันแคน แต่นี่คือ "เครื่องหมายวงเล็บคืออะไร" และฉันคิดว่า OP รู้ดี ความจริงที่ว่ามันถูกตีความว่าเป็นวัตถุ Number จากนั้น0คุณสมบัติจึงถูกเข้าถึงและเนื่องจากไม่มีอยู่จริงundefinedจึงถูกต้องกว่าตามที่อธิบายไว้ใน jfriend00
Amit Joki

@AmitJoki เป็นสมมติฐานที่ไม่ถูกต้องที่0[0]จะกลับมาไม่ได้กำหนด มีแนวโน้มว่าจะเป็นไปได้ แต่ไม่จำเป็นต้องเป็นเช่นนั้น
Rune FS

9

ฉันแค่อยากจะทราบว่าไวยากรณ์ที่ถูกต้องนี้ไม่ได้ใช้เฉพาะกับ Javascript แต่อย่างใด ภาษาส่วนใหญ่จะมีข้อผิดพลาดรันไทม์หรือข้อผิดพลาดประเภท แต่นั่นไม่ใช่สิ่งเดียวกับข้อผิดพลาดทางไวยากรณ์ Javascript เลือกที่จะส่งคืนที่ไม่ได้กำหนดไว้ในหลาย ๆ สถานการณ์ที่ภาษาอื่นอาจเพิ่มข้อยกเว้นรวมถึงเมื่อการสมัครสมาชิกอ็อบเจ็กต์ที่ไม่มีคุณสมบัติของชื่อที่กำหนด

ไวยากรณ์ไม่ทราบประเภทของนิพจน์ (แม้แต่นิพจน์ธรรมดาเช่นลิเทอรัลตัวเลข) และจะช่วยให้คุณสามารถใช้โอเปอเรเตอร์กับนิพจน์ใดก็ได้ ตัวอย่างเช่นพยายามที่จะห้อยundefinedหรือnullทำให้เกิดTypeErrorใน Javascript ไม่ใช่ข้อผิดพลาดทางไวยากรณ์ - หากสิ่งนี้ไม่เคยดำเนินการ (อยู่ผิดด้านของ if-statement) จะไม่ทำให้เกิดปัญหาใด ๆ ในขณะที่ข้อผิดพลาดทางไวยากรณ์เกิดจากคำจำกัดความที่จับได้ในเวลาคอมไพล์เสมอ (eval, Function ฯลฯ ทั้งหมดนับเป็นการรวบรวม)


8

เนื่องจากเป็นไวยากรณ์ที่ถูกต้องและแม้แต่รหัสที่ถูกต้องในการตีความ คุณสามารถพยายามเข้าถึงคุณสมบัติของวัตถุใด ๆ (และในกรณีนี้ 0 จะถูกส่งไปยังวัตถุที่เป็นตัวเลข) และจะให้ค่าแก่คุณหากมีอยู่หรือไม่ได้กำหนดไว้ อย่างไรก็ตามการพยายามเข้าถึงคุณสมบัติของ undefined ไม่ได้ผลดังนั้น 0 [0] [0] จะทำให้เกิดข้อผิดพลาดรันไทม์ สิ่งนี้จะยังคงจัดเป็นไวยากรณ์ที่ถูกต้องแม้ว่า มีความแตกต่างของไวยากรณ์ที่ถูกต้องและสิ่งที่จะไม่ทำให้เกิดข้อผิดพลาด runtime / compiletime


3

ไวยากรณ์ไม่เพียง แต่ถูกต้อง แต่ผลลัพธ์ก็ไม่จำเป็นต้องเป็นไปตามนั้นundefinedส่วนใหญ่ถ้าไม่ใช่ทุกกรณีที่มีเหตุผล JS เป็นหนึ่งในภาษาเชิงวัตถุที่บริสุทธิ์ที่สุด ภาษาที่เรียกว่า OO ส่วนใหญ่จะเน้นคลาสในแง่ที่คุณไม่สามารถเปลี่ยนรูปแบบ (ซึ่งเชื่อมโยงกับคลาส) ของอ็อบเจ็กต์ที่สร้างขึ้นเมื่อสร้างขึ้นแล้วมีเพียงสถานะของอ็อบเจ็กต์เท่านั้น ใน JS คุณสามารถเปลี่ยนสถานะตลอดจนรูปแบบของวัตถุได้ซึ่งคุณทำได้บ่อยกว่าที่คุณคิด ความสามารถนี้ทำให้โค้ดบางส่วนค่อนข้างคลุมเครือหากคุณใช้ผิด ตัวเลขไม่เปลี่ยนรูปดังนั้นคุณจึงไม่สามารถเปลี่ยนวัตถุเองได้ไม่ใช่สถานะหรือรูปแบบดังนั้นคุณจึงสามารถทำได้

0[0] = 1;

ซึ่งเป็นนิพจน์การกำหนดที่ถูกต้องซึ่งส่งคืน 1 แต่ไม่ได้กำหนดอะไรเลยจริง ๆ แล้วตัวเลข0ไม่เปลี่ยนรูป ซึ่งในตัวมันค่อนข้างแปลก คุณสามารถมีนิพจน์การกำหนด (ปฏิบัติการ) ที่ถูกต้องและถูกต้องซึ่งไม่ได้กำหนดอะไรเลย (*) อย่างไรก็ตามประเภทของตัวเลขเป็นวัตถุที่ไม่แน่นอนดังนั้นคุณจึงสามารถกลายพันธุ์ประเภทได้และการเปลี่ยนแปลงจะลดหลั่นกันไปตามห่วงโซ่ต้นแบบ

Number[0] = 1;
//print 1 to the console
console.log(0[0]);
//will also print 1 to the console because all integers have the same type
console.log(1[0]); 

แน่นอนว่ามันเป็นวิธีที่ห่างไกลจากหมวดหมู่การใช้งานที่มีเหตุผล แต่มีการระบุภาษาที่อนุญาตให้ทำเช่นนี้ได้เพราะในสถานการณ์อื่น ๆ การขยายความสามารถของวัตถุนั้นสมเหตุสมผลมาก เป็นวิธีที่ปลั๊กอิน jQuery เชื่อมต่อกับวัตถุ jQuery เพื่อให้เป็นตัวอย่าง

(*) จริง ๆ แล้วมันกำหนดค่า 1 ให้กับคุณสมบัติของวัตถุอย่างไรก็ตามไม่มีทางที่คุณสามารถอ้างอิงวัตถุนั้น (ชั่วคราว) ได้และจะถูกรวบรวมที่ nexx GC pass


3

ใน JavaScript ทุกอย่างเป็นวัตถุดังนั้นเมื่อล่ามแยกวิเคราะห์มันจะถือว่า 0 เป็นวัตถุและพยายามส่งคืน 0 เป็นคุณสมบัติ สิ่งเดียวกันนี้เกิดขึ้นเมื่อคุณพยายามเข้าถึงองค์ประกอบที่ 0 ของ true หรือ "" (สตริงว่าง)

แม้ว่าคุณจะตั้งค่า 0 [0] = 1 มันจะตั้งค่าคุณสมบัติและค่าของมันในหน่วยความจำ แต่ในขณะที่คุณเข้าถึง 0 มันจะถือว่าเป็นตัวเลข (อย่าสับสนระหว่างการปฏิบัติเป็น Object และตัวเลขที่นี่)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.