Scope (kapsam), bir değişkenin kodun hangi bölümünden görülebildiğini belirleyen kurallar bütünüdür. JavaScript'te dört kapsam türü vardır: global kapsam, fonksiyon kapsamı, let/const/class için blok kapsamı ve ES modüllerinde modül kapsamı. Bunları birbirine bağlayan yapı ise sözcüksel (lexical) kapsam zinciridir. Bir değişken arandığında motor önce en içteki kapsama bakar, bulamazsa dışa doğru ilerler; zincirin hangi sırayla kurulacağını kodun nerede yazıldığı belirler, nerede çağrıldığı değil.

Bu yazıda her kapsam türünü Node.js 24'te çalıştırılmış örneklerle anlatıyoruz. var, let ve const farkının tamamı değişken tanımlama yazısında, tanımdan önce erişim ve TDZ konusu ise hoisting yazısında ayrıca ele alınıyor; burada odak, kapsamların nasıl iç içe geçtiğidir.

Global kapsam ve fonksiyon kapsamı

Global kapsam ve fonksiyon kapsamı

Hiçbir fonksiyonun, bloğun ya da modülün içinde olmayan bildirimler global kapsamdadır ve her yerden okunabilir. Bir fonksiyon ise kendi kapsamını oluşturur: İçinde tanımlanan değişkenler dışarıdan görünmez, ama fonksiyon dışarıdakileri görebilir. MDN bunu iç fonksiyon için bir tür kapsülleme (encapsulation) olarak anlatır:

javascript
const site = "keydal";

function rapor() {
  const ziyaret = 42;
  return `${site}: ${ziyaret}`;
}

console.log(rapor()); // keydal: 42
console.log(typeof ziyaret); // undefined

typeof tanımlı olmayan bir ad için hata fırlatmaz, "undefined" döndürür; bu yüzden örnekte değişkenin dışarıdan görünmediğini güvenle gösterebildik. Doğrudan console.log(ziyaret) yazsaydınız ReferenceError alırdınız.

Blok kapsamı: let, const ve var farkı

Süslü parantezlerle oluşturulan bir blok (if, for, tek başına { }) let, const ve class için yeni bir kapsam oluşturur. var ise blokları tanımaz; en yakın fonksiyona ya da global kapsama aittir. MDN, try...catch, switch ve for başlıklarının da var için kapsam oluşturmadığını belirtir:

javascript
function blokTesti() {
  if (true) {
    var a = "var";
    let b = "let";
    const c = "const";
  }
  console.log(a); // var
  console.log(typeof b, typeof c); // undefined undefined
}
blokTesti();

let x = 1;
{
  let x = 2;
  console.log("blok içi:", x); // blok içi: 2
}
console.log("blok dışı:", x); // blok dışı: 1

İkinci bölümde içteki x, dıştakini gölgeler (shadowing): Aynı ad, iç kapsamda yeni bir değişken olarak tanımlanır ve blok bittiğinde dıştaki değişken olduğu gibi kalır. Bloklar, IIFE'lere benzer şekilde geçici değişkenlerin global kapsamı kirletmesini önlemek için de kullanılabilir.

Sözcüksel kapsam ve kapsam zinciri

Sözcüksel kapsam ve kapsam zinciri

JavaScript sözcüksel (lexical, statik) kapsam kullanır: Bir fonksiyonun hangi değişkenleri göreceği, fonksiyonun kaynak kodda nereye yazıldığına göre belirlenir. İç içe fonksiyonlarda her iç fonksiyon, dışındaki tüm kapsamlara erişir; MDN buna kapsam zinciri (scope chaining) der:

javascript
function A(x) {
  function B(y) {
    function C(z) {
      return x + y + z;
    }
    return C(3);
  }
  return B(2);
}
console.log(A(1)); // 6

C, B'nin y parametresini ve A'nın x parametresini görür. Tersi doğru değildir: A, B ya da C içindeki değişkenlere erişemez. Aynı ad farklı kapsamlarda tanımlıysa en içteki kazanır, çünkü arama içten dışa doğru yapılır:

javascript
const deger = "global";
function dis() {
  const deger = "dış";
  function ic() {
    const deger = "iç";
    return deger;
  }
  return [ic(), deger];
}
console.log(dis(), deger); // [ 'iç', 'dış' ] global

Sözcüksel kapsamın en önemli sonucu, fonksiyonun çağrıldığı yerin değişken aramasını etkilememesidir. Aşağıdaki goster fonksiyonu, kendisini çağıran fonksiyonun yerel x değişkenini değil, tanımlandığı yerdeki x değişkenini görür:

javascript
const x = "tanımlandığı yer";

function goster() {
  return x;
}

function cagir() {
  const x = "çağrıldığı yer";
  return goster();
}

console.log(cagir()); // tanımlandığı yer

Spesifikasyonda kapsam: Environment Record

ECMAScript spesifikasyonu kapsamı Environment Record adlı bir yapıyla tanımlar. Bir fonksiyon bildirimi, blok ya da catch bölümü her değerlendirildiğinde yeni bir Environment Record oluşur ve o koddaki ad bağlamalarını tutar. Her kaydın [[OuterEnv]] adlı bir alanı, yani dıştaki kayda bir referansı vardır; kapsam zinciri bu referansların arka arkaya izlenmesidir. Global kaydın dış referansı null olduğu için zincir orada biter. Modül kayıtlarının dış ortamı ise global kayıttır. Spesifikasyon bu kayıtların yalnızca tanımlama aracı olduğunu, programın bunlara doğrudan erişemeyeceğini de belirtir.

Bir fonksiyon oluşturulduğunda o anki ortama referans tutar; bu yüzden dış fonksiyon bitse bile iç fonksiyon değişkenlere erişmeye devam eder. Bu davranışın adı closure'dır ve closure yazısında ayrıntılı örneklerle anlatılıyor.

Global nesne: var ve let farkı

Klasik bir script'in en üst düzeyinde var ile tanımlanan değişkenler ve fonksiyon bildirimleri global nesnenin (globalThis) özelliği olur. let, const ve class ise global kapsamda olsalar da globalThis üzerinde özellik oluşturmaz. node -e ile kodu script olarak çalıştırdığımızda fark görünür:

javascript
// node -e ile script olarak çalıştırıldı
var eski = 1;
let yeni = 2;
console.log(globalThis.eski, globalThis.yeni); // 1 undefined

Aynı kodu bir dosyaya yazıp node dosya.js ile çalıştırırsanız ikisi de undefined çıkar. MDN'ye göre Node.js CommonJS modüllerinde ve ES modüllerinde en üst düzeydeki bildirimler modüle aittir ve global nesneye eklenmez.

Bir de bildirimsiz atama vardır: Strict olmayan kodda tanımlanmamış bir ada değer atarsanız, değişken sessizce global nesnede oluşur. Strict mode bu hatayı yakalar:

javascript
function sayac() {
  toplam = 5;
}
sayac();
console.log(globalThis.toplam); // 5

function katiSayac() {
  "use strict";
  try {
    baskaToplam = 5;
  } catch (e) {
    console.log(e.name + ": " + e.message); // ReferenceError: baskaToplam is not defined
  }
}
katiSayac();

Modül kapsamı

ES modülünde en üst düzeyde tanımlanan değişkenler yalnızca o modüle aittir; var bile global nesneye eklenmez. Başka bir dosyanın kullanabilmesi için değeri export ile açmanız gerekir. Aşağıdaki iki dosyada sayi değişkeni sayac.mjs içinde gizli kalır:

javascript
// sayac.mjs
let sayi = 0;
export const arttir = () => ++sayi;

// ana.mjs
import { arttir } from "./sayac.mjs";

arttir();
console.log(arttir()); // 2
console.log(typeof sayi); // undefined
var modulVar = 1;
console.log(globalThis.modulVar); // undefined

Modüller otomatik olarak strict mode'da çalışır. import/export ayrıntıları için ES modülleri yazısına bakabilirsiniz.

Kapsam türleri tablosu

Kapsam türleri tablosu: Global, Fonksiyon, Blok, Modül
KapsamNe oluştururHangi bildirimlerNot
GlobalKlasik script en üst düzeyiTümüvar ve function globalThis özelliği olur
FonksiyonHer fonksiyon çağrısıvar, let, const, function, classParametreler de bu kapsamdadır
Blok{ }, if, for, while, trylet, const, classvar blok kapsamını tanımaz
ModülHer ES modülüTümüGlobal nesneye eklenmez, strict mode
Sözcüksel zincirİç içe yazılan kodTümüArama içten dışa, tanım yerine göre

Sık yapılan hatalar

  • Blok içinde var ile tanımlanan değişkenin bloğun dışında görünmeyeceğini sanmak.
  • Tanımlamadan atama yapıp yanlışlıkla global değişken oluşturmak; strict mode bunu ReferenceError ile engeller.
  • Bir fonksiyonun, kendisini çağıran fonksiyonun yerel değişkenlerini göreceğini düşünmek.
  • İç kapsamda aynı adla değişken tanımlayıp dıştakini istemeden gölgelemek.
  • let ile tanımlı bir değişkeni aynı kapsamda bildirimden önce kullanmak: Dışarıda aynı adlı değişken olsa bile ReferenceError: Cannot access 'ad' before initialization alırsınız.
javascript
const ad = "dış";
function test() {
  try {
    console.log(ad);
  } catch (e) {
    console.log(e.name + ": " + e.message); // ReferenceError: Cannot access 'ad' before initialization
  }
  const ad = "iç";
}
test();

Sık Sorulan Sorular

Scope ile closure arasındaki fark nedir?

Scope, bir adın nereden görülebildiğini belirleyen kural setidir. Closure ise bir fonksiyonun bu kurallarla erişebildiği dış değişkenleri, dış kapsam bittikten sonra da yanında taşımasıdır. Kısacası closure, sözcüksel kapsamın fonksiyonlar dışarı taşındığında ortaya çıkan sonucudur.

Dinamik kapsam JavaScript'te var mı?

Değişken araması için yok; JavaScript sözcüksel kapsam kullanır. Çağrı anında belirlenen tek yaygın değer this'tir. with ifadesi nesne özelliklerini kapsama ekleyerek bu kuralı karmaşıklaştırır ve strict mode'da kullanılamaz.

IIFE hâlâ gerekli mi?

MDN, IIFE'nin geleneksel kullanımlarının çoğunun modüller ve blok kapsamlı bildirimlerle gereksizleştiğini belirtir. Bir ifadenin ortasında birkaç satırlık hesaplama yapıp sonuç döndürmek gibi durumlarda hâlâ kullanılabilir.

Fonksiyon parametreleri hangi kapsamdadır?

Parametreler fonksiyonun kapsamındadır. Default parametre kullanıldığında ise MDN'ye göre parametre başlatıcıları fonksiyon gövdesinin üstündeki ayrı bir kapsamda çalışır; bu yüzden gövdede tanımlanan değişkenleri göremez. Ayrıntılar default ve rest parametreler yazısında.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: JavaScript öğrenme rehberi · Closure nedir · var, let ve const farkı · Hoisting ve TDZ · Strict mode · ES modülleri