JAVASCRIPT’IN TARIHÇESI

Execution Context, Lexical Environment, Scope ve Clousure Anlamak

JS derinlerine inerek kodun JS tarafından bellekte nasıl işletildiğini anlamak için Execution Context (İşletilme bağlamı) ve Lexical Enviroment (Sözcük Çerçevesi), Scope ve Clousure’ danbahsediyor olacağım.

--

Javascript Tarihçesi yazıma ilk başlarken Neden ? var → let, const blog yazısını yazmıştım. O blog yazısının içerisinde şöyle bir kısım vardı.

Neden ? var → let, const içerisinden bir alıntı.

Bu yazıdan sonra yazdığım yazılarda Pure Functions , IIFE , Data Types Immutable, Pass By Reference bahsettim. Bu kapsam da eksik kalan iki konu var

  • Scoping , Hoisting, Lexical Scoping
  • LHS, RHS(Left Hand, Right Hand)

Bu yazımda Lexical Environment ve Execution Context üzerinde yoğunlaşacağız.

JS Örnek Bir Kod

JS Örnek bir kod yazmak istediğimizde önce HTML içerisinde HTML elemanlarını (p, div vb..) tanımlayıp en sonda da script.js dosyasını load ederek JS bu DOM yapısı üzerinde çalışmasını sağlarız.

index.html

Burda her id elemana tek tek erişip kendisine tanımlanan kelimeleri ilgili elemana innerHTML olarak eşliyoruz.

script.js

JS Çalıştırılmasının Ana Akışı

  • JS kaynak kodları → Parser tarafından işlenerek → Abstract Syntax Tree
  • Abstract Syntax Tree → Interpreter tarafından işlenerek → ByteCode
  • ByteCode üstünde çalıştırılan sistemin profiling bilgilerinide alarak → Optimizing Compiler işlenerek → Optimized Makine koduna dönüştürülür.
  • Daha sonra çalıştırılan kod üzerinde de-optimize edilerek bytecode ve elde edilen optimization ve profiling verileri ile tekrardan
JS Engines: Good Part by Mathias Bynens & Benedikt Meurer — JSConf EU 2018

Syntax Parsing → Interprete → Compile

JS kodları işlendiğinde bunları Functions ve Variables olarak makine koduna çevirir ve adım adım ilerletir. İlerde bunu daha iyi analiz edeceğiz ama önce gelin Lexical Environment, Execution Context ve Object Model kavramlarını bir anlayalım ..

JS Compile

Temel Kavramlar

Lexical Environment

  • JS kodundaki parçaların (Variable, Function, Blok) nerede olduğu önemlidir ?
  • Kod parçacıkları neyin içerisinde , Veya çevresi ne ile sarılı ?
  • Bu kapsama Lexical(Grammar) Environment yani (Sözcük Ortamı) denir.
  • Bu ortam sizin hangi diğer sözcükler ile nasıl iletişim kurabileceğiniz konusunda, compiler karar vermesini sağlayacak. (var → function scope, let,const → block scope olması …)

Execution Context

  • Lexical Environment fiziksel olarak tanımlaman alanların kod çalışırken hangi kapsamda olacağını belirten çalışma bağlamına Execution Context denir.
  • Özetle kodlar çalışırken ilgili değişkenler , fonksiyon adresleri vs.. çalışan kod ile ilgili bağlamlar burda tutulur ve buradan erişilir.

Object Model

  • JS Object’ler String key olacak şekilde Dictionary/Map gibi tutulur. Örneğin içerisinde x ve y değeri olan bir object tanımladığımızda Objenin her bir x ve y property için Value, Writable, Enumerable ve Configurable bilgilerini tutar. ( Bu konunun detayını Chrome JS Nasıl İşletir ? yazımı okumanızı öneririm)
JS Engine Objeyi Nasıl Tutuyor ?
  • Object içerisinde key , value değerlerinde value → obje/array/primitif type olabilecek şekilde iç içe object yapıları oluşturabilir. Örneğin aşağıdaki Address objesinde olduğu gibi. Bu sayede çok karmaşık obje tanımlamarı yapabilirsiniz.
Address Object

Execution Context Nasıl Çalışır ?

İster JS Tarayıcıda çalışsın, İster sunucu, ister local her ortamda JS Engine çalışma ortamına bir tane Global Execution Context oluşturur ve bu Global Scope içerisinden Tarayıcılar için WebAPI ye erişim imkanı sağlar. Bu ilk Global Execution Context içerisinde bir Global Object ve this nesnesi bulunur. Bunu JSEngine kendisi ilk JS çalıştırılacak ortamda ayağa kaltığı zaman yerleştirir.

Global Execution Context -1

Örneğin aşağıda Tarayıcıda çalışan JS kodları için Console this ve window Global Objeye karşılık gelir.

Tarayıcıdaki Global Obj

Peki aşağıdaki kod nasıl çalışıyor.

Adım 1. Hello World → a değişkenine ata…
Adım 2. b fonksiyonunu çağır ve ekrana Hello World yazdır.

Burda kodun sonuna geldiğimizde
a, b, this, this.a, this.b, window değelerine baktığımızda aşağıdaki
sonuçları görürüz.
Kodun içerisinde Değerler

Bu durumda Global Execution Context aynı zamanda bizim kodumuzda tutulmaya(Your Code) başladı. Outer Environment ise sonradan bahsedeceğimiz bir konu..

Global Execution Context içeriği

Hoisting Nedir?

Hoisting kod üzerinden anlatmak daha kolay o yüzden bende öyle başlıyorum Aşağıdaki örnekte

Adım 1: Hello World kelimesini → a atanır.
Adım 2: Ekrana Hello World yazdırılır.
Adım 3: b fonksiyonu çağrılır ve ekrana b yazdırılır.
Hoisting Örnek Kod

Peki yukardaki örnekte 2 seçenek olabilirdi.

A durumu → console.log(a) a değişkenini tanımlamadığı için ilk baştan bir exception atabilirdi ama atmamış değeri undefined olarak ekrana yazmış . Bu nasıl olabilir diğer dillerde bu hata verirdi..

B durumu → bu daha da ilginç b() çağrımı sırasında ortada fonksiyon yok burda birde ekrana b yazmış. Bu daha da ilginç

A ve B durumu nasıl böyle çalışıyor ? JS Engine bu kodu nasıl algılıyor. İşte bu kısımda biraz daha derinlere inmek gerekiyor

Execution Context 2 Fazı Bulunur

Öncelike Execution Context (işleme bağlamının) 2 tane fazı bulunur. Bunlardan birisi Creation (Oluşturulma) diğeride Execution(Run) yani çalıştırılma fazı.

Execution Context Fazları

1. Creation Phase (Oluşturulma Fazı)

Execution Context — Oluşturulma Fazı
  • Fonksiyonlar tümüyle bellek alanına konulur.
  • Değişkenler de alınır ama bu aşamda undefined olarak atanır var x=undefined
  • undefined bu değişkene henüz bir değer ataması yapılmadığı anlamına gelir.

2. Run Phase (Çalıştırılma Fazı)

Execution Context — Çalıştırılma Fazı
  • Çalıştırılması aşamasında kod satır satır işletilir.
  • console.log(a) → undefined çünkü oluşturulma fazında bu değer bulunup değişkenin değeri olarak undefined atanmıştı.
  • b() → fonksiyonu b yazıyor çünkü oluşturulma fazında bu fonksiyon bulunup referansı tutulmaya başlanmıştır. Bu sayede b fonksiyonu içerisinde değer ile birlikte ekrana yazdırılır.

Hoisting işte bu Creation(Oluşturma) fazında değişkenleri ve fonksiyonları yukarı taşıma özelliği sayesinde değişken ve fonksiyonlara çalıştırılan kodun ilerisinde olması durumunda bile kodun çalışabilmesi..

Peki çalıştırılma aşamasında kod nasıl çalıştırılır ?

Call Stack Nasıl Çalışır ?

  • main() ile JS kodu çalıştırılıyor ve printSquare(4) fonksiyonu çağrılıyor
CallStack Bellekte Nasıl Çalışıyor — 1
  • main fonksiyonu kendi içerisindeki çalıştırılacak fonksiyonları sırası ile stack taşıyor. main → printSquare → square → multiply fonksiyonlarını stack yüklüyor
  • LIFO (Son giren ilk çıkar mantığı ile son fonksiyona geldi ise artık bunları işleterek stack siler. Özetle kod blokları işletildikçe bu stack dolar ve boşalır..
  • Stack üzerine konulan her bir eleman için yeni bir ExecutionContext oluşturulup bunlar bu stack eklenir.
JS Stack and Execution Context

Şimdi gelin bir ExecutionContext birbirlerine olan erişimlerine bakalım.

Scope

Örnek 1 Kod — Execution Context Run — (Variable Env)

Aşağıdaki örnekte iç içe birbirini çağıran fonksiyonlar var. a → b → c çağırıyor.

Scope Örnek 1 Kodun İşletilmesi

Bu durumda sıra ile gidersek önce

Adım 1: 1 değeri → x atanır. (Global Context x =1 değerindedir)
Adım 2: a fonksiyonu çağrılır bunun için A Context oluşturulup Stack üzerine atılır. A Context x=2 değerinde
Adım 3: a->b fonksiyonu çağırır. B Context oluşturulup Stack üzerine atılır B Context x=3 değerinde
Adım 4: b->c fonksiyonu çağırır. C Context oluşturulup Stack üzerine atılır C Context x=4 değerinde
Adım 5: C fonksiyonu işletilir. Kendi Scope değer 4 olduğu için ekrana basar ve işi bittiği için Stack atılır.Adım 6: B fonksiyonu işletilir. Kendi Scope değer 3 olduğu için ekrana basar ve işi bittiği için Stack atılır.Adım 7: A fonksiyonu işletilir. Kendi Scope değer 2 olduğu için ekrana basar ve işi bittiği için Stack atılır.Adım 8: Global Context değer ekrana yazılır yani 1 ve kod exit exit eder.

Örnek 2 Kod — Execution Context Run — (Variable Env)

Burda a → b → c kodunu çağıralım. Ama bu sefer c fonksiyonu içerisinde x değişkeni bulunmasın. Bu durumda nasıl davranır. Neden aşağıdaki resimde olduğu gibi B context değeri değilde Global Context değeri okuyor ?

Scope Örnek 2 Kodun İşletilmesi

Yukarıdaki adımlar benzer şekilde işletilir. Fakat C contextinde değer yoksa kendi bir üst Scope yani Lexical(Sözcük) üzerindeki ExecutionContext değişkenlerine erişmeye başlar. c fonksiyonu nerde tanımlanmış Global alanda, ozaman Global Context alanına erişir.

Scope Nedir?

Özetle scope sözcük dizimi {} kısmında sizin değişkenlere ve fonksiyonlara erişebilme yetkinizdir.

  • Örneğin Globalde çalışanlar b fonksiyonuna direk erişemez ama a fonksiyonuna direk erişebilir
  • a fonksiyonu c fonksiyonuna direk erişemez b fonksiyonu erişebilir
  • c fonksiyon ExecutionContext içerisinde bir x değeri bulamadığı durumda bir üst scope b executionContext sorar. Yoksa bir üst Scope yani Lexical Scope sorar.
Scope Nedir ?

Scope Chain Nedir ?

  • Bu şekilde c kendi executationContext bulamaz ise bir üst Scope Context var mı
  • c , b context ‘inde bulamadığı için bir üstüne sorar
  • c, a context’inde de bulamadı bir üstüne sorar
  • c global scope’ctaki x değerini =1 buldu onu kullanır
    Bu üst Scope zinciri gezmeye Scope Chain Diyoruz.
Scope Chain

Bu aşamada var , let , const tekrar hatırlamakta fayda var bunun için Neden ? var → let, const yazımı okuyunuz .

Closure

JS’de bir çok konunun temelini oluşturan diğer bir konuda Closure kavramıdır. Bu da Scope olayının farkı bir şekilde çalışması ile hem Callback temelini, hemde High Order Functions temelini oluşturur. Nasıl mı ?

Yine aşağıdaki örnek üzerinden anlatalım;

saySomething fonksiyonu bir fonksiyon ref dönüyor ve işini bitirip ExecutionContext siliniyor. Peki nasıl oluyorda return eden who parametresi alan fonksiyonu çağırdığımızda bu fonksiyon something değişkenindeki değeri hatırlayabiliyor ? Çünkü bunun işi bittiği için stack atılmış ve değişkeninde silinmiş olması gerekiyordu.

Clousure Örneği

İşte JS yine burada biraz farklı şekilde düşünüyor. Stack ilgili ExecutionContext siline bile onu çevreleyen {} blockta bu değişkenlere ihtiyaç duyan fonksiyon bloğu var ise Closure yeneği sayesinde silinen ExecutionContext değerleri tutulmaya devam ediyor. Bu sayede aşağıdaki örnekte “Merhaba” değeri daha hala kullanılabiliyor oluyor.

Closure Stack için

Not: Biraz uzun bir yazı oldu ama JS temellerini anlamak için bu konuları anlamanız gerekiyor. Önerim kendinize bir kod deneme ortamı bulup bu konuları teker teker denemeniz olur 😄

Github Kod Örnekleri

Referanslar

  • Javascript: Understanding the Weird Parts

Okumaya Devam Et 😃

Bu yazının devamı veya yazı grubundaki diğer yazılara erişmek için bu linke tıklayabilirsiniz.

--

--