Scope Türleri ve Closure

Haluk Keskin
5 min readDec 29, 2018

--

JS’in çekirdek kavramlarından Scope türleri ve Closure’a hemen atılmadan önce bunun temellerini oluşturan bazı detay başlıkların üzerinden geçmekte fayda var.

Lexical Scope (Static Scope)

Bir JS kod parçasının runtime anında interpreter tarafından derlenmesine değinerek başlayalım. Bu işlem diğer dillerde olduğu gibi alttaki 3 fazda gerçekleşir:

  1. Lexing (Tokenizing)
  2. Parsing (Abstract Syntax Tree(AST))
  3. Code Generation (Machine code)

(Derleme işleminin yukarıdaki gibi 3 başlıktan ibaret olduğunu düşünüyorsak burada çözülmüş bir sürü ciddi probleme büyük haksızlık etmiş oluruz ki bunun içinde mesela kodun icra(executing) performansı var.)

Lexing

Buradaki aktör lexical analyzer, belirli kurallara göre karakterler okunup token’a dönüştürülür. Kod metninden aşağıdaki gibi bir token listesi üretilmiş oluyor.

let count = 2; 
/*
[
{value: 'let', type: 'keyword'},
{value: 'count', type: 'identifier'},
...
]
*/

Bu işlem sırasında kelimeleri ayrıştırmanın aslında basit bir mantığı var. Kod harf harf okunurken bir boşluk karakterine, operatör işaretine(== gibi) veya özel bir karaktere rastlandığında bir kelime tamamlanıp ayrıştırılır.

Parsing

Syntax analyzer diğer adıyla parser, bir önceki aşamada oluşturulmuş token serisini kurallı anlamlı bir yapıya(Abstract Syntax Tree) dönüştürür. Ayrıca dilin syntax’ına uygun olup olmadığı burada kontrol edilir ve buna göre varsa syntax hataları fırlatılır. Parsing sonrası oluşacak çıktıyı(output) daha iyi anlamak için burayı tıklayın.

Code Generation ve Executing

Bu süreçte input olarak alınan AST, makine koduna dönüştürülür. Bu kod bir JS Engine(V8, SpiderMonkey…) tarafından execute edilir.

Lexical Scope Bunun Neresinde?

Scope, compiler ve engine icra boyunca iletişim halindedir.

let count=2;

Bu tek satır 2 aşamadan geçmeli; birincisi, Compiler tarafından yürütülmeli ikincisi ise engine tarafından.

İlk olarak lexing sırasında tokenlar üretilir ve scope oluşturulur(lexical scope), sonra parsing ile AST oluşturulur. Ve Code Generation! Tam bu anda durup neler olduğuna bakalım.

  • Compiler count tanımlamasıyla karşılaştığında ilgili bloktaki scope’a bakar, zaten tanımlanmışsa scope’a yeni bir tanımlamayı es geçer, aksi durumda yeni bir tanımlama yapılmasını ister.
  • Compiler daha sonra executing için gereken makine kodunu üretir.
  • Engine, erişilir durumda count değişkenini scope aracılığıyla kontrol eder, ilgili blokta varsa bu değişkeni kullanır, yoksa üst bloklar için tanımlanmış olabilir (nested scope) arayıp kullanmaya çalışır fakat sonuç olarak hiç bulamazsa hata fırlatır.

Nested scope için şöyle bir örnek verebiliriz;

function foo(a) {  
console.log( a + b );
}
var b = 2;foo( 2 ); // 4

Yukardaki kod parçasına göre, engine, foo fonksiyonu bloğunda b değişkenini bulamayacağı için en yakın dış scope’a sorup bulacak. Tam bu noktada lexical scope modelinin nasıl kullanıldığı vurgulanmış oluyor.

Lexical Scope’a göre aşağıdaki print fonksiyonu ekranda 12 gösterecektir. print(); ifadesi hangi scope içerisinde çağrılırsa çağrılsın hangi scope’u referans alacağı derleme anında belirlenmiştir, number = 12'nin bulunduğu scope print fonksiyonu için lexical scope anlamına gelir. Mesela dinamik scope olsaydı, print(); ifadesindeki number’ın ne olduğu print fonksiyonunun nerede tanımlandığına değil nereden çağrıldığına göre değişecekti çünkü dinamik scope kavramında scope runtime anında belirlenir. Aşağıdaki kod parçasına göre print(); ifadesinden önce number = 14 ataması yapılmış dolayısıyla ekranda 14 görecektik çünkü dinamik scope runtime anında scope’u oluşturur.

let number = 12;function print() {
console.log(number);
}
function log() {
let number = 14;
print();
}
// Prints 12
log();
  • Lexical scope bir model ve adından da anlaşıldığı üzere lexing sırasında oluşturulur.
  • Her tanımlanmış fonksiyon kendi scope’una sahip ve değişkenlerin compiler tarafından scope’a kaydedilmesi sağlanır.
  • İç bloklardaki ifadelerin icrası sırasında ilgili bloğa ait olmayan değişken, dış bloklardan temin edilir.

Fonksiyon ve Blok Seviyesinde Scope

Fonksiyon Seviyesinde Scope

function foo(a) {  
var b = 2; // some code
function bar() { // ... }
var c = 3;
}
foo();

Tanımlanan her fonksiyon kendisi için bir scope oluşturur. Yukarıdaki kod parçasını incelediğimizde burada üç farklı iç içe geçmiş scope olduğunu görürüz. Bunlardan ilki global scope ki bunu block seviyesinde scope olarak düşünebiliriz.

{ // global scope'u gizli bir block scope olarak düşünebiliriz.
function foo(a) {
var b = 2; // şuraya biraz ağaç
function bar() { // ... }
var c = 3;
}
foo();
}

Diğer ikisi ise fonksiyon seviyesinde iç içe geçmiştir. yani foo fonksiyonunun kendine ait bir scope’u var ve bu scope içinde tanımlanmış bar fonksiyonunun kendi scope’u var.

Blok Seviyesinde Scope

for (let i=0; i<10; i++) {  
console.log( i );
}

for ifadesindeki i artık bu döngü içerisinde sorumlu yani for blokları arasındaki scope’a ait.

if (isSomethingTrue) {
let i = 4;
console.log(i);
}
try {
x = 4;
}
catch(err) {
console.log(err);
}
try {
y = 2;
}
catch(err) {
console.log(err);
}

Bu durum if/else blokları için de geçerli ve hatta try/catch blokları için de geçerli.

Ve Nihayet Closure

Kısaca closure, bir fonksiyonun kendi lexical scope’u dışında çalıştırılsa bile bu scope’a erişebiliyor olmasıdır. Nasıl mı?

// global scopefunction foo() {  // foo için scope, bar için lexical scope
var a = 2;
function bar() {
console.log( a ); // 2
}
return bar; // foo'a ait kapsam'a erişmek için kapı
}
var baz = foo(); // kapıyı buldunbaz(); // Ve evet Closure!

Normalde foo’nun dışından kendi kapsamındaki bir değişkene erişemezdik fakat bar fonksiyonu bu kapsama erişebilir durumda ve foo bar fonksiyonunu açık kapı olarak geri dönüyor.

var baz = foo(); // kapıyı buldun

Bu satırdan sonra GC (garbage collector) devreye girip foo’nun bellekte sakladıklarını temizlemesi beklenir fakat, foo bir fonksiyon dönüyor (bar) ve bu fonksiyon foo kapsamındaki bir elemana erişiyor, sonuç olarak bu erişimi hayatta tutan bar’ın referansını taşıyan baz olmuş oluyor.

Kaynak:

--

--