แปลกใจที่ตัวแปรส่วนกลางมีค่าที่ไม่ได้กำหนดใน JavaScript


89

วันนี้ฉันรู้สึกประหลาดใจมากเมื่อเห็นว่าตัวแปรส่วนกลางมีundefinedค่าในบางกรณี

ตัวอย่าง:

var value = 10;
function test() {
    //A
    console.log(value);
    var value = 20;

    //B
    console.log(value);
}
test();

ให้เอาต์พุตเป็น

undefined
20

ที่นี่เหตุใดเครื่องมือ JavaScript จึงพิจารณาค่าส่วนกลางเป็นundefined? ฉันรู้ว่า JavaScript เป็นภาษาที่ตีความได้ สามารถพิจารณาตัวแปรในฟังก์ชันได้อย่างไร?

นั่นเป็นข้อผิดพลาดจากเอ็นจิ้น JavaScript หรือไม่?

คำตอบ:


177

ปรากฏการณ์นี้เป็นที่รู้จักกันในนามJavaScript Hoisting

คุณไม่ได้เข้าถึงตัวแปรส่วนกลางในฟังก์ชันของคุณ ณ จุดใด คุณจะเข้าถึงvalueตัวแปรภายในเครื่องเท่านั้น

รหัสของคุณเทียบเท่ากับสิ่งต่อไปนี้:

var value = 10;

function test() {
    var value;
    console.log(value);

    value = 20;
    console.log(value);
}

test();

ยังแปลกใจที่คุณได้รับundefined?


คำอธิบาย:

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

ดังนั้นเมื่อคุณโทรครั้งแรกconsole.log(value)คุณกำลังอ้างถึงตัวแปรที่ประกาศในเครื่องของคุณซึ่งยังไม่ได้กำหนดอะไรเลย ด้วยเหตุนี้undefined .

นี่คืออีกตัวอย่างหนึ่ง :

var test = 'start';

function end() {
    test = 'end';
    var test = 'local';
}

end();
alert(test);

คุณคิดว่าสิ่งนี้จะแจ้งเตือนอะไร ไม่อย่าเพิ่งอ่านลองคิดดู มูลค่าของtestอะไร?

ถ้าคุณพูดอะไรนอกเหนือจากstartนั้นคุณคิดผิด รหัสข้างต้นเทียบเท่ากับสิ่งนี้:

var test = 'start';

function end() {
    var test;
    test = 'end';
    test = 'local';
}

end();
alert(test);

เพื่อไม่ให้ตัวแปร global ได้รับผลกระทบ

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


หมายเหตุด้านข้าง:

นอกจากนี้ยังใช้กับฟังก์ชัน

พิจารณาโค้ดส่วนนี้ :

test("Won't work!");

test = function(text) { alert(text); }

ซึ่งจะทำให้คุณมีข้อผิดพลาดในการอ้างอิง:

Uncaught ReferenceError: ไม่มีการกำหนดการทดสอบ

สิ่งนี้ทำให้นักพัฒนาจำนวนมากไม่พอใจเนื่องจากโค้ดชิ้นนี้ใช้งานได้ดี:

test("Works!");

function test(text) { alert(text); }

เหตุผลนี้ตามที่ระบุไว้เนื่องจากส่วนที่ได้รับมอบหมายไม่ได้ถูกยกขึ้น ดังนั้นในตัวอย่างแรกเมื่อtest("Won't work!")ถูกเรียกใช้ไฟล์testตัวแปรได้ถูกประกาศแล้ว แต่ยังไม่ได้กำหนดฟังก์ชันให้

ในตัวอย่างที่สองเราไม่ได้ใช้การกำหนดตัวแปร แต่เรากำลังใช้ไวยากรณ์การประกาศฟังก์ชันที่เหมาะสมซึ่งจะทำให้ฟังก์ชันถูกยกขึ้นอย่างสมบูรณ์


Ben Cherryได้เขียนบทความที่ยอดเยี่ยมเกี่ยวกับเรื่องนี้โดยมีชื่อว่าJavaScript Scoping and Hoistingอย่างเหมาะสม
อ่านมัน. จะให้ภาพรวมโดยละเอียด


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

1
@ DuKes0mE - คุณสามารถเข้าถึงตัวแปรส่วนกลางภายในฟังก์ชันได้ตลอดเวลา
Joseph Silber

3
ใช่แล้วทำไมกรณี A จากโพสต์เปิดไม่ทำงานและบอกว่าไม่ได้กำหนด? ฉันเข้าใจว่ามันจะถูกตีความเป็น var ท้องถิ่นแทนที่จะเป็น global แต่ตอนนั้นจะเป็น global ได้อย่างไร
DuKes0mE

4
ในการเข้าถึงโลก window.value ใช้ varaible
Venkat เรดดี้

1
รูปแบบของการชักรอกตัวแปรนี้ถูกนำมาใช้เมื่อใด สิ่งนี้เป็นมาตรฐานในจาวาสคริปต์หรือไม่?
Dieskim

55

ฉันค่อนข้างผิดหวังที่มีการอธิบายปัญหาที่นี่ แต่ไม่มีใครเสนอวิธีแก้ไข หากคุณต้องการเข้าถึงตัวแปรโกลบอลในขอบเขตฟังก์ชันโดยที่ฟังก์ชันไม่ได้สร้าง local var ที่ไม่ได้กำหนดก่อนให้อ้างอิง var เป็นwindow.varName


8
ใช่มันแย่มากที่คนอื่นไม่ได้เสนอวิธีแก้ปัญหาเพราะนี่เป็นผลลัพธ์แรกในผลลัพธ์ของ Google พวกเขาเกือบจะตอบคำถามจริงที่ถามว่ามันเป็นข้อผิดพลาดในเครื่องยนต์ js .... ถอนหายใจ -> ขอบคุณสำหรับคำตอบของคุณ
user1567453

2
นั่นคือความแตกต่างระหว่างความรู้ทางทฤษฎีและการทำ sh .. ขอบคุณสำหรับคำตอบ!
hansTheFranz

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

Javascript ทำให้ฉันประหลาดใจทุกวันที่ผ่านไป ขอบคุณมากคำตอบมีประโยชน์
Raf

ขอบคุณสำหรับวิธีแก้ปัญหาฉันพบสิ่งนี้หลังจากที่ global var ไม่ได้กำหนด แต่เฉพาะใน Safari ไฟล์ 'รวม' อื่น ๆ และตัวแปรส่วนกลางปรากฏขึ้นเช่น 'google' ดังนั้นฉันจึงคัดลอกแนวทางที่ Google ใช้: window.globalVarFromJS = window.globalVarFromJS || {}; จากนั้นฉันก็พบวิธีแก้ปัญหาของคุณจึงคิดว่าจะเพิ่มเข้าไป
Ralph Hinkley

10

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

ดังที่กล่าวไว้คนแรกconsole.log(value)เห็นvalueตัวแปร (ตัวในซึ่งเงาด้านนอกvalue) แต่ยังไม่ได้เริ่มต้น คุณสามารถคิดได้ว่าการประกาศตัวแปรทั้งหมดถูกย้ายไปที่จุดเริ่มต้นของฟังก์ชันโดยปริยาย ( ไม่ใช่บล็อกโค้ดด้านในสุด) ในขณะที่คำจำกัดความจะถูกทิ้งไว้ที่เดียวกัน

ดูสิ่งนี้ด้วย


ฉันชอบคำพูดง่ายๆ +1 เสมอ :)
Jashwant

3

มีตัวแปรส่วนกลางvalueแต่เมื่อการควบคุมเข้าสู่testฟังก์ชันvalueจะมีการประกาศตัวแปรอื่นซึ่งจะเป็นเงาของตัวแปรทั่วโลก เนื่องจากการประกาศตัวแปร ( แต่ไม่ใช่การกำหนด ) ใน JavaScript จะถูกยกไปไว้ที่ด้านบนสุดของขอบเขตที่ประกาศ:

//value == undefined (global)
var value = 10;
//value == 10 (global)

function test() {
    //value == undefined (local)
    var value = 20;
    //value == 20 (local)
}
//value == 10 (global)

โปรดทราบว่าการประกาศฟังก์ชันก็เช่นเดียวกันซึ่งหมายความว่าคุณสามารถเรียกใช้ฟังก์ชันก่อนที่จะกำหนดไว้ในรหัสของคุณ:

test(); //Call the function before it appears in the source
function test() {
    //Do stuff
}

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

var test = function() {
    //Do stuff
};
test(); //Have to call the function after the assignment

0
  1. วิธีที่ง่ายที่สุดในการรักษาการเข้าถึงตัวแปรภายนอก (ไม่ใช่เฉพาะขอบเขตส่วนกลาง) คือพยายามไม่ประกาศซ้ำภายใต้ชื่อเดียวกันในฟังก์ชัน อย่าใช้var ที่นั่น การใช้กฎการตั้งชื่อที่เป็นคำอธิบายที่เหมาะสมคือควร ด้วยสิ่งเหล่านี้จะเป็นการยากที่จะลงเอยด้วยตัวแปรที่มีชื่อว่า like value (ด้านนี้ไม่จำเป็นต้องเกี่ยวข้องกับตัวอย่างในคำถามเนื่องจากอาจมีการกำหนดชื่อตัวแปรนี้เพื่อความเรียบง่าย)

  2. หากฟังก์ชันนี้อาจถูกนำกลับมาใช้ที่อื่นและด้วยเหตุนี้จึงไม่มีการรับประกันว่าตัวแปรภายนอกที่กำหนดไว้ในบริบทใหม่นั้นสามารถใช้ฟังก์ชันEvalได้ การดำเนินการนี้ช้าดังนั้นจึงไม่แนะนำสำหรับฟังก์ชันที่ต้องการประสิทธิภาพ:

    if (typeof variable === "undefined")
    {
        eval("var variable = 'Some value';");
    }
    
  3. หากตัวแปรขอบเขตภายนอกที่คุณต้องการเข้าถึงถูกกำหนดไว้ในฟังก์ชันที่ตั้งชื่อไว้ตัวแปรนั้นอาจถูกแนบเข้ากับฟังก์ชันเองตั้งแต่แรกแล้วเข้าถึงได้จากที่ใดก็ได้ในโค้ดไม่ว่าจะเป็นฟังก์ชันที่ซ้อนกันลึก ๆ หรือตัวจัดการเหตุการณ์ภายนอก อย่างอื่น. โปรดสังเกตว่าการเข้าถึงคุณสมบัติจะช้าลงและต้องการให้คุณเปลี่ยนวิธีการเขียนโปรแกรมดังนั้นจึงไม่แนะนำให้ใช้เว้นแต่ว่าจำเป็นจริงๆ: ตัวแปรเป็นคุณสมบัติของฟังก์ชัน (JSFiddle) :

    // (the wrapper-binder is only necessary for using variables-properties
    // via "this"instead of the function's name)
    var functionAsImplicitObjectBody = function()
    {
        function someNestedFunction()
        {
            var redefinableVariable = "redefinableVariable's value from someNestedFunction";
            console.log('--> functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
            console.log('--> redefinableVariable: ', redefinableVariable);
        }
        var redefinableVariable = "redefinableVariable's value from someFunctionBody";
        console.log('this.variableAsProperty: ', this.variableAsProperty);
        console.log('functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
        console.log('redefinableVariable: ', redefinableVariable);
        someNestedFunction();
    },
    functionAsImplicitObject = functionAsImplicitObjectBody.bind(functionAsImplicitObjectBody);
    functionAsImplicitObjectBody.variableAsProperty = "variableAsProperty's value, set at time stamp: " + (new Date()).getTime();
    functionAsImplicitObject();
    
    // (spread-like operator "..." provides passing of any number of arguments to
    // the target internal "func" function in as many steps as necessary)
    var functionAsExplicitObject = function(...arguments)
    {
        var functionAsExplicitObjectBody = {
            variableAsProperty: "variableAsProperty's value",
            func: function(argument1, argument2)
            {
                function someNestedFunction()
                {
                    console.log('--> functionAsExplicitObjectBody.variableAsProperty: ',
                        functionAsExplicitObjectBody.variableAsProperty);
                }
                console.log("argument1: ", argument1);
                console.log("argument2: ", argument2);
                console.log("this.variableAsProperty: ", this.variableAsProperty);
                someNestedFunction();
            }    
        };
        return functionAsExplicitObjectBody.func(...arguments);
    };
    functionAsExplicitObject("argument1's value", "argument2's value");
    

0

ฉันประสบปัญหาเดียวกันแม้จะมีตัวแปรทั่วโลก ฉันค้นพบปัญหาของฉันคือตัวแปรส่วนกลางไม่คงอยู่ระหว่างไฟล์ html

<script>
    window.myVar = 'foo';
    window.myVarTwo = 'bar';
</script>
<object type="text/html" data="/myDataSource.html"></object>

ฉันพยายามอ้างอิง myVar และ myVarTwo ในไฟล์ HTML ที่โหลด แต่ได้รับข้อผิดพลาดที่ไม่ได้กำหนด เรื่องยาว / วันสั้นฉันค้นพบว่าสามารถอ้างอิงตัวแปรโดยใช้:

<!DOCTYPE html>
<html lang="en">
    <!! other stuff here !!>
    <script>

        var myHTMLVar = this.parent.myVar

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