กฎการแทรกอัฒภาคอัตโนมัติ (JavaScript) ของ JavaScript คืออะไร


445

ก่อนอื่นฉันควรถามว่าเบราว์เซอร์นี้ขึ้นอยู่กับหรือไม่

ฉันได้อ่านแล้วว่าหากพบโทเค็นที่ไม่ถูกต้อง แต่ส่วนของรหัสนั้นใช้ได้จนกระทั่งโทเค็นที่ไม่ถูกต้องนั้นจะมีการแทรกเครื่องหมายอัฒภาคไว้ข้างหน้าโทเค็นหากนำหน้าด้วยตัวแบ่งบรรทัด

อย่างไรก็ตามตัวอย่างทั่วไปที่อ้างถึงสำหรับข้อบกพร่องที่เกิดจากการแทรกอัฒภาคคือ:

return
  _a+b;

.. ซึ่งดูเหมือนจะไม่ปฏิบัติตามกฎนี้เนื่องจาก _a จะเป็นโทเค็นที่ถูกต้อง

ในทางกลับกันการเลิกใช้เครือข่ายการโทรทำงานได้ตามที่คาดไว้:

$('#myButton')
  .click(function(){alert("Hello!")});

ใครบ้างมีคำอธิบายในเชิงลึกของกฎ?



33
@Miles ไม่ได้อยู่ที่ลิงค์ที่ขาด ;-) ecma-international.org/publications/standards/Ecma-262.htm
Zach Lysobey

3
ดูหน้า 26 จากการอ้างถึง PDF ข้างต้น
ᴠɪɴᴄᴇɴᴛ


อ้างถึงส่วนที่ 11.9 การแทรกอัฒภาคอัตโนมัติ
Andrew Lam

คำตอบ:


454

ก่อนอื่นคุณควรทราบว่าข้อความใดบ้างที่ได้รับผลกระทบจากการแทรกอัฒภาคอัตโนมัติ (หรือที่รู้จักในชื่อ ASI สำหรับความกะทัดรัด):

  • คำสั่งที่ว่างเปล่า
  • var คำให้การ
  • คำสั่งการแสดงออก
  • do-while คำให้การ
  • continue คำให้การ
  • break คำให้การ
  • return คำให้การ
  • throw คำให้การ

กฎที่เป็นรูปธรรมของ ASI อธิบายไว้ในข้อกำหนดspecification11.9.1 กฎของการแทรกเครื่องหมายอัฒภาคอัตโนมัติ

มีการอธิบายสามกรณี:

  1. เมื่อพบโทเค็น ( LineTerminatorหรือ}) ที่ไม่ได้รับอนุญาตจากไวยากรณ์เซมิโคลอนจะถูกแทรกก่อนหน้าถ้า:

    • โทเค็นจะถูกแยกออกจากโทเค็นก่อนหน้าอย่างน้อยหนึ่ง LineTerminatorโทเค็นจะถูกแยกออกจากโทเค็นก่อนที่หนึ่งอย่างน้อย
    • โทเค็นคือ }

    เช่น :

    { 1
    2 } 3

    ถูกแปลงเป็น

    { 1
    ;2 ;} 3;

    NumericLiteral 1ตรงตามเงื่อนไขแรก, โทเค็นต่อไปนี้เป็นเทอร์มิบรรทัด ตรงตามเงื่อนไขที่สอง, โทเค็นต่อไปนี้เป็น
    2}

  2. เมื่อพบจุดสิ้นสุดของอินพุตสตรีมของโทเค็นและตัวแยกวิเคราะห์ไม่สามารถแยกวิเคราะห์โทเค็นอินพุตโทเค็นเป็นโปรแกรมที่สมบูรณ์เดียวจากนั้นเซมิโคลอนจะถูกแทรกโดยอัตโนมัติที่ส่วนท้ายของอินพุตสตรีม

    เช่น :

    a = b
    ++c

    เปลี่ยนเป็น:

    a = b;
    ++c;
  3. กรณีนี้เกิดขึ้นเมื่อโทเค็นได้รับอนุญาตจากการผลิตไวยากรณ์บางอย่าง แต่การผลิตเป็นการผลิตแบบจำกัดเซมิโคลอนจะถูกแทรกโดยอัตโนมัติก่อนโทเค็นที่ถูก จำกัด

    โปรดักชั่นที่ จำกัด :

    UpdateExpression :
        LeftHandSideExpression [no LineTerminator here] ++
        LeftHandSideExpression [no LineTerminator here] --
    
    ContinueStatement :
        continue ;
        continue [no LineTerminator here] LabelIdentifier ;
    
    BreakStatement :
        break ;
        break [no LineTerminator here] LabelIdentifier ;
    
    ReturnStatement :
        return ;
        return [no LineTerminator here] Expression ;
    
    ThrowStatement :
        throw [no LineTerminator here] Expression ; 
    
    ArrowFunction :
        ArrowParameters [no LineTerminator here] => ConciseBody
    
    YieldExpression :
        yield [no LineTerminator here] * AssignmentExpression
        yield [no LineTerminator here] AssignmentExpression

    ตัวอย่างคลาสสิกกับReturnStatement:

    return 
      "something";

    ถูกแปลงเป็น

    return;
      "something";

4
# 1: โทเค็นที่ไม่ได้รับอนุญาตจากไวยากรณ์มักจะไม่ใช่ตัวต่อบรรทัดหรือไม่ (เว้นแต่คุณหมายถึงโปรดักชั่นที่ถูก จำกัด จาก # 3) มันคิดว่าคุณควรละเว้นวงเล็บ # 2 ตัวอย่างไม่ควรแสดงเฉพาะการแทรกหลังจาก++cเพื่อความชัดเจนหรือไม่
Bergi

3
โปรดทราบว่า ASI ไม่จำเป็นต้อง "แทรกเซมิโคลอน" จริง ๆ เพียงแค่ยกเลิกคำสั่งในโปรแกรมแยกวิเคราะห์ของเครื่องยนต์ ...
Aprillion

1
มันพูดว่าอะไร "input stream" นั่นหมายความว่า "a line"? "อินพุตโทเค็นสตรีม" กำลังทำให้เข้าใจยากขึ้น
nonopolarity

ลิงค์ข้อมูลจำเพาะใช้ได้กับคนอื่นหรือไม่ มันนำฉันไปสู่หน้าที่ว่างเปล่าเกือบแล้วซึ่งมีลิงค์ตายอยู่
บุกรุก

โปรดอธิบายวิธีตามกฎเหล่านี้ตัวอย่างด้านล่างโดย太極者無極而生ของ "a [LineBreak] = [LineBreak] 3" ยังใช้งานได้
Nir O.

45

ส่งตรงจากECMA-262 ข้อมูลจำเพาะ ECMAScript Fifth Edition :

7.9.1 กฎการแทรกอัฒภาคอัตโนมัติ

มีกฎพื้นฐานสามข้อในการแทรกเครื่องหมายอัฒภาค:

  1. เมื่อเมื่อโปรแกรมถูกแยกวิเคราะห์จากซ้ายไปขวาโทเค็น (เรียกว่าโทเค็นที่ละเมิด ) ซึ่งไม่ได้รับอนุญาตจากการผลิตไวยากรณ์ดังนั้นเซมิโคลอนจะถูกแทรกโดยอัตโนมัติก่อนโทเค็นที่ละเมิดหากหนึ่งหรือมากกว่าต่อไปนี้ เงื่อนไขเป็นจริง:
    • โทเค็นที่ละเมิดจะถูกแยกออกจากโทเค็นก่อนหน้านี้อย่างน้อยหนึ่งโทเค็น LineTerminatorละเมิดโทเค็นจะถูกแยกออกจากโทเค็นก่อนที่หนึ่งอย่างน้อย
    • }ละเมิดโทเค็น
  2. เมื่อเมื่อโปรแกรมถูกแยกวิเคราะห์จากซ้ายไปขวาจะพบจุดสิ้นสุดอินพุตสตรีมของโทเค็นและ parser ไม่สามารถแยกวิเคราะห์สตรีมโทเค็นอินพุตเป็น ECMAScript แบบสมบูรณ์เดียว Programจากนั้นเซมิโคลอนจะถูกแทรกโดยอัตโนมัติที่ส่วนท้ายของ สตรีมอินพุต
  3. เมื่อเมื่อโปรแกรมถูกแยกวิเคราะห์จากซ้ายไปขวาจะพบโทเค็นที่ได้รับอนุญาตจากการผลิตไวยากรณ์บางอย่าง แต่การผลิตเป็นการผลิตที่ จำกัดและโทเค็นจะเป็นโทเค็นแรกสำหรับเทอร์มินัลหรือไม่ใช่เทอร์มินอล " [ไม่LineTerminatorอยู่ที่นี่] " ภายในการผลิตที่ จำกัด (และโทเค็นดังกล่าวเรียกว่าโทเค็นที่ถูก จำกัด ) และโทเค็นที่ จำกัด จะถูกแยกออกจากโทเค็นก่อนหน้าอย่างน้อยหนึ่งLineTerminatorจากนั้นเซมิโคลอนจะแทรกอัตโนมัติก่อนโทเค็นที่ จำกัด

อย่างไรก็ตามมีเงื่อนไขที่สำคัญเพิ่มเติมเกี่ยวกับกฎก่อนหน้า: อัฒภาคไม่เคยถูกแทรกโดยอัตโนมัติหากอัฒภาคจะถูกแยกเป็นคำสั่งที่ว่างเปล่าหรือถ้าอัฒภาคจะกลายเป็นหนึ่งในสองอัฒภาคในส่วนหัวของforคำสั่ง (ดู 12.6 0.3)


44

ฉันไม่สามารถเข้าใจกฎทั้งสามข้อในรายละเอียดได้ดีเกินไป - หวังว่าจะมีบางสิ่งที่เป็นภาษาอังกฤษธรรมดา ๆ มากกว่านี้ - แต่นี่คือสิ่งที่ฉันรวบรวมจาก JavaScript: The Definitive Guide, 6th Edition, David Flanagan, O'Reilly, 2011:

อ้างถึง:

JavaScript ไม่ถือว่าการแบ่งบรรทัดทุกครั้งเป็นเครื่องหมายอัฒภาค: โดยทั่วไปจะถือว่าการแบ่งบรรทัดเป็นเครื่องหมายอัฒภาคเฉพาะในกรณีที่ไม่สามารถแยกรหัสโดยไม่มีเครื่องหมายอัฒภาค

อ้างอีก: สำหรับรหัส

var a
a
=
3 console.log(a)

JavaScript ไม่ถือว่าการแบ่งบรรทัดที่สองเป็นเซมิโคลอนเนื่องจากสามารถแยกวิเคราะห์คำสั่งที่ยาวขึ้น a = 3;

และ:

ข้อยกเว้นสองข้อสำหรับกฎทั่วไปที่ JavaScript ตีความการแบ่งบรรทัดเป็นเซมิโคลอนเมื่อไม่สามารถแยกบรรทัดที่สองเป็นการต่อเนื่องของคำสั่งในบรรทัดแรก ข้อยกเว้นแรกเกี่ยวข้องกับคำสั่ง return, break และ Continue

... หากมีการขึ้นบรรทัดใหม่หลังจากคำใดคำหนึ่งเหล่านี้ ... JavaScript จะแปลความหมายของการแบ่งบรรทัดเป็นอัฒภาคเสมอ

... ข้อยกเว้นที่สองเกี่ยวข้องกับตัวดำเนินการ ++ และ ... ... หากคุณต้องการใช้ตัวดำเนินการเหล่านี้เป็นตัวดำเนินการ postfix จะต้องปรากฏในบรรทัดเดียวกับนิพจน์ที่ใช้ มิเช่นนั้นตัวแบ่งบรรทัดจะถูกใช้เป็นเครื่องหมายอัฒภาคและ ++ หรือ - จะถูกแยกวิเคราะห์เป็นตัวดำเนินการส่วนนำหน้าที่ใช้กับรหัสที่ตามมา พิจารณารหัสนี้ตัวอย่างเช่น:

x 
++ 
y

มันถูกแยกวิเคราะห์ว่าx; ++y;ไม่ใช่x++; y

ดังนั้นฉันคิดว่าจะทำให้มันง่ายขึ้นนั่นหมายความว่า:

โดยทั่วไป, JavaScript จะรักษามันเป็นความต่อเนื่องของรหัสตราบใดที่มันทำให้รู้สึก - ยกเว้น 2 กรณีคือ (1) หลังจากที่คำหลักบางอย่างเช่นreturn, break, continueและ (2) ถ้ามันเห็น++หรือ--ในบรรทัดใหม่แล้วมันจะเพิ่ม;ที่ท้ายบรรทัดก่อนหน้านี้

ส่วนที่เกี่ยวกับ "ถือว่าเป็นความต่อเนื่องของรหัสตราบใดที่มันสมเหตุสมผล" ทำให้รู้สึกเหมือนการจับคู่โลภของนิพจน์ปกติ

จากที่กล่าวมาข้างต้นหมายความว่าเมื่อreturnมีการขึ้นบรรทัดใหม่ล่าม JavaScript จะแทรก a;

(เสนอราคาอีกครั้ง: หากมีตัวแบ่งบรรทัดปรากฏหลังจากคำใด ๆ เหล่านี้ [เช่น return ] ... JavaScript จะตีความตัวแบ่งบรรทัดนั้นเป็นเครื่องหมายอัฒภาคเสมอ)

และด้วยเหตุนี้จึงเป็นตัวอย่างคลาสสิกของ

return
{ 
  foo: 1
}

จะไม่ทำงานตามที่คาดไว้เพราะล่าม JavaScript จะถือว่าเป็น:

return;   // returning nothing
{
  foo: 1
}

จะต้องไม่มีการขึ้นบรรทัดใหม่ทันทีหลังจากreturn:

return { 
  foo: 1
}

เพื่อให้ทำงานได้อย่างถูกต้อง และคุณสามารถแทรก;ตัวคุณเองได้หากคุณต้องปฏิบัติตามกฎการใช้;คำสั่งหลังคำสั่งใด ๆ :

return { 
  foo: 1
};

17

เกี่ยวกับการแทรกเครื่องหมายอัฒภาคและคำสั่ง var ระวังอย่าลืมเครื่องหมายจุลภาคเมื่อใช้ var แต่ครอบคลุมหลายบรรทัด บางคนพบสิ่งนี้ในรหัสของฉันเมื่อวาน:

    var srcRecords = src.records
        srcIds = [];

มันวิ่ง แต่ผลที่ได้คือการประกาศ srcIds / การมอบหมายเป็นโลกเพราะการประกาศในท้องถิ่นที่มี var ในบรรทัดก่อนหน้านี้ไม่ได้ใช้อีกต่อไปเป็นคำสั่งที่ถือว่าเสร็จแล้วเนื่องจากการแทรกกึ่งอัตโนมัติลำไส้ใหญ่


4
นี่เป็นเหตุผลว่าทำไมฉันถึงใช้ jsLint
Zach Lysobey

1
JsHint / Lint ในเครื่องมือแก้ไขโค้ดของคุณพร้อมการตอบกลับทันที :)
dmi3y

5
@ balupton เมื่อเครื่องหมายจุลภาคที่จะสิ้นสุดบรรทัดจะถูกลืม, semi-colon จะถูกแทรกโดยอัตโนมัติ เมื่อเทียบกับกฎมันก็เหมือน "gotcha"
Dexygen

1
ฉันคิดว่า balupton นั้นถูกต้องมันเป็นความแตกต่างถ้าคุณเขียน: var srcRecords = src.records srcIds = [];ในบรรทัดเดียวและลืมเครื่องหมายจุลภาคหรือคุณเขียน "return a & b" และลืมอะไรเลย ... แต่การแบ่งบรรทัดก่อน a จะแทรกเซมิโคลอนอัตโนมัติหลังจากกลับมา ซึ่งถูกกำหนดโดยกฎ ASI ...
เซบาสเตียน

3
ฉันคิดว่าความชัดเจนของการพิมพ์var( let, const) ในแต่ละบรรทัดนั้นมีค่ามากกว่าเสี้ยววินาทีในการพิมพ์
squidbe

5

คำอธิบายบริบทที่สุดของ JavaScript ของอัตโนมัติอัฒภาคแทรกฉันได้พบมาจากหนังสือเกี่ยวกับงานหัตถกรรมล่าม

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

เขาก็จะบอกว่ามันเป็นที่คุณจะรหัสกลิ่น

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


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