ลำดับฟังก์ชัน JavaScript: ทำไมถึงมีความสำคัญ?


107

คำถามเดิม:

JSHintบ่นเมื่อ JavaScript ของฉันเรียกใช้ฟังก์ชันที่กำหนดไว้ที่ด้านล่างของหน้ามากกว่าการเรียกใช้ อย่างไรก็ตามหน้าของฉันมีไว้สำหรับเกมและจะไม่มีการเรียกใช้ฟังก์ชันใด ๆ จนกว่าจะดาวน์โหลดทั้งหมด เหตุใดฟังก์ชันคำสั่งจึงปรากฏในรหัสของฉันจึงมีความสำคัญ

แก้ไข: ฉันคิดว่าฉันอาจพบคำตอบแล้ว

http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting

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


+1 สำหรับการอ้างอิงที่ยอดเยี่ยมในการอัปเดต และฉันหวังว่าจะทำให้คุณมั่นใจได้ว่าคุณไม่จำเป็นต้องสั่งซื้อรหัสของคุณใหม่ :)
AWM

คำตอบ:


295

tl; drถ้าคุณไม่ได้โทรหาอะไรเลยจนกว่าทุกอย่างจะโหลดเสร็จคุณก็สบายดี


แก้ไข: สำหรับภาพรวมซึ่งครอบคลุมการประกาศ ES6 ( let, const): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Scope_Cheatsheet

พฤติกรรมแปลก ๆ นี้ขึ้นอยู่กับ

  1. คุณกำหนดฟังก์ชันและ
  2. เมื่อคุณโทรหาพวกเขา

นี่คือตัวอย่างบางส่วน

bar(); //This won't throw an error
function bar() {}

foo(); //This will throw an error
var foo = function() {}
bar();
function bar() {
    foo(); //This will throw an error
}
var foo = function() {}
bar();
function bar() {
    foo(); //This _won't_ throw an error
}
function foo() {}
function bar() {
    foo(); //no error
}
var foo = function() {}
bar();

นี่เป็นเพราะสิ่งที่เรียกว่าการชักรอก !

ฟังก์ชั่น: มีสองวิธีที่จะกำหนดฟังก์ชั่นที่มีการประกาศและฟังก์ชั่นการแสดงออก ความแตกต่างนั้นน่ารำคาญและเป็นนาทีดังนั้นสมมติว่าสิ่งนี้ผิดเล็กน้อย: หากคุณกำลังเขียนมันเหมือนfunction name() {}เป็นการประกาศและเมื่อคุณเขียนมันเช่นvar name = function() {}(หรือฟังก์ชันที่ไม่ระบุตัวตนที่กำหนดให้ส่งคืนสิ่งต่างๆเช่นนั้น) ฟังก์ชั่นการแสดงออก

ก่อนอื่นมาดูวิธีจัดการตัวแปร:

var foo = 42;

//the interpreter turns it into this:
var foo;
foo = 42;

ตอนนี้วิธีจัดการการประกาศฟังก์ชัน:

var foo = 42;
function bar() {}

//turns into
var foo; //Insanity! It's now at the top
function bar() {}
foo = 42;

varงบ "โยน" การสร้างของfooไปด้านบนมาก แต่ไม่ได้กำหนดค่าให้มันยัง fooการประกาศฟังก์ชั่นมาในบรรทัดถัดไปและในที่สุดก็มีค่าได้รับมอบหมายให้

แล้วนี่ล่ะ?

bar();
var foo = 42;
function bar() {}
//=>
var foo;
function bar() {}
bar();
foo = 42;

เฉพาะประกาศของfooถูกย้ายไปด้านบน การมอบหมายจะเกิดขึ้นหลังจากมีการโทรหาbarเท่านั้นซึ่งเป็นจุดเริ่มต้นก่อนที่จะเกิดการชักรอกทั้งหมด

และสุดท้ายเพื่อความกระชับ:

bar();
function bar() {}
//turns to
function bar() {}
bar();

แล้วนิพจน์ฟังก์ชันล่ะ

var foo = function() {}
foo();
//=>
var foo;
foo = function() {}
foo();

เช่นเดียวกับตัวแปรทั่วไปอันดับแรกfooจะถูกประกาศที่จุดสูงสุดของขอบเขตจากนั้นจึงกำหนดค่า

มาดูกันว่าเหตุใดตัวอย่างที่สองจึงเกิดข้อผิดพลาด

bar();
function bar() {
    foo();
}
var foo = function() {}
//=>
var foo;
function bar() {
    foo();
}
bar();
foo = function() {}

ดังที่เราเคยเห็นมาก่อนมีเพียงการสร้างเท่านั้นที่fooถูกยกขึ้นงานจะมาในตำแหน่งที่ปรากฏในรหัส "ดั้งเดิม" (ไม่ได้รับการยก) เมื่อbarจะเรียกว่ามันเป็นก่อนที่จะมีการกำหนดค่าเพื่อให้foo foo === undefinedตอนนี้ใน function-body ของbarมันเหมือนกับว่าคุณกำลังทำอยู่undefined()ซึ่งทำให้เกิดข้อผิดพลาด


ขออภัยที่ต้องขุดขึ้นมา แต่มีโอเวอร์โหลดเช่น Array.prototype.someMethod = function () {} hoisted? ดูเหมือนว่าฉันจะได้รับข้อผิดพลาดหากสิ่งเหล่านี้อยู่ที่ส่วนท้ายของสคริปต์ของฉัน
ขอบ

คุณแน่ใจได้อย่างไรว่าทุกอย่างโหลด มีการปฏิบัติร่วมกันหรือไม่?
zwcloud

6

สาเหตุหลักน่าจะเป็นเพราะ JSLint ส่งผ่านไฟล์เพียงครั้งเดียวดังนั้นจึงไม่ทราบว่าคุณจะทำกำหนดฟังก์ชันดังกล่าว

หากคุณใช้ไวยากรณ์คำสั่งฟังก์ชัน

function foo(){ ... }

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

ในทางกลับกันถ้าฟังก์ชันของคุณถูกตั้งค่าเหมือนตัวแปรทั่วไป

var foo = function() { ... };

คุณต้องรับประกันว่าคุณจะไม่เรียกมันก่อนที่จะเริ่มต้น (นี่อาจเป็นที่มาของข้อบกพร่อง)


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

แสดงความคิดเห็นเกี่ยวกับการขอทานของไฟล์

/*globals foo1 foo2 foo3*/

หรือจะใช้กล่องข้อความตรงนั้นก็ได้ (ฉันยังคิดว่าคุณสามารถส่งสิ่งนี้ในอาร์กิวเมนต์ไปยังฟังก์ชัน jslint ภายในได้หากคุณสามารถเข้าไปยุ่งได้)


ขอบคุณ. ดังนั้น / * globals * / line จะใช้ได้ไหม ดี - อะไรก็ได้ที่จะทำให้ JsHint ชอบฉัน ฉันยังใหม่กับ JavaScript และได้รับการหยุดชั่วคราวที่อธิบายไม่ได้เมื่อฉันรีเฟรชหน้า แต่ไม่มีรายงานข้อบกพร่อง ฉันจึงคิดว่าวิธีแก้ปัญหาคือเล่นตามกฎทั้งหมดแล้วดูว่ามันยังเกิดขึ้นหรือไม่
Chris Tolworthy

4

มีคนจำนวนมากเกินไปที่จะผลักดันกฎโดยพลการเกี่ยวกับวิธีการเขียน JavaScript กฎส่วนใหญ่เป็นขยะที่สุด

Function hoisting เป็นคุณลักษณะหนึ่งใน JavaScript เพราะเป็นความคิดที่ดี

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

คุณควรยึดมั่นในหลักการเดียวตลอด codebase ของคุณไม่ว่าจะใส่ฟังก์ชันส่วนตัวก่อนหรือสุดท้ายในโมดูลหรือฟังก์ชันของคุณ JSHint เป็นสิ่งที่ดีสำหรับการบังคับใช้ความสอดคล้อง แต่คุณควรปรับ. jshintrc ให้เหมาะกับความต้องการของคุณอย่าปรับซอร์สโค้ดของคุณเป็นแนวคิดการเข้ารหัสที่แปลกประหลาดของคนอื่น ๆ

รูปแบบการเขียนโค้ดแบบเดียวที่คุณอาจเห็นในป่าที่คุณควรหลีกเลี่ยงเพราะมันไม่ได้ทำให้คุณได้เปรียบและมีเพียงความเจ็บปวดในการปรับโครงสร้างใหม่เท่านั้น:

function bigProcess() {
    var step1,step2;
    step1();
    step2();

    step1 = function() {...};
    step2 = function() {...};
}

นี่คือสิ่งที่มีการยกฟังก์ชันที่ควรหลีกเลี่ยง เพียงแค่เรียนรู้ภาษาและใช้ประโยชน์จากจุดแข็งของมัน


2

การประกาศฟังก์ชันเท่านั้นที่ถูกยกขึ้นไม่ใช่นิพจน์ฟังก์ชัน (การกำหนด)

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