javascript - let veya const ile beyan edilen değişkenler yukarı taşınır mı?
Bir süredir ES6 ile oynuyorum ve fark ettim ki var ile deklar edilen değişkenler beklendiği gibi yukarı taşınırken...
console.log(typeof name); // undefined
var name = "John";
...let veya const ile bildirilen değişkenlerin hoisting ile ilgili bazı sorunları olduğu görülüyor:
console.log(typeof name); // ReferenceError
let name = "John";
ve
console.log(typeof name); // ReferenceError
const name = "John";
Bu, let veya const ile bildirilen değişkenlerin hoist edilmediği anlamına mı geliyor? Burada gerçekten neler oluyor? Bu konuda let ve const arasında herhangi bir fark var mı?
Cevap #1
@thefourtheye, bu değişkenlerin tanımlanmadan önce erişilemeyeceğini söylediğinde haklı. Ancak durum bundan biraz daha karmaşık.
letveyaconstile bildirilen değişkenler taşınmıyor mu? Burada gerçekten neler oluyor?
Tüm bildiriler (var, let, const, function, function*, class) JavaScript'te "yukarı taşınır". Bu, bir ad bir kapsamda bildirildiğinde, o kapsamda tanımlayıcının her zaman o özel değişkeni referans alacağı anlamına gelir:
x = "global";
// function scope:
(function() {
x; // not "global"
var/let/… x;
}());
// block scope (not for `var`s):
{
x; // not "global"
let/const/… x;
}
Bu, hem fonksiyon hem de blok kapsamları için geçerlidir1.
var/function/function* bildirimleri ile let/const/class bildirimleri arasındaki fark başlatmadır.
İlk olanlar, bağlam oluşturulduğunda, kapsamın en üstünde undefined veya (üretici) fonksiyon ile başlatılır. Ancak sözcüksel olarak bildirilen değişkenler başlatılmamış olarak kalır. Bu, ona erişmeye çalıştığınızda bir ReferenceError istisnası fırlatılacağı anlamına gelir. Bu değişken yalnızca let/const/class ifadesi değerlendirildiğinde başlatılır; bunun öncesindeki (yukarıdaki) her şeye geçici ölü bölge denir.
x = y = "global";
(function() {
x; // undefined
y; // Reference error: y is not defined
var x = "local";
let y = "local";
}());
Bir let y; ifadesinin değişkeni tıpkı let y=undefined; yapacağı gibi undefined ile başlattığını fark edin.
Zamansal ölü bölge, sözdizimsel bir konum değildir; daha ziyade değişkenin (kapsamın) oluşturulması ile başlatılması arasındaki zamandır. Değişkene, bildirimden önceki kodda referans vermek bir hata değildir, solange o kod çalıştırılmamışsa (ör. bir fonksiyon gövdesi veya yalnızca ölü kod), ve başlatmadan önce değişkene erişirseniz bir istisna fırlatır; bu, erişim kodu bildirimin altında olsa bile geçerlidir (ör. çok erken çağrılan bir hoisted fonksiyon bildirimi içinde).
Bu konuda
letileconstarasında herhangi bir fark var mı?
Hayır, kaldırma (hoisting) bakımından aynı şekilde çalışırlar. Aralarındaki tek fark, bir const değişkenin atanması gerektiği ve yalnızca bildirim kısmındaki başlatıcıda atanabileceğidir (const one=1;, hem const one; hem de sonradan yapılan yeniden atamalar one=2 geçersizdir).
1: var bildirimleri elbette sadece fonksiyon düzeyinde çalışmaya devam ediyor
Cevap #2
ECMAScript 6 (ECMAScript 2015) spesifikasyonunun let ve const bildirimleri bölümünden alıntı yaparak,
Değişkenler, bulundukları Sözdizimsel Ortam (Lexical Environment) oluşturulduğunda yaratılır, ancak değişkenin Sözdizimsel Bağlamı (LexicalBinding) değerlendirilen kadar hiçbir şekilde erişilemez.
Yani, sorunuza cevap vermek gerekirse, evet, let ve const yüklenir (hoist edilir) ama bunlara, gerçek bildirim çalışma zamanında değerlendirilmadan önce erişemezsiniz.
Cevap #3
ES6, blok seviyesi kapsam ile gelen Let değişkenlerini tanıtır. ES5'e kadar blok seviyesi kapsam yoktu, bu yüzden bir blok içinde tanımlanan değişkenler her zaman fonksiyon seviyesi kapsamına taşınırdı (hoisted).
Temel olarak Scope, değişkenlerinizin programınızın neresinde görünebileceğini ifade eder ve bu, beyan ettiğiniz değişkenleri nerede kullanmanıza izin verildiğini belirler. ES5’te global scope, function scope ve try/catch scope bulunur, ES6 ile ise Let kullanarak blok düzeyinde kapsam elde ederiz.
varanahtar kelimesiyle bir değişken tanımladığınızda, tanımlandığı andan itibaren tüm fonksiyon tarafından bilinir.letifadesiyle bir değişken tanımladığınızda, o değişken yalnızca tanımlandığı blokta bilinir.function doSomething(arr){ //i is known here but undefined //j is not known here console.log(i); console.log(j); for(var i=0; i<arr.length; i++){ //i is known here } //i is known here //j is not known here console.log(i); console.log(j); for(let j=0; j<arr.length; j++){ //j is known here } //i is known here //j is not known here console.log(i); console.log(j); } doSomething(["Thalaivar", "Vinoth", "Kabali", "Dinesh"]);
Eğer kodu çalıştırırsanız, j değişkeninin sadece döngü içinde bilindiğini ve öncesinde veya sonrasında bilinmediğini görebilirsiniz. Buna karşın, i değişkenimiz tanımlandığı andan itibaren tüm fonksiyon içinde bilinir.
let kullanmanın başka büyük bir avantajı daha var çünkü yeni bir sözdizimsel ortam oluşturur ve eski bir referansı tutmak yerine yeni bir değer bağlar.
for(var i=1; i<6; i++){
setTimeout(function(){
console.log(i);
},1000)
}
for(let i=1; i<6; i++){
setTimeout(function(){
console.log(i);
},1000)
}
İlk for döngüsü her zaman son değeri yazdırır, let ile yeni bir kapsam oluşturur ve taze değerler bağlayarak bize 1, 2, 3, 4, 5 yazdırır.
constants kısmına gelirsek, temelde let gibi çalışır, tek farkları değerlerinin değiştirilememesidir. Sabitlerde mutasyon izinlidir ama yeniden atama izinli değildir.
const foo = {};
foo.bar = 42;
console.log(foo.bar); //works
const name = []
name.push("Vinoth");
console.log(name); //works
const age = 100;
age = 20; //Throws Uncaught TypeError: Assignment to constant variable.
console.log(age);
Eğer bir sabit bir nesne'ye referans veriyorsa, her zaman o nesne'ye referans verir ama nesnenin kendisi değiştirilebilir (eğer değiştirilebilir ise). Eğer değiştirilemez bir nesneye sahip olmak istiyorsanız, Object.freeze([]) kullanabilirsiniz.
Cevap #4
ECMAScript® 2021'e göre
Let ve Const Bildirimleri
- let ve const bildirimleri, çalışmakta olan yürütme bağlamının Sözdizimsel Ortamına (LexicalEnvironment) özgü değişkenleri tanımlar.
- Değişkenler, bulundukları Ortam Kaydı oluşturulduğunda yaratılır, ancak değişkenin Sözdizimsel Bağlamı değerlendirilene kadar hiçbir şekilde erişilemez.
- Bir Başlangıç Değeri ile bir Sözdizimsel Bağlama tarafından tanımlanan bir değişkene, Sözdizimsel Bağlama değerlendirildiğinde Başlangıç Değerinin Atama İfadesinin değeri atanır, değişken oluşturulduğunda değil.
- Eğer bir let bildirimi içindeki LexicalBinding'in bir Başlatıcısı yoksa, LexicalBinding değerlendirildiğinde değişkene undefined değeri atanır.
Blok Bildirimi Örnekleme
- Bir Blok veya CaseBlock değerlendirildiğinde, yeni bir bildirisel Ortam Kaydı oluşturulur ve blokta bildirilen her blok kapsamlı değişken, sabit, fonksiyon veya sınıf için bağlamalar Ortam Kaydında örneklendirilir.
- Kontrol bloktan ne zaman ayrılırsa ayrılsın, Sözdizimsel Ortam her zaman eski haline geri döndürülür.
Üst Düzey Leksiksel Olarak Tanımlanmış İsimler
Bir fonksiyonun veya betiğin en üst seviyesinde, fonksiyon bildirimleri, sözcüksel (lexical) bildirimler gibi değil, var bildirimleri gibi ele alınır.
Sonuç
let ve const kaldırılır (hoisted) ama başlatılmaz.
Blok içinde değişkenin bildiriminden önce değişkene başvurmak bir ReferenceError ile sonuçlanır, çünkü değişken bloktaki başlangıçtan bildirim işlenene kadar "geçici ölü bölge" içindedir.
Aşağıdaki örnekler, "let" değişkenlerinin leksikal kapsam/ iç içe leksikal kapsamda nasıl davrandığını açıkça ortaya koymaktadır.
Örnek 1
var a;
console.log(a); //undefined
console.log(b); //undefined
var b;
let x;
console.log(x); //undefined
console.log(y); // Uncaught ReferenceError: y is not defined
let y;
Değişken 'y' bir referenceError verir, bu onun hoist edilmediği anlamına gelmez. Değişken, içerdiği ortam oluşturulduğunda yaratılır. Ancak, erişilemez bir "geçici ölü bölge" içinde olması nedeniyle ona erişilemez.
Örnek 2
let mylet = 'my value';
(function() {
//let mylet;
console.log(mylet); // "my value"
mylet = 'local value';
})();
Örnek 3
let mylet = 'my value';
(function() {
let mylet;
console.log(mylet); // undefined
mylet = 'local value';
})();
Örnek 3'te, fonksiyon içindeki yeni tanımlanmış "mylet" değişkeninin log ifadesinden önce bir Başlatıcısı yoktur, bu nedenle değeri "undefined" olur.
Kaynak
ECMA MDN
