فصل 2ساختار برنامه
سرخی قلبم از زیر پوست شفاف و نازکم میدرخشد و آنها باید به من 10 سیسی جاوااسکریپت تزریق کنند تا من را به زندگی برگردانند. (بدن من به وجود سم در خون خوب واکنش میدهد. ) این آشغال فورا نفس شما را برمیگرداند!
در این فصل، قصد داریم کارهایی را شروع کنیم که واقعا بتوان نامشان را برنامهنویسی گذاشت. میخواهیم دانش جاوااسکریپت خود را به فراتر از اسامی و بخشهای کوچکی که تا کنون دیدهایم، توسعه دهیم تا به نقطهای برسیم که بتوانیم برنامههای معناداری بنویسیم.
عبارتها و دستورات
در فصل 1، مقادیری را ایجاد و به آنها عملگرهایی اعمال کردیم تا بتوانیم مقدارهای تازهای ایجاد کنیم. تولید مقدارها به این صورت، جوهر اساسی هر برنامهی جاوااسکریپت است، اما این جوهر اساسی باید در یک ساختار بزرگتر قرار گیرد تا کاربردی شود. این کاری است که در این فصل به آن خواهیم پرداخت.
قطعه کدی که باعث تولید یک مقدار میشود را اصطلاحا عبارت (expression) میگویند. هر مقداری که در برنامه به طور مستقیم نوشته میشود (مانند 22
یا "psychoanalysis"
) در واقع یک عبارت است. یک عبارت اگر داخل پرانتز قرار گیرد نیز یک عبارت محسوب میشود. به همین ترتیب، عملگر دودویی اگر به دو عبارت اعمال شود یا عملگر یکانی اگر به یک عبارت اعمال شود، باعث تولید عبارت جدید میشود.
این شیوه کارکرد عبارتها گوشهای از زیبایی شباهت زبان برنامهنویسی به زبان طبیعی را نشان میدهد. درست شبیه زیرجملهها در زبانهای بشری، عبارتها نیز میتوانند تو در تو باشند. یک زیرجمله میتواند زیرجملهی دیگری را در بر داشته باشد و الی آخر. این ویژگی باعث میشود بتوانیم عبارتها را ترکیب کنیم تا محاسبات پیچیدهتری را انجام دهیم.
اگر یک عبارت را معادل بخشی از جمله در نظر بگیریم، یک دستور (statement) جاوااسکریپت را میتوان یک جملهی کامل در زبان بشری دانست. یک برنامه، لیستی از دستورات است.
سادهترین شکل یک دستور شامل یک عبارت و یک نقطهویرگول در انتهای آن است. این یک برنامه است:
1; !false;
البته برنامهی بالا، کاربردی ندارد. یک عبارت میتواند فقط منجر به تولید یک مقدار بشود، مقداری که میتواند بعدا توسط کدهای اطرافش استفاده شود. یک دستور به طور مستقل عمل میکند و زمانی میتوان آن را دستوری صحیح نامید که اثری برجای بگذارد. یک دستور میتواند چیزی را روی صفحه نمایش دهد – که نوعی اثرگذاری محسوب میشود – یا حالت درونی برنامه را به گونهای تغییر دهد که بر دستوراتی که بعد میآیند اثر بگذارد. این تغییرات را اثرات جانبی (side effects) مینامند. در مثال قبل، دستوراتی که آورده شدند فقط مقدارهای 1
و true
را تولید میکردند و بلافاصله آنها را بدون استفاده رها میکردند. این کار به هیچ وجه اثری بر جای نمیگذارد. زمانی که این برنامه اجرا میشود، اتفاق قابل مشاهدهای رخ نمیدهد.
در بعضی موارد، میتوان در جاوااسکریپت از گذاشتن نقطهویرگول در انتهای دستور صرف نظر کرد. در موارد دیگر، باید حتما استفاده شود، در غیر این صورت خط بعدی به عنوان ادامهی دستور قبل محسوب خواهد شد. قوانین مربوط به حذف یا جایگذاری نقطهویرگول در دستورات میتوانند پیچیده و خطاساز باشند. بنابراین در این کتاب، هر دستوری که به نقطهویرگول نیاز داشته باشد، با نقطهویرگول پایان مییابد. توصیهی من این است که شما نیز همین کار را بکنید، حداقل تا زمانی که اطلاعات بیشتری در مورد ریزه کاریهای مربوط به حذف نقطهویرگول بدست بیاورد.
متغیرها (انتسابها)
چگونه یک برنامه، یک وضعیت داخلی را نگهداری میکند؟ چگونه برنامه مواردی را به خاطر میسپارد؟ پیش از این دیدیم که چطور میتوان مقادیر جدید را از مقدارهای قبلی به وجود آورد، این کار تغییری در مقادیر قبلی به وجود نمیآورد و مقادیر جدید هم باید بلافاصله استفاده شوند در غیر این صورت از دسترس خارج میشوند. برای حفظ این مقادیر، جاوااسکریپت راهی را پیشنهاد میدهد که متغیر نامیده میشود:
let caught = 5 * 5;
استفاده از متغیر، نوع دومی از دستور را به ما معرفی میکند. کلیدواژهی let
مشخص میکند که این جمله قرار است تا متغیری را تعریف نماید. بعد از این کلیدواژه، نام متغیر میآید و در صورتی که بخواهیم مقداری را بلافاصله به آن اختصاص دهیم، میتوانیم از عملگر =
استفاده کنیم.
دستور بالا متغیری به نام caught
را ایجاد کرده و از آن برای نگهداری یک عدد که از حاصل ضرب 5 در 5 به وجود آمده است، استفاده میکند.
بعد از اینکه یک متغیر تعریف شد، میتوان نام آن را به عنوان یک عبارت مورد استفاده قرار داد. مقدار این نوع عبارت همان مقداری است که متغیر اکنون نگهداری میکند. به مثال توجه نمایید:
let ten = 10; console.log(ten * ten); // → 100
زمانی که یک متغیر به مقداری اشاره میکند، به این معنا نیست که برای همیشه به آن گره خورده است. عملگر =
را میتوان در هر زمان استفاده کرد و متغیرهای فعلی را از مقادیری که به آنها اشاره میکنند جدا کرده و به آنها مقادیر جدیدی اختصاص داد.
let mood = "light"; console.log(mood); // → light mood = "dark"; console.log(mood); // → dark
میتوانید متغیرها را به جای جعبه به بازوچهها تشبیه کنید. آنها مقدارها را در خود نگه نمیدارند، بلکه به آنها دسترسی دارند – دو متغیر میتوانند به یک مقدار مشابه اشاره کنند. یک برنامه فقط میتواند به مقدارهایی که همچنان امکان رجوع به آنها را دارد، دسترسی داشته باشد. زمانی که لازم باشد چیزی را در برنامه حفظ کنید (به خاطر بسپارید)، بازوچهای را به سمت آن میفرستید تا آن را نگه دارد یا یکی از بازوچههای فعلی را مامور آن چیز میکنید.
اجازه دهید تا مثالی دیگر را بررسی کنیم. برای به یادآوری دلارهایی که لوئیجی هنوز به شما بدهکار است، میتوانید از یک متغیر استفاده کنید. زمانی که او 35 دلار به شما پس میدهد، مقدار متغیر مورد نظر را تغییر میدهید.
let luigisDebt = 140; luigisDebt = luigisDebt - 35; console.log(luigisDebt); // → 105
زمانی که متغیری را تعریف میکنید اما به آن مقدار اولیه اختصاص نمیدهید، بازوچهای که مثال زدیم هنوز چیزی ندارد تا به آن اشاره کند، بنابراین به جای خاصی اشاره نخواهد کرد. اگر در جایی از برنامه، درخواستی برای یک متغیر خالی ارسال کنید، مقداری که به شما باز خواهد گشت مقدار undefined
خواهد بود.
میتوان از یک let
برای تعریف چندین متغیر استفاده نمود. این تعریفها باید به وسیله ویرگول جدا شوند.
let one = 1, two = 2; console.log(one + two); // → 3
کلمههای var
و const
را نیز میتوان به طور مشابهی برای ایجاد متغیرها (انتسابها) استفاده کرد. درست مانند استفاده از let
.
var name = "Ayda"; const greeting = "Hello "; console.log(greeting + name); // → Hello Ayda
اولین کلمه کلیدی، var
(کوتاه شدهی “variable”)، روشی بود که انتسابها را در نسخهی قبل از 2015 جاوااسکریپت با آن تعریف میکردند. در فصل بعدی به تفاوت آن با let
خواهیم پرداخت. فعلا، به خاطر داشته باشید که هر دوی آنها در بیشتر اوقات، کار مشابهی را انجام میدهند، اما در این کتاب ما به ندرت از var
استفاده خواهیم کرد به خاطر اینکه خصوصیات گیجکنندهای دارد.
کلیدواژهی const
به معنای انتساب ثابت است. این کلیدواژه یک ثابت تعریف مینماید که این ثابت تا زمانی که وجود دارد، به مقدار یکسانی اشاره خواهد کرد. این نوع تعریف، برای نامگذاری یک مقدار در طول برنامه برای مراجعات بعدی مفید است.
نامگذاری انتسابها (متغیرها)
میتوان از هر کلمهای برای نامگذاری انتسابها استفاده کرد. میتوان از ارقام برای بخشی از نام متغیر استفاده نمود – مثلا catch22
یک نام معتبر است – اما نمیتوان آن را با اعداد شروع کرد. نام یک متغیر میتواند شامل علامت دلار ($
) یا زیرخط (_
) باشد اما دیگر علامتهای نقطهگذاری یا کاراکترهای ویژه را نمیتوان استفاده نمود.
کلماتی که معنای خاصی در جاوااسکریپت دارند مانند let
را کلیدواژه مینامند و نمیتوان از آنها برای نامگذاری متغیرها استفاده کرد. همچنین یک تعداد مشخصی از کلمات وجود دارند که برای نسخههای آینده جاوااسکریپت رزرو شدهاند که نمیتوان از آنها نیز برای نامگذاری متغیرها استفاده نمود. لیست همهی این کلمات کلیدی و رزرو شده، نسبتا لیست بلندی میشود.
break case catch class const continue debugger default delete do else enum export extends false finally for function if implements import interface in instanceof let new package private protected public return static super switch this throw true try typeof var void while with yield
نگران حفظ کردن کلمات بالا نباشید. اگر هنگام تعریف یک متغیر با خطایی خلاف انتظار روبرو شدید، مطمئن شوید که از کلمات لیست بالا استفاده نکرده باشید.
محیط اجرایی
به مجموعهی انتسابها (متغیرها) و مقادیر آنها که در یک زمان مشخص فعال هستند محیط اجرایی (Environment) گفته میشود. زمانی که برنامهای اجرا میشود، این محیط خالی نیست و حاوی انتسابهایی است که بخشی از استاندارد زبان میباشند. بیشتر اوقات، محیط اجرایی، مواردی را هم فراهم مینماید تا بتوان به وسیلهی آنها با سیستم پیرامون تعامل برقرار کرد. به عنوان مثال، در یک مرورگر وب، توابعی در دسترس هستند که میتوان از طریق آنها با وب سایتی که بارگیری شده است تعامل داشت و یا ورودیهای موس و صفحهکلید را خواند.
توابع
خیلی از مقدارهایی که در محیط اجرایی پیشفرض، وجود دارند از نوع function میباشند. یک تابع در واقع یک قطعه یا بخشی از برنامه است که در قالب یک مقدار قرار گرفته است. این نوع مقدارها را میتوان بکار گرفت تا برنامهی موجود در آن اجرا شود. به عنوان مثال، در محیط مرورگر، انتساب prompt
تابعی را نگهداری میکند که در صورت اجرا، یک پنجرهی تعاملی را نشان میدهد که میتواند اطلاعاتی را از کاربر دریافت کند و به شکل زیر استفاده میشود:
prompt("Enter passcode");
به اجرای یک تابع، فراخوانی ، صدازدن ، یا بکارگیری آن گفته میشود. برای صدازدن یک تابع میتوان یک جفت پرانتز را در جلوی عبارتی که یک مقدار از نوع تابع را تولید میکند قرار دارد. شما معمولا به طور مستقیم با متغیری که حاوی تابع است کار میکنید. اگر بین پرانتز مقادیری قرار گیرد، این مقادیر به برنامهای که در داخل تابع قرار گرفته است فرستاده میشود. در مثال، تابع prompt
از رشتهای که به آن میدهیم استفاده کرده و آن را به عنوان متن پنجرهی تعاملی نشان میدهد. مقدارهایی که به توابع فرستاده میشوند را آرگومان میگوییم. توابع مختلف ممکن است به تعداد و انواع مختلفی از آرگومانها نیاز داشته باشند.
تابع prompt
زیاد در برنامهنویسی وب مدرن استفاده نمیشود، و علت آن بیشتر به خاطر نداشتن کنترل روی ظاهر پنجرهی تعاملی است. اما میتوان از آن در بعضی موارد مثل برنامههای تفننی یا آزمایش کردنها استفاده کرد.
تابع console.log
در مثالهای قبلی، من از console.log
برای نمایش مقدارها استفاده کردم. اکثر سیستمهای جاوااسکریپت ( شامل همه مرورگرهای مدرن و node.js) تابعی به نام console.log
را فراهم میکنند که آرگومانهای ورودیش را در جایی به صورت متنی نشان میدهند. در مرورگرها، این خروجی در کنسول جاوااسکریپت قرار میگیرد. کنسول بخشی از مرورگر است که به صورت پیش فرض مخفی است، اما در بیشتر مرورگرها با فشار دادن کلید F12 یا در مک با command-option-I در دسترس خواهد بود. اگر این کلیدهای میانبر کار نکرد، از طریق منوی مرورگر به دنبال “developer tools” و شبیه آن بگردید.
زمانی که مثالهای اینجا(یا کدهای خودتان) را در صفحات این کتاب اجرا میکنید، خروجی تابع console.log
، به جای نمایش در کنسول جاوااسکریپت مرورگر، بعد از خود مثال نشان داده خواهد شد.
let x = 30; console.log("the value of x is", x); // → the value of x is 30
اگرچه در نام متغیرها نمیتوان از کاراکتر نقطه استفاده کرد، اما console.log
نقطه دارد. علت آن این است که console.log
یک انتساب (متغیر) ساده نیست. در واقع عبارتی است که خاصیت log
را از مقداری که در متغیر console
نگهداری شده است برمیگرداند. در فصل 4، با مفهوم آن به طور دقیق آشنا خواهیم شد.
بازگرداندن مقادیر
نمایش یک پنجرهی تعاملی یا نوشتن متن در صفحهی نمایش یک اثر جانبی (side effect) تلقی میشوند. خیلی از توابع به خاطر اثرات جانبیای که تولید میکنند کاربرد دارند. توابع همچنین میتوانند مقدار تولید کنند و در این صورت نیازی به اثر جانبی ندارند تا در برنامه کاربرد داشته باشند. به عنوان مثال، تابع Math.max
به تعداد دلخواه، آرگومان عددی میگیرد و بزرگترین آنها را برمیگرداند.
console.log(Math.max(2, 4)); // → 4
زمانی که یک تابع مقداری را تولید میکند، گفته میشود که آن مقدار را برگردانده است. در جاوااسکریپت هرچیزی که مقداری را تولید میکند یک عبارت است که به این معنا است که میتوان فراخوانی توابع را در عبارتهای بزرگتر قرار داد. در اینجا فراخوانی Math.min
که عکس تابع Math.max
میباشد، به عنوان بخشی از یک عبارت جمع حسابی استفاده شده است:
console.log(Math.min(2, 4) + 100); // → 102
در در فصل بعد توضیح میدهم که چگونه توابع خود را بنویسید.
جریان کنترل
زمانی که برنامهی شما بیش از یک دستور دارد، دستورها مثل خواندن یک داستان، از بالا به پایین اجرا میشوند. به عنوان مثال، این برنامه دو دستور دارد. اولین دستور از کاربر درخواست میکند که عددی را وارد نماید و دومین که پس از آن اجرا میشود، مربع عدد وارد شده را نشان میدهد.
let theNumber = Number(prompt("Pick a number")); console.log("Your number is the square root of " + theNumber * theNumber);
تابع Number
مقدار ورودیاش را به عدد تبدیل میکند. ما به این تبدیل نیاز داریم چرا که خروجی تابع prompt
از جنس رشته است و ما به دنبال عدد هستیم. توابع مشابهی به نامهای String
و Boolean
وجود دارند که مقادیر را به نوع خودشان تبدیل میکنند.
در اینجا تصویری ابتدایی از چگونگی جریان کنترل خطی مستقیم نمایش داده شده است.
اجرای شرطی
همهی برنامهها از جنس خطی و مستقیم نیستند. مثلا ممکن است بخواهیم یک راه دیگر هم در برنامه در نظر بگیریم که برنامه با توجه به شرایطی که در دست دارد راه مناسب را انتخاب کند. به این کار اجرای شرطی میگویند.
در جاوااسکریپت اجرای شرطی را با کلمه کلیدی if
پیادهسازی میکنند. در یک مورد ساده، ما قصد داریم که قطعه کدی فقط در صورتی اجرا شود که شرط خاصی برقرار باشد. به عنوان نمونه، در برنامهی قبل، ممکن است بخواهیم که مربع ورودی را تنها زمانی نشان دهیم که ورودی از جنس عدد باشد.
let theNumber = Number(prompt("Pick a number")); if (!Number.isNaN(theNumber)) { console.log("Your number is the square root of " + theNumber * theNumber); }
با این تغییر، اگر رشتهی “parrot” را وارد کنید، چیزی به عنوان خروجی نشان داده نخواهد شد.
کلمه کلیدی if
براساس مقدار یک عبارت بولی، دستوری را اجرا یا از آن صرف نظر میکند. این عبارت بولی که مبنای تصمیمگیری است درست بعد از کلمه کلیدی، در داخل پرانتز نوشته میشود و در ادامه عبارتی میآید که قرار است اجرا شود.
تابع Number.isNaN
یک تابع استاندارد جاوااسکریپت است که زمانی مقدار true
را برمی گرداند که آرگومان ورودی آن از نوع NaN
باشد. اگر رشتهای که به تابع Number
داده میشود، بیانگر یک عدد معتبر نباشد، این تابع مقدار NaN
را برمیگرداند. بنابراین، معنای عبارت شرطی اینگونه میشود: "اگر theNumber
از جنس غیر عدد نباشد (عددی باشد) فلان کار را انجام بده. "
در این مثال دستوری که در خط بعد از if
قرار دارد توسط کروشههای مجعد محصور شده است (}
و {
). از کروشهها میتوان برای گروهبندی دستورات متعدد به عنوان یک دستور استفاده کرد که به آن بلاک گفته میشود. در مثال فوق میتوانستید که کروشهها را حذف کنید چون فقط یک دستور داشتید اما برای دوری از ابهام گذاشتن یا نگذاشتن آنها، بیشتر برنامهنویسان جاوااسکریپت از کروشهها در مواردی شبیه به این هم استفاده میکنند. ما هم در این کتاب از همین عرف استفاده میکنیم مگر در بعضی موارد که قصد داریم کلا دستور در یک خط باشد.
if (1 + 1 == 2) console.log("It's true"); // → It's true
همیشه اینطور نیست که برنامه در صورت true بودن شرط اجرا شود، بلکه گاهی عکس این مطلب اتفاق میافتد. این مسیر جانبی در نمودار با پیکان دوم نمایش داده شده است. استفاده از کلیدواژهی else
به همراه if
این امکان را به ما میدهد تا دو مسیر اجرایی متفاوت تعریف کنیم.
let theNumber = Number(prompt("Pick a number")); if (!Number.isNaN(theNumber)) { console.log("Your number is the square root of " + theNumber * theNumber); } else { console.log("Hey. Why didn't you give me a number?"); }
اگر بیش از دو مسیر برای انتخاب داشتیم، میتوان از چندین جفت if
/elseکه
با هم زنجیر شدهاند استفاده کرد. به مثال توجه کنید:
let num = Number(prompt("Pick a number")); if (num < 10) { console.log("Small"); } else if (num < 100) { console.log("Medium"); } else { console.log("Large"); }
برنامه در ابتدا بررسی میکند که آیا num
از 10 مقدارش کمتر است یا خیر. اگر کمتر بود، به سراغ آن شاخه از کد میرود و "Small"
را نشان میدهد و کار تمام است. اگر شرط دوم (< 100
) برقرار بود، به این معنا است که عدد بین 10 و 100 است که در این صورت "Medium"
نمایش داده میشود. اگر هیچ کدام نبود، دومین و آخرین else
انتخاب میشود.
نمودار برنامه بالا چیزی شبیه عکس زیر خواهد بود.
while و do loops
برنامهای را در نظر بگیرید که همهی اعداد زوج بین 0 و 12 را چاپ میکند. یکی از راههای نوشتن این برنامه به شکل زیر است:
console.log(0); console.log(2); console.log(4); console.log(6); console.log(8); console.log(10); console.log(12);
این برنامه کار خواهد کرد اما ایدهی اینکه اصلا برنامهای نوشته میشود این است که که یک وظیفه، کمتر کار ببرد نه بیشتر. اگر ما بخواهیم تمام اعداد زوج کوچکتر از 1000 را چاپ کنیم، روش قبلی دیگر عملی نیست. چیزی که لازم داریم روشی است که بتوان با آن کدهایی را تکرار کرد. این شکل جریان کنترل را loop یا حلقه مینامند.
جریان کنترل حلقهای به ما این امکان را میدهد که به نقطهای در برنامه برگردیم، جایی که قبلا بودهایم و آن را در حالت فعلی برنامه تکرار کنیم. اگر این امکان را با یک متغیر شمارشگر ترکیب کنیم، میتوانیم کاری شبیه زیر را انجام دهیم:
let number = 0; while (number <= 12) { console.log(number); number = number + 2; } // → 0 // → 2 // … etcetera
دستوری که با کلیدواژهی while
شروع شود، یک حلقه ایجاد میکند. ساختار while
بسیار شبیه به if
است. کلمهی while در ابتدا، سپس یک جفت پرانتز که در داخل آن یک عبارت قرار میگیرد و بعد دستور نوشته میشود. حلقهی while،
دستور مورد نظر را به صورت مداوم اجرا میکند البته تا زمانی که عبارت داخل پرانتز true ارزیابی شود. مقدار داخل پرانتز به نوع دادهی بولی تبدیل میشود و بعد ارزیابی میشود.
متغیر number
در اینجا نشان میدهد که چگونه میتوان از یک متغیر برای نگهداری پیشرفت یک برنامه استفاده کرد. هر بار که حلقه تکرار میشود، مقدار number
به اندازهی 2 واحد از قبل بیشتر میشود. در ابتدای هر تکرار این عدد با عدد 12 مقایسه شده تا مشخص شود که آیا به پایان اجرای برنامه رسیدهایم یا خیر.
به عنوان یک مثال کاربردی، اکنون میتوانیم برنامهای بنویسیم که مقدار 210 را محاسبه کند و نمایش دهد. از دو متغیر استفاده خواهیم کرد: یکی برای نگه داشتن نتیجه محاسبه و دیگری برای شمردن تعداد دفعاتی که نتیجه را در 2 ضرب کردهایم. حلقه بررسی میکند که متغیر دوم به 10 رسیده باشد که در غیر این صورت هر دوی متغیرها را به روزرسانی کند.
let result = 1; let counter = 0; while (counter < 10) { result = result * 2; counter = counter + 1; } console.log(result); // → 1024
میتوانستیم متغیر شمارندهی counter را از عدد 1
شروع کنیم که در این صورت شرط به صورت <= 10
بررسی میشود. به دلایلی که در فصل ?](data#array_indexing) روشن خواهد شد، بهتر است که عادت کنیم تا از 0 بشماریم.
حلقهی do
یک ساختار کنترلی شبیه به حلقهی while
است. تفاوت فقط در یک چیز است: یک حلقهی do
حداقل بدنهاش را یک بار اجرا میکند و بررسی شرط توقف را بعد از اولین اجرا انجام میدهد. برای نشان دادن این ویژگی، قسمت بررسی شرط، بعد از بدنهی حلقه نوشته میشود:
let yourName; do { yourName = prompt("Who are you?"); } while (!yourName); console.log(yourName);
این برنامه شما را وادار خواهد کرد که حتما نامی را وارد نمایید. این درخواست آن قدر پرسیده خواهد شد تا بالاخره چیزی غیر از رشتهی خالی را دریافت کند. افزودن عملگر !
باعث میشود که یک مقدار قبل از معکوس شدن به نوع بولی تبدیل شود و میدانیم که تمام رشتهها به جز ""
به true
تبدیل میشوند. این بدین معنا است که حلقه تا زمانی که نامی غیر تهی وارد نکنید اجرا میشود.
ایجاد تورفتگی در کدها
همان طور که در مثالها مشاهده کردهاید، در ابتدای دستوراتی که بخشی از دستورات بزرگتر هستند، فضاهای خالی قرار دادهام. در جاوااسکریپت، این فضاها ضروری نیستند – کامپیوتر برنامه را بدون آنها به خوبی قبول میکند. در واقع، حتی شکستن خطوط در برنامهها اختیاری است. اگر دوست داشته باشید میتوانید برنامهای را در یک خط بلند بنویسید.
نقش تورفتگیها در بلاکها، نمایانسازی ساختار کد است. هنگام کدنویسی گاهی بلاکهای جدید درون بلاکهای دیگر قرار میگیرند و پیدا کردن پایان و شروع بلاکها سخت میشود. با ایجاد تورفتگی مناسب، شکل ظاهری برنامه، خود بیانگر ساختار بلاکهای درون آن خواهد بود. من ترجیح میدهم که از دو فاصله برای هر بلاک باز استفاده کنم اما سلیقهها متفاوت هستند بعضی افراد دوست دارند که از چهار فاصله استفاده کنند و بعضی کاراکتر تب را میپسندند. اما مساله مهم این است که برای هر بلاک جدید از میزان تورفتگی یکسانی استفاده کنیم.
if (false != true) { console.log("That makes sense."); if (1 < 2) { console.log("No surprise there."); } }
اکثر برنامههای ویرایشگر کد (مثل ویرایشگر کد این کتاب)، تو رفتگی ها را به صورت خودکار و با اندازه مناسب ایجاد میکنند.
حلقههای for
خیلی از حلقهها از الگویی که در مثالهای while
دیدیم پیروی میکنند. در ابتدا یک متغیر "شمارنده" ایجاد میشود تا پیشرفت حلقه را نگهداری کند. سپس حلقهی while
خواهد آمد که عبارت شرط آن معمولا مقدار شمارنده را بررسی میکند که به مرز خاصی رسیده باشد. در پایان بدنهی حلقه، شمارنده به روزرسانی میشود تا پیشرفت برنامه پیگیری شود.
چون این الگو بسیار رایج است، جاوااسکریپت و زبانهای مشابه آن روش کمی کوتاهتر و جامعتری را ارائه میدهند، حلقه for
.
for (let number = 0; number <= 12; number = number + 2) { console.log(number); } // → 0 // → 2 // … etcetera
برنامهی بالا دقیقا معادل برنامهی چاپ اعداد زوج پیشین است. تنها تفاوت این است که تمامی دستوراتی که به "وضعیت" برنامه ربط دارند، اکنون در یک گروه بعد از for
قرار گرفتهاند.
قسمت داخل پرانتز بعد از کلیدواژهی for
باید حتما دارای دو نقطهویرگول باشد. قسمت قبل از نقطهویرگول اول، حلقه را مقداردهی اولیه میکند، که این کار معمولا با تعریف یک متغیر انجام میشود. قسمت دوم، عبارتی است که بررسی میکند تا کی حلقه باید ادامه پیدا کند. بخش نهایی وضعیت حلقه را بعد از هربار تکرار به روزرسانی میکند. در بیشتر موارد، این روش از ساختار while
کوتاهتر و سرراستتر است.
در اینجا کدی را مشاهده میکنید که 210 را محاسبه میکند البته با استفاده از for
به جای while
:
let result = 1; for (let counter = 0; counter < 10; counter = counter + 1) { result = result * 2; } console.log(result); // → 1024
شکستن حلقه و خروج از آن
تنها راه به پایان رسیدن یک حلقه فقط تولید مقدار false
توسط بخش شرط حلقه نیست. دستور خاصی به نام break
وجود دارد که در صورت استفاده باعث میشود که بلافاصله برنامه از حلقهی پیرامونش خارج شود.
برنامهی زیر چگونگی استفاده از break
را نمایش میدهد. این برنامه، اولین عددی که بزرگتر و مساوی 20 است و همچنین قابل تقسیم بر 7 است را پیدا میکند.
for (let current = 20; ; current = current + 1) { if (current % 7 == 0) { console.log(current); break; } } // → 21
با استفاده از عملگر باقی مانده (%
) میتوان به راحتی بخشپذیری یک عدد بر عددی دیگر را امتحان کرد. اگر بخشپذیر باشد، باقیماندهی تقسیم برابر صفر خواهد بود.
ساختار for
در این مثال، بخشی که پایان حلقه را بررسی میکند را ندارد. این بدین معنا است که حلقه هرگز متوقف نخواهد شد مگر اینکه دستور break
در درون آن اجرا شود.
اگر آن دستور break
را حذف کنید یا به طور اتفاقی شرطی را بنویسید که همیشه مقدار true
را تولید نماید، برنامه شما در یک حلقهی بینهایت گیر خواهد افتاد. برنامهای که در حلقه بینهایت بیفتد، اجرای آن پایان نمییابد، که معمولا چیز بدی است.
اگر در مثالهای موجود در صفحات آنلاین کتاب یک حلقهی بینهایت ایجاد کنید، پس از چند ثانیه، معمولا از شما پرسیده میشود که قصد ادامه اجرای اسکریپت را دارید یا خیر. اگر برنامه متوقف نشود، مجبور خواهید شد تا برگهای که در آن کار میکنید را ببندید، یا در بعضی مرورگرها، کل مرورگر را ببندید.
کلیدواژهی continue
همانند break
روی پیشروی حلقه تاثیر میگذارد. زمانی که continue
در داخل بدنهی حلقه اجرا شود، کنترل برنامه از بدنه خارج میشود و به تکرار بعدی منتقل شده و ادامه مییابد.
روش کوتاه بهروزرسانی متغیرها
بخصوص در حلقهها، معمولا لازم است تا یک متغیر را بر اساس مقدار قبلیاش بهروز کنیم.
counter = counter + 1;
جاوااسکریپت راه میانبری برای این کار دارد:
counter += 1;
میانبرهای مشابهی برای دیگر عملگرها وجود دارد مثل result *= 2
که مقدار متغیر result
را دوبرابر میکند یا counter -= 1
که مقدار count را کاهش میدهد.
با این روش میتوانیم مثال شمارش اعداد را کمی کوتاهتر بنویسیم.
for (let number = 0; number <= 12; number += 2) { console.log(number); }
برای counter += 1
و counter -= 1
معادلهای کوتاهتری هم وجود دارد: counter++
و counter--
.
تصمیم گیری بر اساس یک مقدار به کمک switch
کدهایی شبیه کد زیر بسیار رایج هستند:
if (x == "value1") action1(); else if (x == "value2") action2(); else if (x == "value3") action3(); else defaultAction();
ساختاری به نام switch
وجود دارد که در مواردی شبیه بالا خواناتر و سرراستتر است. متاسفانه، سبکی که جاوااسکریپت برای این ساختار استفاده میکند ( که از زبانهای شبیه C/Java گرفته شده است) کمی بدقواره است – زنجیرهای از دستورات if
ممکن است زیباتر به نظر بیاید. به مثال توجه فرمایید:
switch (prompt("What is the weather like?")) { case "rainy": console.log("Remember to bring an umbrella."); break; case "sunny": console.log("Dress lightly."); case "cloudy": console.log("Go outside."); break; default: console.log("Unknown weather type!"); break; }
میتوانید هر تعداد دلخواه برچسب case
درون یک بلاک switch
قرار دهید. برنامه با توجه به مقداری که به switch
داده میشود برچسب متناظر را انتخاب کرده و به آن قسمت منتقل میشود یا اگر موردی پیدا نشود قسمت default
را اجرا میکند. پس از انتخاب برچسب، دستورات آن اجرا میشوند حتی دستوراتی که زیر برچسب دیگری قرار دارند تا زمانیکه برنامه به دستور break
برسد. در بعضی موارد، مانند case مربوط به "sunny"
در مثال بالا، این امکان را میتوان برای به اشتراکگذاری کدهایی بین case ها استفاده کرد (برنامه، رفتن به بیرون شهر را برای هر دو هوای ابری و آفتابی پیشنهاد میدهد). اما حواستان باشد: خیلی ساده امکان دارد break
فراموش شود که باعث میشود کدهایی اجرا شوند که مورد نظر شما نبودهاند.
استفاده از حروف بزرگ
در نام متغیرها نمیتوان از فاصله (فضای خالی) استفاده کرد اما اغلب، استفاده از چند واژه برای شرح محتوای متغیر، مفید است. اینکه از چه شیوهای برای نوشتن نام متغیرهای چند کلمهای استفاده میکنید بستگی به سلیقه شما دارد:
fuzzylittleturtle fuzzy_little_turtle FuzzyLittleTurtle fuzzyLittleTurtle
خواندن سبک اول میتواند سخت باشد. شخصا، روش استفاده از زیرخط (underscore) را میپسندم اگرچه تایپ آن کمی مشکلتر است. توابع استاندارد جاوااسکریپت و بیشتر برنامهنویسان جاوااسکریپت از روش آخر استفاده میکنند – به جز کلمهی اول دیگر کلمات را با حروف بزرگ شروع میکنند. میتوان به راحتی به یکی از این روشها عادت کرد. کدنویسی با سبکهای نامگذاری متفاوت، مشکلاتی در خوانایی کد ایجاد میکند، بنابراین ما فقط از یک سبک و آن هم سبک آخر استفاده خواهیم کرد.
در موارد کمی، مثل تابع Number
حرف اول متغیر به صورت بزرگ نوشته شده است. این کار برای نشانهگذاری این تابع به عنوان یک تابع سازنده (constructor) است. اینکه یک تابع سازنده چیست در فصل ?](object#constructors) روشن خواهد شد. فعلا نکته مهم این است که این عدم یکپارچگی در این گونه موارد شما را ناراحت نکند.
توضیحات
معمولا کدهای اصلی برنامه نمیتوانند تمام اطلاعاتی را که میخواهید افراد دیگر با دیدن کدهای شما متوجه آن بشوند را منتقل کنند. یا این کار را به شکلی رمزآلود و گنگ انجام میدهند که دیگران ممکن است متوجه آن نشوند. گاهی هم ممکن است بخواهید تا نظرات مرتبطی را به عنوان بخشی از برنامه به کدهایتان اضافه کنید. اینجاست که توضیحات به کار میآیند.
یک توضیح، متنی است که بخشی از برنامه محسوب میشود اما کامپیوتر آن را نادیده میگیرد. جاوااسکریپت دو راه برای نوشتن توضیحات دارد. برای نوشتن توضیحات تک-خطی، میتوانید از دو کاراکتر اسلش (//
) در ابتدای توضیح استفاده کنید.
let accountBalance = calculateBalance(account); // It's a green hollow where a river sings accountBalance.adjust(); // Madly catching white tatters in the grass. let report = new Report(); // Where the sun on the proud mountain rings: addToReport(accountBalance, report); // It's a little valley, foaming like light in a glass.
توضیحی که با //
مشخص شده باشد فقط تا پایان همان خط در نظر گرفته میشود. متنی که بین /*
و */
محصور شده باشد به طور کامل و بدون توجه به اینکه حاوی چند خط باشد، به عنوان توضیح در نظر گرفته میشود. این ویژگی برای افزودن بلاکهای اطلاعات دربارهی یک فایل یا بخشی از برنامه استفاده میشود.
/* I first found this number scrawled on the back of an old notebook. Since then, it has often dropped by, showing up in phone numbers and the serial numbers of products that I've bought. It obviously likes me, so I've decided to keep it. */ const myNumber = 11213;
خلاصه
اکنون شما میدانید که یک برنامه از مجموعهای از دستورات ساخته میشود، که خود آنها نیز گاهی حاوی دستورات بیشتری میباشند. دستورات حاوی عبارتها هستند که خود این عبارتها نیز میتوانند از عبارتهای کوتاهتری تشکیل شده باشند.
قرار دادن دستورات یکی پس از دیگری، برنامهای را میسازد که از بالا به پایین به ترتیب اجرا میشود. میتوان این ترتیب جریان کنترل را با استفاده از دستورات شرطی (if،
else
و switch
) و حلقهها (while،
do،
و for
) به هم زد.
متغیرها را میتوان برای دستهبندی بخشی از دادهها تحت یک نام استفاده کرد. همچنین آنها برای حفظ و پیگیری حالت فعلی برنامهی شما مفید میباشند. محیط اجرایی مجموعهای از انتسابها (متغیرها) است که قبلا تعریف شده است. سیستمهای جاوااسکریپت همیشه تعدادی متغیر استاندارد مفید را در محیط شما تعبیه میکنند.
توابع مقدارهای ویژهای هستند که بخشی از برنامه را در درون خود نگه داری میکنند. میتوانید آنها را با نوشتن functionName(argument1, argument2)
فراخوانی کنید. این گونه فراخوانی کردن یک تابع، یک نوع عبارت محسوب میشود که میتواند مقداری را هم تولید کند.
تمرینها
اگر مطمئن نیستید که چگونه راه حلهایی را که برای تمرینها ارائه میدهید آزمایش و اجرا کنید، به مقدمه رجوع کنید.
هر تمرین با یک شرح مساله شروع میشود. آن را بخوانید و سعی کنید آن را حل کنید. اگر در حل مساله به مشکل برخورد کردید، به قسمت "راهنمای تمرینها" که در انتهای هر تمرین قرار گرفته است است رجوع کنید. راه حل کامل برای تمرینها در داخل این کتاب گنجانده نشده است، اما میتوانید آنها را در https://eloquentjavascript.net/code بیابید. اگر واقعا میخواهید که از تمرینها چیزی بیاموزید، فقط زمانی به سراغ راه حلها بروید که مساله را حل نمودهاید یا حداقل به اندازه کافی تلاش و زمان صرف حل آن کرده باشید که کمی احساس سردرد داشته باشید.
حلقه و مثلث
حلقهای را پیادهسازی کنید که هفت بار تابع console.log
را فراخوانی کرده و مثلث زیر را تولید کند:
# ## ### #### ##### ###### #######
دانستن این نکته که طول یک رشته را میتوان با گذاشتن .length
در انتهای آن بدست آورد شاید به دردتان بخورد.
let abc = "abc"; console.log(abc.length); // → 3
بیشتر تمرینها به همراه کدی میآیند که میتوانید برای حل تمرین آن را ویرایش کنید. به یاد داشته باشید که برای ویرایش باید روی بلاک کد کلیک کنید.
// Your code here.
میتوانید با برنامهای شروع کنید که اعداد 1 تا 7 را چاپ میکند، که با کمی دستکاری کد مثال چاپ اعداد زوج که در این فصل آمد به دست می آید. جایی که حلقهی for
را معرفی کردیم.
اکنون به شباهت بین اعداد و رشتههایی که از کاراکتر #
تشکیل شدهاند توجه کنید. با افزودن 1، میشود از عدد 1 به عدد 2 رسید (+= 1
). همین کار را میتوان برای ایجاد "##"
از"#"
انجام داد (+= "#"
). پس راه حل شباهت زیادی به مثال چاپ اعداد دارد.
FizzBuzz
برنامهای بنویسید که با استفاده از console.log
با دو استثنا تمامی اعداد بین 1 و 100 را چاپ نماید. برای اعدادی که بر 3 بخش پذیرند به جای عدد عبارت "Fizz"
، و برای اعدادی که بر 5 بخش پذیرند (نه بر 3)، مقدار "Buzz"
را چاپ نماید.
وقتی برنامه شما موارد بالا را به درستی انجام داد، آن را تغییر داده تا مقدار "FizzBuzz"
را برای اعدادی که بر 3 و 5 به طور همزمان بخش پذیرند چاپ کند (برنامه همچنان باید "Fizz"
و "Buzz"
را برای اعدادی که به هر یک بخش پذیرند را چاپ کند).
این مساله در واقع یکی از سوالات مصاحبه شغلی است که ادعا شده که میتواند برای رد کردن افرادی که مناسب برنامهنویسی نیستند استفاده شود. بنابراین اگر شما آن را حل کردید، پس ارزش شما در بازار کار بالاتر رفته است.
// Your code here.
حرکت بین اعداد، به وضوح به حلقهها مربوط میشود و انتخاب یک گزینه برای خروجی، به اجرای شرطی ارتباط پیدا میکند. ترفند استفاده از عملگر باقیمانده (%
) برای چک کردن تقسیمپذیر بودن یک عدد بر دیگری را به خاطر بیاورید (که باقی مانده آن صفر میشد).
در نسخهی اول، سه خروجی برای هر عدد ممکن است، بنابراین باید یک زنجیرهی if
/else if
/else
ایجاد کنید.
نسخهی دوم این برنامه یک راه حل سرراست و یک راه حل زیرکانه دارد. راه حل ساده این است که یک شاخهی شرط دیگر اضافه شود تا شرط داده شده به دقت بررسی شود. برای راه زیرکانه، رشتهای حاوی کلمه یا کلماتی بسازید که در صورت تطبیق عدد، رشته و در غیر این صورت عدد چاپ شود. میتوان از عملگر ||
در اینجا به شکل خوبی استفاده کرد.
صفحهی شطرنج
برنامهای بنویسید که رشتهای را ایجاد میکند که نمایانگر یک صفحهی 8×8 است. از کاراکتر خط جدید برای جداسازی خطوط استفاده کنید. در هر مکان از صفحه میتوان فضای خالی (فاصله) یا کاراکتر "#" استفاده کرد. مجموع کاراکترها باید شبیه صفحهی شطرنج باشند.
رشتهی تولیدی را اگر به تابع console.log
بدهید باید چیزی شبیه شکل زیر را تولید کند:
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
بعد از ساختن قسمت اول و تولید الگوی بالا، اکنون متغیر size = 8
را تعریف کنید و برنامه را طوری تغییر دهید که برای هر مقدار size
به درستی کار کند و صفحهای با آن طول و عرض ایجاد کند.
// Your code here.
با یک رشتهی خالی (""
) میتوانید ساخت رشتهی مورد نظر را شروع کنید و بعد به صورت مداوم کاراکترها را اضافه نمایید. یک کاراکتر خط جدید به صورت "\n"
نوشته میشود.
برای کار با دو بعد، لازم است تا از یک حلقه درون حلقهای دیگر استفاده کنید. پیرامون بدنههای هر دو حلقه، کروشه بگذارید تا شروع و پایان هر یک را گم نکنید. سعی کنید برای بدنهها به خوبی تورفتگی ایجاد کنید. ترتیب قرارگیری حلقهها باید براساس رشتهای که میسازیم باشد (خط به خط، چپ به راست، بالا به پایین). بنابراین حلقهی بیرونی خطوط را پوشش میدهد و حلقهی درونی کاراکترهای هر خط را تولید میکند.
به دو متغیر برای ردگیری پیشرفت برنامه نیاز خواهید داشت. برای دانستن اینکه در یک موقعیت آیا باید فضای خالی قرار دهید یا کاراکتر #
، میتوانید بررسی کنید که جمع دو شمارنده مقداری زوج باشد (% 2
).
پایان دادن یک خط با یک کاراکتر خط جدید ("\n"
) باید بعد از اینکه هر خط ساخته شد اتفاق بیفتند، بنابراین این کار را بعد از حلقهی درونی اما درون حلقهی بیرونی انجام دهید.