ทำไมฉันถึงใช้ฟังก์ชั่นก่อนที่มันจะถูกกำหนดใน JavaScript?


168

รหัสนี้ใช้งานได้แม้ในเบราว์เซอร์ที่แตกต่างกัน:

function fooCheck() {
  alert(internalFoo()); // We are using internalFoo() here...

  return internalFoo(); // And here, even though it has not been defined...

  function internalFoo() { return true; } //...until here!
}

fooCheck();

ฉันไม่สามารถหาข้อมูลอ้างอิงเดียวว่าทำไมมันควรทำงาน ฉันเห็นสิ่งนี้เป็นครั้งแรกในบันทึกการนำเสนอของ John Resig แต่มีการพูดถึงเท่านั้น ไม่มีคำอธิบายใด ๆ ในนั้น

มีคนช่วยสอนฉันได้มั้ย


3
ใน Firefox รุ่นใหม่กว่านี้ไม่ทำงานหากรหัสอยู่ในลอง / จับ ดูซอนี้: jsfiddle.net/qzzc1evt
Joshua Smith

คำตอบ:


217

การfunctionประกาศเป็นเวทย์มนตร์และทำให้ตัวระบุถูกผูกไว้ก่อนที่จะมีการดำเนินการสิ่งใดในโค้ดบล็อก

สิ่งนี้แตกต่างจากการมอบหมายด้วยfunctionนิพจน์ซึ่งถูกประเมินตามลำดับจากบนลงล่างปกติ

หากคุณเปลี่ยนตัวอย่างที่จะพูด:

var internalFoo = function() { return true; };

มันจะหยุดทำงาน

การประกาศฟังก์ชั่นนั้นค่อนข้างจะแยกทางจากการแสดงออกของฟังก์ชั่นทางวากยสัมพันธ์แม้ว่ามันจะดูเกือบจะเหมือนกันและอาจคลุมเครือในบางกรณี

นี้ถูกบันทึกไว้ในมาตรฐาน ECMAScriptส่วน10.1.3 น่าเสียดายที่ ECMA-262 ไม่ใช่เอกสารที่อ่านได้ง่ายแม้ตามมาตรฐาน!

*: ฟังก์ชั่นที่มีบล็อกโมดูลหรือสคริปต์


ฉันเดาว่ามันอ่านไม่ออกจริงๆ ฉันเพิ่งอ่านส่วนที่คุณชี้ 10.1.3 และไม่เข้าใจว่าทำไมบทบัญญัติที่นั่นจะทำให้เกิดพฤติกรรมนี้ ขอบคุณสำหรับข้อมูล.
Edu Felipe

2
@ Bobince เอาล่ะฉันเริ่มสงสัยตัวเองเมื่อฉันไม่สามารถพูดถึงคำว่า“ hoisting” ในหน้านี้ได้ หวังว่าความคิดเห็นเหล่านี้จะมี Google Juice ™เพียงพอที่จะกำหนดสิ่งต่าง ๆ ได้อย่างเหมาะสม :)
Mathias Bynens

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

1
บทความนี้มีตัวอย่าง: JavaScript-Scoping-and-Hoisting
Lombas

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

28

มันถูกเรียกว่า HOISTING - เรียกใช้ฟังก์ชั่นก่อนที่มันจะได้รับการกำหนด

ฟังก์ชันที่แตกต่างกันสองประเภทที่ฉันต้องการเขียนคือ:

ฟังก์ชั่นการแสดงออกและฟังก์ชั่นการประกาศ

  1. ฟังก์ชั่นการแสดงออก:

    การแสดงออกของฟังก์ชั่นสามารถเก็บไว้ในตัวแปรเพื่อให้พวกเขาไม่ต้องการชื่อฟังก์ชั่น พวกเขาจะถูกตั้งชื่อเป็นฟังก์ชั่นที่ไม่ระบุชื่อ (ฟังก์ชั่นที่ไม่มีชื่อ)

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

    let lastName = function (family) {
     console.log("My last name is " + family);
    };
    let x = lastName("Lopez");

    นี่คือวิธีที่คุณเขียนใน ECMAScript 6:

    lastName = (family) => console.log("My last name is " + family);
    
    x = lastName("Lopez");
  2. ฟังก์ชั่นการประกาศ:

    ฟังก์ชั่นที่ประกาศพร้อมกับไวยากรณ์ต่อไปนี้จะไม่ถูกดำเนินการในทันที พวกเขาจะ "บันทึกไว้สำหรับใช้ในภายหลัง" และจะถูกดำเนินการในภายหลังเมื่อพวกเขาถูกเรียก (เรียกร้องให้) ฟังก์ชั่นประเภทนี้ใช้งานได้ถ้าคุณเรียกมันว่าก่อนหรือหลังที่มีการกำหนดไว้ หากคุณเรียกใช้ฟังก์ชันการประกาศก่อนที่จะมีการกำหนด Hoisting ให้ทำงานได้อย่างถูกต้อง

    function Name(name) {
      console.log("My cat's name is " + name);
    }
    Name("Chloe");

    ตัวอย่างการชักรอก:

    Name("Chloe");
    function Name(name) {
       console.log("My cat's name is " + name);
    }

let fun = theFunction; fun(); function theFunction() {}จะทำงาน (โหนดและเบราว์เซอร์)
fider

14

เบราว์เซอร์อ่าน HTML ของคุณตั้งแต่ต้นจนจบและสามารถเรียกใช้งานได้เนื่องจากมีการอ่านและแยกวิเคราะห์เป็นชิ้น ๆ ที่ปฏิบัติการได้ (การประกาศตัวแปรคำจำกัดความของฟังก์ชั่น ฯลฯ ) แต่ ณ จุดใดก็ตามสามารถใช้สิ่งที่กำหนดไว้ในสคริปต์ก่อน

สิ่งนี้แตกต่างจากบริบทการเขียนโปรแกรมอื่น ๆ ที่ประมวลผล (คอมไพล์) ซอร์สโค้ดของคุณทั้งหมดอาจเชื่อมโยงเข้าด้วยกันกับไลบรารีใด ๆ ที่คุณต้องการแก้ไขการอ้างอิงและสร้างโมดูลปฏิบัติการที่จุดเริ่มต้นการประมวลผล

รหัสของคุณสามารถอ้างถึงอ็อบเจกต์ที่มีชื่อ (ตัวแปร, ฟังก์ชั่นอื่น ๆ ฯลฯ ) ที่กำหนดไว้เพิ่มเติม แต่คุณไม่สามารถรันโค้ดอ้างอิงได้จนกว่าชิ้นส่วนทั้งหมดจะพร้อมใช้งาน

เมื่อคุณคุ้นเคยกับ JavaScript คุณจะเริ่มตระหนักถึงความจำเป็นในการเขียนสิ่งต่าง ๆ ในลำดับที่ถูกต้อง

การตรวจทานแก้ไข: เพื่อยืนยันคำตอบที่ยอมรับ (ด้านบน) ให้ใช้ Firebug เพื่อทำขั้นตอนส่วนสคริปต์ของหน้าเว็บ คุณจะเห็นมันข้ามจากฟังก์ชั่นหนึ่งไปอีกฟังก์ชั่นโดยไปที่บรรทัดแรกเท่านั้นก่อนที่มันจะประมวลผลโค้ดใด ๆ


3

บางภาษามีข้อกำหนดที่จะต้องมีการกำหนดตัวระบุก่อนใช้ เหตุผลสำหรับสิ่งนี้คือคอมไพเลอร์ใช้รหัสผ่านครั้งเดียวบนซอร์สโค้ด

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


2

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

http://www.dustindiaz.com/javascript-function-declaration-ambiguity/


ลิงค์ไม่ตายอีกต่อไป
mwclarke

1
ลิงก์ตาย
Jerome Indefenzo

นี่คือภาพรวมจาก archive.org ดูเหมือนว่าผู้เขียนจะลงเว็บไซต์ทั้งหมดเพราะมีเนื้อหาที่ล้าสมัยไม่ใช่ว่าโพสต์บล็อกนี้อยู่ในหมวดหมู่นั้น
jkmartindale

1

เนื้อความของฟังก์ชั่น "internalFoo" จำเป็นต้องไปที่ใดที่หนึ่งในการแยกวิเคราะห์ดังนั้นเมื่อโค้ดถูกอ่าน (การแยกวิเคราะห์) โดยล่าม JS โครงสร้างข้อมูลสำหรับฟังก์ชั่นจะถูกสร้างขึ้นและชื่อถูกกำหนด

หลังจากนั้นรหัสจะถูกรัน JavaScript จะพยายามค้นหาว่ามี "internalFoo" อยู่หรือไม่และมันคืออะไรและสามารถเรียกได้หรือไม่เป็นต้น


-4

ด้วยเหตุผลเดียวกันสิ่งต่อไปนี้จะใส่fooในเนมสเปซส่วนกลาง:

if (test condition) {
    var foo;
}

8
จริงๆแล้วมันมีเหตุผลที่แตกต่างกันมาก ifบล็อกไม่สร้างขอบเขตในขณะที่function()บล็อกจะสร้างหนึ่ง เหตุผลที่แท้จริงคือคำจำกัดความของชื่อจาวาสคริปต์ทั่วโลกเกิดขึ้นในขั้นตอนการคอมไพล์ดังนั้นแม้ว่าจะไม่ได้รันโค้ด แต่ชื่อนั้นก็ถูกกำหนดไว้ (ขออภัยใช้เวลานานในการแสดงความคิดเห็น)
Edu Felipe
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.