جاوااسکریپت کامپایل میشود. بله شما درست متوجه شدید. با این وجود برخلاف دیگر زبانهای کامپایلی که یک مرحله را برای بهینهسازی زودهنگام کدها آماده میکنند، کامپایلر جاوااسکریپت مجبور است که آن را درست در آخرین نقطه کامپایل نماید. تکنولوژی که برای کامپایل جاوااسکریپت استفاده میشود JIT یا Just-In-Time نام دارد. این شیوه «کامپایل روی هوا (اصطلاح)» در موتور جاوااسکریپت مدرن برای سرعتبخشی به مرورگر در جهت پیادهسازی آنها فراهم شده است.
موضوعات گفته شده تا به اینجا مطمئنا برای توسعهدهندههایی که جاوااسکریپت را به عنوان یک زبان مفسری شنیدهاند گیج کننده است. برای این موضوع توضیحی دیگر را باید اضافه کنم: موتور جاوااسکریپت تا چند مدت پیش با مفسر کار میکرد. اما حال با وجود موتوری مانند Google V8 توسعهدهندگان میتوانند از هر دو حالت برخوردار باشند.
در این مطلب قصد داریم به شما شیوه پردازش کدهای جاوااسکریپت را با استفاده از یکی از کامپایلرهای موسوم به JIT را نشان دهیم. اما نمیخواهیم وارد بحث مربوط به مکانیزم جدید بهینهسازی موتور جاوااسکریپت شویم. این مکانیزم شامل تکنیکهایی مانند inlining (حذف کردن فضاهای سفید)، کمک گرفتن از کلاسهای پنهان و حذف کردن افزونگی میشود. بجای این موارد ما قصد داریم به شما ایده اینکه چگونه موتور مدرن جاوااسکریپت کار میکند را بدهیم.
زبان و کد
برای اینکه بهتر شیوه خواندن کدها توسط کامپایلر را متوجه شویم، به نظر بسیار کمک کننده میآید که ما نگاهی به زبان استفاده شده در جهت خواندن این مقاله بکنیم (فارسی، انگلیسی و...). همه ما با SyntaxErrorهایی که با رنگ قرمز در محیط توسعه کنسول ظاهر شدهاند مواجه شدهایم، اما با کمی سر خاراندن و گشتن به دنبال سیمیکالنهای از یاد رفته، هیچوقت تفکر نوام چامسکی را فراموش نکردهایم. چامسکی دانشمند و فیلسوفی است که سینتکس و شیوه ترکیبات کلمه را به صورت زیر بیان میکند:
«مطالعه قواعد و پردازشهایی که با استفاده از آنها جملات در یک زبان ساخته میشوند.»
ما نیز در کدنویسی زمان تابعی مانند simplify() را براساس تعریف نوام چامسکی ایجاد میکنیم:
simplify(quote, "grossly");
البته تعریفی که چامسکی داشت مربوط به زبانهایی مانند انگلیسی، آلمانی و... بود نه مواردی مانند جاوااسکریپت و روبی! با این حال زبانهای سطح بالایی که در برنامهنویسی از آنها استفاده میکنیم نزدیکی بسیار زیادی به زبانی که با آن صحبت میکنیم دارد. بنابراین در نهایت موتور جاوااسکریپت نیز توسط مهندسان خبرهای برای خواندن کدها آموزش داده شده، درست مانند زمانی که ما در مدرسه شیوه خواندن جملات را یاد گرفتیم.
در اینجا سه تعریف مربوط به دنیای زبان وجود دارد که در جهت یادگیری شیوه کارکرد کامپایلرها دانستنشان کاربردی است: واحدهای لغوی، سینتکس و مفهوم. به عبارتی دیگر مطالعه در رابطه با مفهوم کلمات و روابطشان، مطالعه در رابطه با آرایش و ترتیب کلمات و مطالعه مفاهیم مربوط به جملات.
به این جمله دقت کنید: ما گوشت خوردیم.
واحد لغوی
به این موضوع که هر کلمه چگونه در این جمله میتوانند یک لغت معنادار باشند دقت کنید: ما/گوشت/خوردیم.
سینتکس
ساختار یک جمله ساده به صورت فاعل/مفعول/فعل است. تمام جملات ساده که درست نوشته میشوند باید بدین صورت باشند، چرا در کامپایلر نیز بدین صورت است؟ به این خاطر که کامپایلر با درک این حالت باید از یک الگوی سختگیرانه پیروی کند تا بتواند اشتباهات سینتکس را متوجه شود. بنابراین جمله گوشت که ما خوردیم قابل فهم به صورت درست نیست و در علم زبان اشتباه است.
مفهوم
از لحاظ معنایی این جمله مفهوم درستی دارد. انسانها گوشت میخورند پس این جمله مشکل مفهومی ندارد.
حال بیایید این موضوعات را در دنیای جاوااسکریپت بیان کنیم.
let sentence = "We ate beef";
واحد لغوی
این عبارت میتواند به قسمتهای کوچکتری تبدیل شود.
let/sentence/=/ "We ate beef"/;
سینتکس
عبارت ما درست مانند یک جمله باید یک ترتیب و سینتکسی داشته باشد. جاوااسکریپت درکنار زبانهای برنامهنویسی دیگری از الگوی زیر پیروی میکند:
(Type) /Variable/ Assignment/Value
در کنار این گزینه use strict نیز در جاوااسکریپت وجود دارد که سینتکس را برای اجرای بسیار درست و دقیقتر توسط کامپایلر مجبور میکند. کار کردن با این قالب دستوری واقعا سنگین و سخت است.
مفهوم
از لحاظ مفهومی کدهای ما مفهوم و معنای منحصر به فردی دارند که در نهایت توسط کامپایلر درک میشود. در جهت دسترسی پیدا کردن به مفهوم کدها کامپایلر نیاز دارد که کدها را مطالعه کند.
LHS/RHS
ما زبان انگلیسی را از چپ به راست مطالعه میکنیم، برای زبان فارسی نیز به صورت عکس، اما کامپایلر جاوااسکریپت میتواند از هر دو طرف این کار را انجام دهد. چگونه؟ با استفاده از LHS یا Left Hand Side و RHS یا Right Hand Side. بیایید بیشتر در این مورد توضیح دهیم:
بخش مربوط به LHS در کامپایلر روی قسمت سمت چپ یک عملیات assignment تمرکز دارد. منظور این است که کامپایلر در این حالت مسئول قسمت هدفگذاری شده در یک عبارت است. ما باید هدفمان را مفهومسازی کنیم نه به آن موقعیت بدهیم. به این دلیل که LHS رفتارهای متفاوتی در قبال این حالت دارد. همچنین منظور از assignment همواره دقیقا عملگر assign نیست.
برای درک بهتر این قضیه کدهای زیر را مشاهده کنید:
function square(a){
return a*a;
}
square(5);
تابع باعث میشود که LHS به مقدار a توجه کند. چرا؟ به این دلیل که مقدار 5 در a قرار گرفته است. اما این حالت مانند همیشه با استفاده از موقعیت assignment انجام نشده!
در حالت عکس RHS روی مقدار تمرکز دارد. با این تعریف اگر به مثال قبلی بازگردیم RHS مقدار خود را در عبارت a*a پیدا میکند.
نکته مهم ماجرا این است که این عملیاتها در فاز آخر مربوط به کامپایل کردن اتفاق میافتد. -قسمت تولید کد- در رابطه با این موضوع در ادامه بحث میکنیم. اما قبل از آن بیایید اطلاعاتی راجع به کامپایلر پیدا کنیم.
کامپایلر
به کامپایلر مانند صنعت بستهبندی گوشت همراه با مکانیزمهای مختلفی که کدها را بستهبندی میکند و کامپیوتر به آن به عنوان خوراکی یا برنامه اجرا شدنی نگاه میکند فکر کنید. بگذارید مرحله مرحله این روند را توضیح دهم:
قسمت Tokenizer
ابتدای امر قسمت tokenizer کدها را به واحدهایی به نام توکن تبدیل میکند. این توکنها بعدا توسط tokenizer شناخته میشود. یک خطا لغوی زمانی اتفاق میافتد که tokenizer الفبایی را پیدا میکند که مربوط به زبان استفاده شده نیست. برای مثال، اگر ما از علامت @ بجای عملگر انتساب استفاده کنیم tokenizer میگوید: «خب این لغت توی دایره لغوی جاوااسکریپتی من پیدا نشد، همه چیز اشتباه است! کد رو قرمز کن :|».
Parser
Parser دنبال خطاهای نحوی است. اگر هیچ خطایی وجود نداشته باشد، این قسمت تومنها را در یک ساختمان داده به نام Parse Tree پکیجبندی میکند. در این قسمت از پروسه کامپایل کردن کدهای جاوااسکریپت Parse شده و آنالیز شده از نظر نحوی در نظر گرفته میشود. یک بار دیگر اگر قواعد جاوااسکریپتی دنبال شود یک ساختمان داده جدید به نام Abstract Syntax Tree (AST) ایجاد میشود.
در اینجا یک مرحله میانجی نیز وجود دارد که سورس کد را به کدهای سطح میانی (بایت کد) به صورت جمله به جمله توسط یک مفسر تبدیل میکند. این بایت کدها بعدا توسط یک ماشین مجازی اجرا میشوند.
بعد از آن کدها بهینهسازی میشوند. این کار با حذف کردن فضای سفید، کدهای مرده و کدهای زائد انجام میشود. البته پروسههای دیگری نیز برای بهینهسازی طی میشود.
سازنده کد
بعد از اینکه کد بهینهسازی شد، وظیفه سازنده کد این است که کدهای سطح میانی را گرفته و آنها را به زبان سطح پایین اسمبلی برای درک توسط ماشین ترجمه کند. از این قسمت به بعد سازنده وظیفه دارد که:
- مطمئن شود که کدهای سطح پایین همان دستورالعملهای مربوط به سورس کد را دنبال میکند.
- بایت کدها را به کد قابل فهم ماشین مورد نظر تبدیل کند.
- تصمیم بگیرد که چه زمان دادهها در رجیستر یا حافظه ذخیره شوند و چه زمانی برگشت یابند.
بدین صورت است که سازنده کد با LHS و RHS کار میکند. به صورت ساده LHS مقادیر مورد نیاز را در حافظه ذخیره میکند و RHS آنها را از حافظه میخواند.
اگر یک مقدار در کش و رجیستر ذخیره شود، سازنده برای بهینهسازی باید آنها را از رجیستر فراخوانی کند. دریافت مقادیر از حافظه باید آخرین کاری باشد که انجام میشود.
کار نهایی که سازنده کد انجام میدهد، تصمیم گیری برای حالتی است که در آن دستورالعملات باید اجرا شود.
سخن پایانی
یک راه دیگر برای درک شیوه کاری موتور جاوااسکریپت نگاه کردن به ذهنتان است. همانطور که شما این مطلب را میخوانید ذهن شما در حال دریافت اطلاعات از بیناییتان است. دادهها توسط عصبهای بیناییتان منتقل میشود. مغز شما تصاویر را تفسیر میکند.
جدای از تصاویر موجود و تشخیص رنگ ها مغز شما میتواند فضاهای خالی را براساس تشخیص الگوها درست مانند قابلیت دریافت اطلاعات از حافظه کش درک کند.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید