V8 Javascript Motoru Nasıl çalışır?
İlk kez 2008 yılında yayınlanan V8, Google tarafından geliştirilen yüksek performanslı bir JavaScript motoru olarak tasarlanmıştır.
“Bu sadece mevcut uygulamanızı daha hızlı çalıştırmak değil, daha önce hiç yapamadığınız şeyleri etkinleştirmekle ilgilidir.”
Daniel Clifford
V8 Javascript Motoru
Açık kaynak kodlu olup C++ ile yazılan V8, hem istemci tarafı (Google Chrome) hem de sunucu tarafı (node.js) JavaScript uygulamaları için kullanılmaktadır.
V8 JavaScript motorunun geliştirilmesi öncesinde, çoğu web tarayıcısı (Netscape ➡️ SpiderMonkey, Microsoft ➡️ JScript, Mozilla ➡️ Rhino) farklı JavaScript motorları kullanıyordu. Her ne kadar bu motorlar, JavaScript kodunu yorumlamak ve çalıştırmak için tasarlansa da performans ve özellikler açısından farklılık göstermekteydi. V8 ile hız konusunda önemli bir performans artışı sağlanmıştır.
V8’in diğer JavaScript motorlarına göre daha iyi performans göstermesine yardımcı olan birkaç faktör vardır:
⭐ Just-in-time (JIT) derleme (Just-in-time (JIT) compilation): V8, kod çalıştırılmasını optimize etmek için bir JIT derleyicisi kullanır. Bu demektir ki, ilgili kod çalışma esnasında analiz edilip anında optimize edilmiş makine kodu oluşturmaktadır. Her ne kadar diğer JavaScript motorları bir JIT derleyicisi kullansa da V8’in JIT derleyicisinin daha hızlı ve verimli olduğu kabul edilmektedir.
⭐ Gizli sınıf optimizasyonu (Hidden class optimization): V8, nesnelerdeki özellik erişimini optimize etmek için “gizli sınıf” adı verilen bir teknik kullanır. Bu teknik, her nesnenin “gizli sınıf”ını oluşturur, bu da nesnenin yapısını ve özelliklerinin türlerini temsil eder. Bir nesne oluşturulduğunda veya değiştirildiğinde, V8 gizli sınıfı değişiklikleri yansıtmak için günceller. Böylece, V8’in nesnenin tüm yapısını aramak zorunda kalmadan hızlı bir şekilde özelliklere erişmesine izin verilmiş olur.
⭐ Çöp toplama (Garbage collection): V8 motoru, bellek yönetimi ve performansını iyileştirmek için bir garbage collector (çöp toplama) kullanır. Çöp toplama, JavaScript kodu tarafından kullanılan belleği düzenli aralıklarla tarar ve artık kullanılmayan belleği serbest bırakır. Bu, bellek sızıntılarını önler ve JavaScript kodunun genel performansını iyileştirir.
⭐ Kod optimizasyonu (Code optimization): V8, JavaScript kodunun performansını iyileştirmek için birçok farklı kod optimizasyon tekniği içerir. Örneğin, küçük işlevleri satır içine yerleştirebilir, yani işlev çağrısını işlev gövdesiyle değiştirir. Bu, işlev çağrısının overhead’ını azaltarak kodun performansını iyileştirir.
V8, JavaScript kodunu yürütmeye ek olarak, TypeScript ve CoffeeScript gibi JavaScript’e “aktarılan” (çevrilen) diğer dilleri yürütmek için de kullanılabilir. Bu, geliştiricilerin bu dillerde uygulamalar oluşturmak için V8’i kullanmasına ve V8’in yüksek performansından ve verimliliğinden yararlanmasına olanak tanır.
Genel olarak, JavaScript kodunu bir web tarayıcısında yürütme işlemi aşağıdaki adımları içerir:
- JavaScript kodu tarayıcıya yüklenir ve bellekte saklanır.
- JavaScript motoru, ilk satırdan başlayarak ve kod boyunca sırayla ilerleyerek kodu yürütmeye başlar.
- Motor, kodda değişkenler, işlevler ve kontrol yapıları gibi çeşitli öğelerle karşılaştığında uygun eylemleri ve işlemleri gerçekleştirir.
- Kod, web sayfasıyla veya tarayıcının kendisiyle herhangi bir etkileşim içeriyorsa (sayfanın içeriğini değiştirmek veya kullanıcı girişine yanıt vermek gibi), motor bu etkileşimleri de yönetir.
- Kod tam olarak yürütüldüğünde, motor tamamlanır ve kodun yürütülmesinin sonuçları web sayfasında görüntülenir.
Javascript Motoru Nasıl Çalışır?
Kabaca V8 JavaScript motoru yukarıdaki resimde görüldüğü gibi çalışmaktadır. Bu adımlar sırasıyla aşağıda açıklanmıştır:
Parser (Ayrıştırıcı) ➡️ Kaynak kodundaki semantik yapıları analiz ederek, bir programın dilbilgisi kurallarına uygunluğunu kontrol eder. Bu işlem, “syntax analysis” veya “parsing” olarak da adlandırılır. Parser, belirli bir dili kullanarak yazılmış bir programın daha yüksek seviyeli yapısını oluşturur. Bu yapının, son olarak, çevrilebilir bir ara form olarak (genellikle ortak ara dil) belirli bir çıktıya dönüştürülebilmesi için işlenmesi gerekir.
AST (SSA) ➡️ “Abstract Syntax Tree” (Soyut Sözdizimi Ağacı) kelimelerinin baş harflerinden oluşmaktadır. AST, parser tarafından oluşturulur ve derleyiciler tarafından kaynak kodunun çevrilebilir hale getirilmesi için kullanılır.
Interpreter (Yorumlayıcı) ➡️ Bir programlama dili kodunun derlenmesine gerek kalmadan doğrudan çalıştırılmasına olanak tanıyan bir yazılımdır. Bu yazılım, kaynak kodu bir satır veya bir ifade düzeyinde yorumlar ve hemen çalıştırır. Ancak ilk hatanın bulunduğu yerde programın çalışması kesilir.
Profiler ➡️ Bir programın performansını ölçmek ve analiz etmek için kullanılan bir tekniktir. Profiling, bir programın hangi bölümlerinin yavaş veya bellek açısından yoğun olduğunu belirlemeye ve geliştirmeyi gerektiren kritik noktaları tanımlamaya yardımcı olur. Bu teknik yazılım geliştirme sürecinde oldukça önemlidir. Çünkü bir programın daha hızlı çalışmasını sağlayarak, kullanıcıların daha verimli bir şekilde çalışmasını sağlayabilir.
Compiler (Derleyici) ➡️ Bir programlama dilinde yazılmış kaynak kodu, o programlama diline ait makine koduna çeviren bir yazılımdır. Bu yazılım, kodun doğru bir şekilde işlevsel hale getirilmesine ve programın optimize edilmesine yardımcı olur. Ayrıca, farklı donanım ve işletim sistemleri için birden fazla compiler mevcut olduğundan aynı kaynak kodu farklı platformlarda çalıştırmak mümkündür.
❗ “Kaynak kod”, insanlar tarafından okunabilen bir kodken, “makine kodu” bilgisayar tarafından doğrudan anlaşılabilen bir kod şeklidir.
Interpreter ve Compiler Arasındaki Fark Nedir?
Yukarıdaki resimde fark edileceği üzere JavaScript, hem interpreter hem de compiler özelliklerine sahiptir. Bu iki yaklaşım sadece JavaScript için değil, Python, Java, C++ gibi aklınıza gelebilecek pek çok programlama dili için de geçerlidir.
JavaScript ilk çıktıkğında interpreter olarak tasarlanmıştır. Ancak yaşanılan hız sorunlarını aşmak adına bazı JavaScript motorları JIT (Just-In-Time) ile compiler özelliklerine de sahip hale gelmiştir. Böylece interpreter, kodu hemen çalıştırmamıza olanak sağlarken compiler ise çalışırken ilgili kodun optimize edilmesine izin verir.
JavaScript motorları, kaynak kodu doğrudan yürütebilecek şekilde optimize edilmiştir. Bunu yaparken, motorlar sıklıkla birkaç adımdan oluşan bir süreç kullanır:
- Lexer (Lexical Analyzer): Kaynak kodun sözdizimini analiz eder ve tokenlere ayırır.
- Parser: Tokenleri kullanarak, programın dilbilgisi yapısını analiz eder ve bir AST (Abstract Syntax Tree) oluşturur.
- Interpreter/Compiler: AST üzerinde çalışarak, programı yürütür veya makine koduna derler.
Bazı JavaScript motorları, kaynak kodun doğrudan yürütülmesinde kullanılan bir interpreter ve JIT compiler’ın yanı sıra, performansı artırmak için bytecode compiler’ları da kullanabilirler.
Yukarıdaki anlatılanların ışığında akıllara “JavaScript interpreter bir dil midir?” sorusu gelebilir. Bu soruya tam anlamıyla evet demek yerine duruma göre evet cevabı verilmesi doğru olacaktır. Zira JIT derleyicileri ile JavaScript kodları çalışma zamanında makine koduna çevirirler.
Özetle, JavaScript hem interpreter hem de compiler özelliklerine sahip bir programlama dilidir. Yani, genel olarak JavaScript motorları, kaynak kodunu interpreter ve JIT compiler arasında birleştirerek optimize ederler.