Genel değişkenin JavaScript'te tanımsız bir değere sahip olmasına şaşırdım


89

Bugün, küresel bir değişkenin undefinedbelirli bir durumda değeri olduğunu görünce tamamen şaşırdım .

Misal:

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

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

Çıktı olarak verir

undefined
20

Burada, JavaScript motoru neden küresel değeri düşünüyor undefined? JavaScript'in yorumlanmış bir dil olduğunu biliyorum. İşlevdeki değişkenleri nasıl değerlendirebilir?

Bu JavaScript motorundan kaynaklanan bir tuzak mı?

Yanıtlar:


177

Bu fenomen, JavaScript Değişken Kaldırma olarak bilinir .

Fonksiyonunuzdaki global değişkene hiçbir noktada erişmiyorsunuz; sadece yerel valuedeğişkene erişiyorsunuz .

Kodunuz aşağıdakine eşdeğerdir:

var value = 10;

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

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

test();

Hala şaşırdığına şaşırdın mı undefined?


Açıklama:

Bu, her JavaScript programcısının er ya da geç karşılaştığı bir şeydir. Basitçe ifade etmek gerekirse, beyan ettiğiniz değişkenler her zaman yerel kapanışınızın en üstüne kaldırılır . Dolayısıyla, değişkeninizi ilk console.logaramadan sonra bildirmiş olsanız bile , yine de daha önce bildirmişsiniz gibi kabul edilir.
Ancak sadece beyanname kısmı kaldırılıyor; ödev ise değil.

Yani, ilk aradığınızda console.log(value), henüz kendisine atanmış hiçbir şey olmayan yerel olarak bildirilmiş değişkeninize başvuruyordunuz; dolayısıyla undefined.

İşte başka bir örnek :

var test = 'start';

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

end();
alert(test);

Bunun neyi uyaracağını düşünüyorsun? Hayır, sadece okumaya devam etme, bir düşün. Değeri nedir test?

Dışında bir şey söylediyseniz start, yanılıyorsunuz. Yukarıdaki kod şuna eşdeğerdir:

var test = 'start';

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

end();
alert(test);

böylece global değişken asla etkilenmez.

Gördüğünüz gibi, değişken bildiriminizi nereye koyarsanız koyun, her zaman yerel kapanışınızın en üstüne kaldırılır .


Kenar notu:

Bu aynı zamanda işlevler için de geçerlidir.

Şu kod parçasını düşünün :

test("Won't work!");

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

bu size bir referans hatası verecektir:

Yakalanmamış Referans Hatası: test tanımlanmadı

Bu kod parçası iyi çalıştığı için pek çok geliştiriciyi kaçırır:

test("Works!");

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

Belirtildiği gibi atama parçası olduğu için bunun nedeni, olduğu değil göndere çekildi. Yani ilk örnekte ne zaman test("Won't work!")çalıştırıldı,test değişken zaten bildirilmiştir, ancak henüz kendisine atanan fonksiyona sahip değildir.

İkinci örnekte, değişken atama kullanmıyoruz. Aksine, biz doğru fonksiyon beyanı sözdizimi, kullandığınız yapar tamamen göndere çekildi fonksiyonu olsun.


Ben Cherry , bununla ilgili olarak uygun şekilde JavaScript Kapsam Belirleme ve Kaldırma başlıklı mükemmel bir makale yazdı .
Oku onu. Size tüm resmi tüm ayrıntılarıyla verecektir.


27
Bu iyi bir açıklamadır. Ancak, bir işlevin içindeki global değişkenlere erişebileceğiniz bir çözümü özlüyorum.
DuKes0mE

1
@ DuKes0mE - bir fonksiyonun içindeki global değişkenlere her zaman erişebilirsiniz.
Joseph Silber

3
evet, ve neden açılış gönderisindeki A durumu çalışmıyor ve tanımsız olduğunu söylüyor? Küresel yerine yerel bir değişken olarak yorumlanacağını anlıyorum, ama o zaman nasıl küresel olacak?
DuKes0mE

4
global değişken kullanım window.value erişmek için
Venkat Reddy

1
bu tür değişken kaldırma ne zaman uygulandı? bu javascript'te her zaman standart mıydı?
Dieskim

55

Buradaki sorunun açıklanması beni biraz hayal kırıklığına uğrattı, ancak kimse bir çözüm önermedi. İşlev kapsamındaki global bir değişkene, işlev önce tanımsız bir yerel değişken oluşturmadan erişmek istiyorsanız, var olanwindow.varName


8
Evet, diğerlerinin bir çözüm önermemesi berbat çünkü bu, Google sonuçlarındaki ilk sonuç.
Js

2
Teorik bilgi ile işi halletmek arasındaki fark budur. Cevabınız için teşekkürler!
hansTheFranz

Benim için, herhangi bir işlevin herhangi bir yerinde global bir ad vermem bir hata. En azından kafa karışıklığı yaratıyor. En kötü ihtimalle bir google aramasına ihtiyaç duyuyor. Teşekkürler
dcromley

Javascript her geçen gün beni şaşırtmaya devam ediyor. Teşekkürler dostum, cevap yardımcı oldu.
Raf

Çözüm için teşekkürler, bunu genel bir değişken tanımsız olduktan sonra ancak Safari'de buldum. Diğer 'dahil' dosyaları ve 'google' gibi genel değişkenler çıktı, bu yüzden google'ın kullandığı yaklaşımı kopyaladım: window.globalVarFromJS = window.globalVarFromJS || {}; Sonra çözümünüzü buldum ve ona ekleyeceğimi düşündüm.
Ralph Hinkley

10

JavaScript'teki değişkenler her zaman işlev çapında kapsama sahiptir. İşlevin ortasında tanımlanmış olsalar bile daha önce görülebilirler. İşlev kaldırma ile benzer olaylar gözlemlenebilir.

Bununla birlikte, ilk değişkeni (dışını gölgeleyen içteki) console.log(value)görür , ancak henüz başlatılmamıştır. Tüm değişken bildirimleri örtük olarak işlevin başına taşınmış gibi düşünebilirsiniz ( değilvaluevalue tanımları aynı yerde bırakılan ederken, en iç kod bloğu).

Ayrıca bakınız


Basit kelimeleri hep severim +1 :)
Jashwant

3

Global bir değişken vardır value, ancak kontrol testfonksiyona girdiğinde value, global olanı gölgeleyen başka bir değişken bildirilir. JavaScript'teki değişken bildirimleri ( ancak atamalar değil ), bildirildikleri kapsamın en üstüne kaldırıldığından:

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

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

Aynı şeyin işlev bildirimleri için de geçerli olduğunu unutmayın; bu, kodunuzda tanımlanmış görünmeden önce bir işlevi çağırabileceğiniz anlamına gelir:

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

Ayrıca, ikisini bir işlev ifadesi olarak birleştirdiğinizde, değişkenin undefinedatama gerçekleşene kadar olacağını ve bu gerçekleşene kadar işlevi çağıramayacağınızı belirtmek gerekir:

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

0
  1. Dış değişkenlere erişimi korumanın en basit yolu (yalnızca genel kapsam değil), elbette, onları işlevlerde aynı adla yeniden bildirmemeye çalışmaktır; sadece orada var kullanmayın . Kullanımı uygun tanımlayıcı isimlendirme kurallarına tavsiye edilir. Bunlarla, değer gibi adlandırılmış değişkenler elde etmek zor olacaktır (bu yönün, sorudaki örnekle ilgili olması gerekmez, çünkü bu değişken adı basitlik için verilmiş olabilir).

  2. İşlev başka bir yerde yeniden kullanılabilirse ve bu nedenle dış değişkenin bu yeni bağlamda gerçekten tanımlandığına dair bir garanti yoksa, Eval işlevi kullanılabilir. Bu işlem yavaştır, bu nedenle performans gerektiren işlevler için önerilmez:

    if (typeof variable === "undefined")
    {
        eval("var variable = 'Some value';");
    }
    
  3. Erişmek istediğiniz dış kapsam değişkeni, adlandırılmış bir işlevde tanımlanmışsa, o zaman işleve ilk etapta eklenebilir ve ardından kodun herhangi bir yerinden erişilebilir - derinlemesine yuvalanmış işlevlerden veya dışındaki olay işleyicilerinden diğer her Şey. Özelliklere erişmenin çok daha yavaş olduğuna ve programlama şeklinizi değiştirmenizi gerektireceğine dikkat edin, bu nedenle gerçekten gerekli olmadıkça önerilmez: Fonksiyonların özellikleri olarak değişkenler (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

Global değişkenlerde bile aynı problemle karşılaşıyordum. Benim sorunum, keşfettim, global değişkenin html dosyaları arasında kalıcı olmamasıydı.

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

Yüklenen HTML dosyasında myVar ve myVarTwo'ya başvurmaya çalıştım, ancak tanımsız hatayı aldım. Uzun hikaye / gün kısa, değişkenleri kullanarak şunları kullanarak referans verebileceğimi keşfettim:

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

        var myHTMLVar = this.parent.myVar

        /* other stuff here */
    </script>
</html>
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.