رفع خطاها در جاوا اسکریپت – به زبان ساده
هیچ برنامهای از خطا (باگ) و اشکال خالی نیست. در واقع فرایند توسعه نرمافزار، یک روند مداوم مبارزه با اشکالها و باگهای برنامه است. خطاهایی که ممکن است در یک برنامه وجود داشته باشند، انواع متفاوتی دارند که در این نوشته تلاش میکنیم آنها را در زبان برنامهنویسی جاوا اسکریپت بررسی کرده و شما را با روشهای عیب یابی خطاها آشنا کنیم.
انواع خطا
به طور کلی وقتی با مشکلی در کد خود مواجه میشویم، دو نوع عمده خطا ممکن است پیش آمده باشد:
خطاهای نحوی
این نوع خطا در واقع خطاهای املایی در کد هستند که باعث میشوند برنامه کلاً کار نکند یا در بخشی از مسیر اجرا دچار توقف شود. در این مورد معمولاً پیامهای خطایی نیز ارائه میشود. رفع این خطاها در صورتی که با ابزارهای مناسب و همچنین معانی پیامهای خطا آشنا باشید، کار آسانی خواهد بود.
خطاهای منطقی
خطاهایی هستند که در آن ساختار کد صحیح است؛ اما کد، کاری که قرار بوده انجام دهد را انجام نمیدهد، یعنی برنامه با موفقیت اجرا میشود؛ اما نتایج نادرستی به دست میدهد. اصلاح این خطاها معمولاً دشوارتر است، چون پیامهای خطایی ارائه نمیشود که ما را به منبع خطا هدایت کند.
البته همه چیز به این سادگیها نیست، تفاوتهای دیگری نیز وجود دارند که با عمیقتر شدن در این زمینه با آنها مواجه میشویم. اما طبقهبندی فوق در این مرحله ابتدایی کفایت میکند. در ادامه این دو دسته از خطاها را بیشتر بررسی میکنیم.
یک مثال خطادار
برای آغاز به کد خطادار زیر از یک بازی که در جاوا اسکریپت کدنویسی شده است، دقت کنید:
1<!DOCTYPE html>
2<html>
3 <head>
4 <meta charset="utf-8">
5
6 <title>Number guessing game</title>
7
8 <style>
9 html {
10 font-family: sans-serif;
11 }
12 body {
13 width: 50%;
14 max-width: 800px;
15 min-width: 480px;
16 margin: 0 auto;
17 }
18 .lastResult {
19 color: white;
20 padding: 3px;
21 }
22 </style>
23 </head>
24
25 <body>
26 <h1>Number guessing game</h1>
27
28 <p>We have selected a random number between 1 and 100. See if you can guess it in 10 turns or less. We'll tell you if your guess was too high or too low.</p>
29
30<div class="form">
31 <label for="guessField">Enter a guess: </label><input type="text" id="guessField" class="guessField">
32 <input type="submit" value="Submit guess" class="guessSubmit">
33</div>
34
35<div class="resultParas">
36 <p class="guesses"></p>
37 <p class="lastResult"></p>
38 <p class="lowOrHi"></p>
39</div>
40
41</body>
42
43<script>
44 var randomNumber = Math.floor(Math.random()) + 1;
45 var guesses = document.querySelector('.guesses');
46 var lastResult = document.querySelector('.lastResult');
47 var lowOrHi = document.querySelector('lowOrHi');
48 var guessSubmit = document.querySelector('.guessSubmit');
49 var guessField = document.querySelector('.guessField');
50 var guessCount = 1;
51 var resetButton;
52 function checkGuess() {
53
54 var userGuess = Number(guessField.value);
55 if(guessCount === 1) {
56 guesses.textContent = 'Previous guesses: ';
57 }
58 guesses.textContent += userGuess + ' ';
59
60 if(userGuess === randomNumber) {
61 lastResult.textContent = 'Congratulations! You got it right!';
62 lastResult.style.backgroundColor = 'green';
63 lowOrHi.textContent = '';
64 setGameOver();
65 } else if(guessCount === 10) {
66 lastResult.textContent = '!!!GAME OVER!!!';
67 setGameOver();
68 } else {
69 lastResult.textContent = 'Wrong!';
70 lastResult.style.backgroundColor = 'red';
71 if(userGuess < randomNumber) {
72 lowOrHi.textContent = 'Last guess was too low!';
73 } else if(userGuess > randomNumber) {
74 lowOrHi.textContent = 'Last guess was too high!';
75 }
76 }
77
78 guessCount++;
79 guessField.value = '';
80 guessField.focus();
81 }
82 guessSubmit.addeventListener('click', checkGuess);
83
84 function setGameOver() {
85 guessField.disabled = true;
86 guessSubmit.disabled = true;
87 resetButton = document.createElement('button');
88 resetButton.textContent = 'Start new game';
89 document.body.appendChild(resetButton);
90 resetButton.addeventListener('click', resetGame);
91 }
92
93 function resetGame() {
94 guessCount = 1;
95
96 var resetParas = document.querySelectorAll('.resultParas p');
97 for(var i = 0; i < resetParas.length; i++) {
98 resetParas[i].textContent = '';
99 }
100 resetButton.parentNode.removeChild(resetButton);
101
102 guessField.disabled = false;
103 guessSubmit.disabled = false;
104 guessField.value = '';
105 guessField.focus();
106
107 lastResult.style.backgroundColor = 'white';
108
109 randomNumber = Math.floor(Math.random()) + 1;
110 }
111</script>
112</html>
در آغاز یک نسخه از کد فوق را در ویرایشگر متنی محبوب خود و همچنین مرورگرتان باز کنید. نسخه آنلاین آن را در این آدرس (+) میتوانید مشاهده کنید. سعی کنید بازی را اجرا کنید و دکمه «Submit guess» را کلیک کنید، شاهد خواهید بود که بازی اجرا نمیشود.
در این زمان اگر به بخش کنسول توسعهدهنده مرورگر مراجعه کنیم، در صورت مشاهده هر گونه خطای نحوی میتوانیم آنها را اصلاح کنیم. روش این کار را در ادامه توضیح میدهیم.
اصلاح خطاهای نحوی
برای مراجعه به بخش توسعهدهنده کنسول مرورگر میتوانید از دکمه F12 استفاده کنید. کنسول توسعهدهنده هر زمان که خطاهای ساختاری در جاوا اسکریپت وجود داشته باشد، پیش از این که کد وارد موتور جاوا اسکریپت مرورگر شود، پیامهای خطا را نمایش میدهد.
ابتدا به برگهای که فایل مربوطه را باز کردهاید بروید و کنسول مرورگر را باز کنید. با پیام خطایی مواجه میشوید که به صورت زیر است.
ردگیری چنین خطاهایی کاملاً آسان است و مرورگر چندین سرنخ مفید برای کمک به شما ارائه میکند. تصویر فوق از مرورگر فایرفاکس گرفته شده است؛ اما مرورگرهای دیگر نیز اطلاعات مشابهی ارائه میکنند. این اطلاعات از راست به چپ شامل موارد زیر است:
- یک علامت ضربدر قرمزرنگ که نشاندهنده وجود یک خطا است.
- یک پیام خطا که نشاندهنده اشکال مورد نظر است: «TypeError: guessSubmit.addeventListener is not a function»
- یک لینک «Learn More» که به صفحه MDN شامل توضیح معنای خطا و جزییات فراوان در مورد آن هدایت میکند.
- نام فایل جاوا اسکریپت که به برگه دیباگر devtools لینک شده است. اگر این لینک را دنبال کنید، دقیقاً آن خطی که خطا در آن هایلایت شده است را مشاهده میکنید.
- شماره خطی که خطا در آن قرار دارد و شماره کاراکتر در آن خطی که خطا نخستین بار دیده شده است. در این مورد خط 86 و کاراکتر شماره 3 است.
اگر به خط 86 ویرایشگر کد خود نگاه کنیم، خط زیر را مییابیم:
guessSubmit.addeventListener('click', checkGuess);
پیام خطا چنین بیان میکند: «guessSubmit.addeventListener is not a function». از این رو احتمالاً چیزی را اشتباه ذکر کردهایم. اگر مطمئن نیستید که املای کلمههای صحیح است یا بخشی از ساختار اشکال دارد، بهتر است به آن ویژگی در وبسایت MDN نگاه کنید. بهترین روش برای انجام این کار جستجوی نام ویژگی به همراه کلمه MDN در موتور جستجو است. در این مورد ما لینک صفحه را ارائه میکنیم تا کار شما آسانتر شود: ()addEventListener
بنابراین اگر به صفحه فوق نگاه کنید متوجه میشوید که ما نام تابع را اشتباه نوشتهایم. به خاطر داشته باشید که جاوا اسکریپت به حروف کوچک و بزرگ حساس است. از این رو هر تفاوت جزئی در املای عبارتها موجب بروز خطا میشود. تغییر دادن عبارت addeventListener به addEventListener این اشکال را رفع میکند و از این رو این کار را هم اینک انجام دهید.
راند دوم خطاهای نحوی
صفحه را ذخیره کرده و مرورگر را رفرش کنید و میبینید که خطا از بین رفته است. اینک اگر یک حدس وارد کنید و دکمه «Submit guess» را بزنید با خطای دیگری مواجه خواهید شد.
این بار خطا به صورت «TypeError: lowOrHi is null» و در خط 78 گزارش شده است. دقت کنید که Null مقدار خاصی است که به معنی «هیچ» یا «بدون مقدار» است. از این رو متغیر lowOrHi اعلان شده و مقداردهی اولیه شده است؛ اما مقدار آن معنیداری نیست یعنی نوع یا مقداری ندارد.
همچنین دقت کنید که این خطا به محض بارگذاری صفحه ظاهر نمیشود، زیرا این خطا درون یک تابع یعنی درون بلوک { ... } ()checkGuess رخ میدهد. از آنجا که کدهای درون تابعها در حیطه جداگانهای از کدهای بیرون تابع اجرا میشوند، این کد اجرا نمیشود و از این رو تا زمانی که تابع ()checkGuess در خط 86 اجرا نشود، خطایی صادر نخواهد شد. نگاهی به خط 78 میاندازیم و خط کد زیر را میبینیم:
lowOrHi.textContent = 'Last guess was too high!';
در این خط تلاش میشود که مشخصه textContent متغیر lowOrHi به صورت یک رشته متنی تعیین شود؛ اما کار نمیکند، زیرا lowOrHi شامل آن چیزی که انتظار میرود نیست. برای این که دلیل این مسئله را دریابیم به جستجوی موارد دیگر حضور lowOrHi در کد میپردازیم. نخستین وهله از این متغیر در خط ۴۶ کد جاوا اسکریپت قرار دارد:
var lowOrHi = document.querySelector('lowOrHi');
در این مقطع ما تلاش میکنیم که یک متغیر شامل ارجاعی به یک عنصر در سند HTML ایجاد کنیم. بررسی میکنیم که آیا پس از اجرای این خط متغیر همچنان null است یا نه. کد زیر را در خط 4۷ اضافه کنید:
console.log(lowOrHi);
دقت کنید که ()console.log یک تابع واقعاً مفید برای دیباگ است که مقدار متغیر را در کنسول نشان میدهد. بنابراین دستور فوق مقدار متغیر lowOrHi را به محض این که به خط 4۶ کد برسیم در کنسول نمایش میدهد.
فایل را ذخیره کرده و صفحه را رفرش کنید. اینک باید نتیجه دستور ()console.log را در کنسول مشاهده کنید.
میبینیم که مقدار lowOrHi در این مرحله برابر با null است و از این رو قطعاً مشکلی در خط 4۶ وجود دارد.
اینک باید فکر کنیم که مشکل از چه میتواند باشد. خط 4۶ از متد ()document.querySelector استفاده میکند که رفرنسی از یک عنصر را با انتخاب کردن آن با سلکتور CSS به دست میآورد. اگر فایل را بیشتر بررسی کنیم، میتوانیم پاراگراف مورد نظر را بیابیم:
<p class="lowOrHi"></p>
بدین ترتیب به یک سلکتور کلاس نیاز داریم که با یک نقطه (.) آغاز شود؛ اما این سلکتور که در خط 4۶ به متد ()querySelector ارسال میشود نقطهای ندارد. این میتواند یک مشکل باشد. در خط 4۶ lowOrHi را بهlowOrHi. تغییر میدهیم.
اکنون اگر فایل را ذخیره و صفحه را رفرش کنیم، میبینیم که در کنسول مرورگر عبارت <p> مورد نظر ما بازگشت مییابد. بدین ترتیب خطای دیگری را اصلاح کردهایم. اکنون میتوانید خط ()console.log را حذف کنید و یا برای ارجاع در موارد آتی آن را حفظ کنید.
راند سوم خطاهای نحوی
اکنون اگر بازی را مجدداً اجرا کنید، شاهد موفقیت بیشتری خواهید بود. بازی به طور کامل بدون هیچ اشکالی تا انتها اجرا میشود و فرقی نمیکند که حدس شما درست یا نادرست باشد. در این مرحله بازی دوباره از کار میافتد و همان خطایی که در ابتدا شاهد بودیم دوباره ایجاد میشود:
TypeError: resetButton.addeventListener is not a function
با این وجود این بار این خطا در خط 94 رخ داده است. اگر به خط 94 نگاه کنیم به سادگی میبینیم که همان اشتباه را در این جا نیز مرتکب شدهایم. ما یک بار دیگر باید addeventListener را به addEventListener. تغییر دهیم و بنابراین آن را هم اینک انجام میدهیم.
یک خطای منطقی
در این مرحله، بازی باید به طور کامل تا آخر به خوبی اجرا شود؛ اما پس از این که چند بار بازی را اجرا کردید بیشک متوجه میشوید که عدد «تصادفی» که از شما خواسته میشود حدس بزنید، همواره 0 یا 1 است. بدیهی است که این آن وضعیتی نیست که ما از یک بازی انتظار داریم. قطعاً مشکلی در منطق بازی در بخشی از این نرمافزار وجود دارد، با این حال، بازی خطایی بازنمیگرداند و صرفاً به درستی اجرا نمیشود.
به دنبال متغیر randomNumber و خطی بگردید که عدد تصادفی نخستین بار تعیین میشود. وهلهای که عدد تصادفی که باید حدس بزنیم در آن ذخیره میشود، در ابتدای بازی و در خط 44 کد قرار دارد:
var randomNumber = Math.floor(Math.random()) + 1;
وهلهای که عدد تصادفی را پیش از هر بازی بعدی ایجاد میکند در خط 113 قرار دارد:
randomNumber = Math.floor(Math.random()) + 1;
برای بررسی این که این خطوط موجب ایجاد مشکل هستند یا نه، مجدداً از console.log() کمک میگیریم. خط زیر را به طور مستقیم زیر هر یک از دو خط فوق درج کنید:
console.log(randomNumber);
فایل را ذخیره کرده و صفحه را رفرش کنید. بدین ترتیب میبینید که randomNumber در هر نقطه از کد که گزارش میگیریم برابر با 1 است.
کار با منطق برنامه
برای اصلاح این مشکل ابتدا باید از طرز کار این خط سر دربیاوریم. بنابراین ابتدا ()Math.random را فراخوانی میکنیم که یک عدد اعشاری تصادفی بین 0 و 1 مانند 0.5675493843 تولید میکند.
()Math.random
سپس نتیجه فراخوانی ()Math.random را از طریق ()Math.floor فراخوانی میکنیم که عدد ارسالی را تا نزدیکترین عدد کامل گرد میکند. و در این حالت نتیجهای برابر با 1 به دست میآوریم:
Math.floor(Math.random()) + 1
گرد کردن یک عدد اعشاری بین 0 و 1 به سمت پایین همواره باعث بازگشت عدد 0 میشود و بنابراین افزودن 1 واحد به آن باعث میشود به 1 تبدیل شود. در واقع ما باید پیش از آن که عدد را گرد کنیم آن را در 100 ضرب کرده باشیم. در کد زیر یک عدد تصادفی بین 0 و 99 به دست میآید:
Math.floor(Math.random()*100);
بدین ترتیب با افزودن مقدار 1، عددی بین 1 و 100 به دست میآوریم:
Math.floor(Math.random()*100) + 1;
اگر سعی کنیم هر دو خط را مانند حالت فوق بهروزرسانی کنیم و سپس فایل را ذخیره کرده و رفرش کنیم، به آن منطق بازی که انتظار داریم خواهیم رسید.
خطاهای رایج دیگر
خطاهای متداول دیگری نیز وجود دارند که ممکن است در کد وجود داشته باشند. در این بخش اغلب آنها را بررسی میکنیم.
SyntaxError: missing; before statement
این خطا به طور کلی به آن معنا است یک نقطهویرگول در انتهای خطوط کد فراموش شده است؛ اما در برخی موارد نیز ممکن است مسئله از این پیچیدهتر باشد. برای نمونه اگر این خط را درون تابع ()checkGuess:
var userGuess = Number(guessField.value);
به صورت زیر تغییر دهیم:
var userGuess === Number(guessField.value);
کد فوق خطایی ایجاد میکند. چون فکر میکند که شما تلاش میکنید کار متفاوتی را انجام دهید. باید اطمینان حاصل کنید که عملگر انتساب (=) که مقدار یک متغیر را انتساب میدهد را با عملگر برابری (===) که برابر بودن دو مقدار را بررسی میکند، و نتیجه درست/نادرست بازمیگرداند، اشتباه نگرفتهاید.
برنامه صرف نظر از مقداری که وارد میکنید، همواره برنده شدن شما را اعلام میکند. این مشکل نیز میتواند یک نشانه دیگر از استفاده اشتباه از عملگر انتساب به جای عملگر برابری باشد. برای نمونه اگر بخواهیم خط درون ()checkGuess را از حالت زیر:
if (userGuess === randomNumber) {
به حالت زیر تغییر دهیم:
if (userGuess = randomNumber) {
نتیجه آزمون ما همواره مقدار true خواهد بود که موجب میشود برنامه گزارش کند که بازیکن برنده شده است، پس باید مراقب این موضوع باشید.
SyntaxError: missing ) after argument list
این خطا کاملاً ساده است و عموماً به این معنا است که پرانتز انتهایی را در پایان فراخوانی یک تابع یا متد فراموش کردهاید.
SyntaxError: missing: after property id
این خطا معمولاً به یک شیء جاوا اسکریپت با شکل نادرست اشاره دارد؛ اما در این مورد میتوان با تغییر کد از وضعیت زیر
function checkGuess() {
به این وضعیت:
function checkGuess({
آن را اصلاح نمود.
این کار باعث میشود که مرورگر فکر کند که ما میخواهیم محتوای تابع را به صورت یک آرگومان به درون تابع ارسال کنیم. بنابراین باید هنگام استفاده از پرانتزها مراقب باشید.
SyntaxError: missing } after function body
این خطا کاملاً ساده است و عموماً به این معنا است که یکی از کروشهها را در ساختار تابع یا شرط فراموش کردهاید. این خطا با حذف یکی از کروشههای پایانی در انتهای تابع ()checkGuess ایجاد شده است.
SyntaxError: expected expression, got 'string' or SyntaxError: unterminated string literal
این خطاها به طور کلی به این معنا هستند که یک گیومه باز یا بسته در یک مقدار رشتهای فراموش شده است. در نخستین خطای فوق مقدار string باید با کاراکترهای ناخواستهای که مرورگر به جای علامت گیومه در آغاز رشته یافته است جایگزین شود. خطای دوم به این معنی است که رشته با یک گیومه بسته نشده است.
در مورد همه این خطاها باید به روشی که در مثالها برای مقابله با آنها استفاده کردیم فکر کنید. زمانی که یک خطا رخ میدهد به شماره خط نگاه کنید و سپس به آن خط رفته و ببینید آیا میتوانید مشکل را پیدا کنید یا نه. به یاد داشته باشید که خطا لزوماً در آن خط قرار ندارد و همچنین خطا ممکن است از همان مسئلهای که در بخش قبل اشاره کردیم ایجاد نشده باشد.
سخن پایانی
در این نوشته به بررسی مبانی مفاهیم مقدماتی مرتبط با خطاها در برنامههای جاوا اسکریپت پرداختیم. البته یافتن خطاها در کد همواره به این سادگیها نیست؛ اما دستکم با مطالعه این راهنما و یادگیری مهارتهای معرفی شده میتوانید ساعتها در زمان خود برای عیبیابی برنامهها صرفهجویی کنید و زمانی که برنامه درست کار نمیکند، با سرعت بیشتری مشکلات را حل کنید.
اگر این مطلب برایتان مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای طراحی و برنامه نویسی وب
- آموزش جاوا اسکریپت (JavaScript)
- مجموعه آموزشهای برنامهنویسی
- آموزش جاوا اسکریپت — مجموعه مقالات جامع وبلاگ فرادرس
- متدهای شیء (Object Methods) در جاوا اسکریپت — به زبان ساده
- متغیرهای جاوا اسکریپت — به زبان ساده
==
سلام وقت بخیر و تشکر بابت آموزش فوق العادهتان
در چند جا بجای خط شماره 49 به اشتباه خط 48 درج شده است.
با سلام و احترام؛
صمیمانه از همراهی شما با مجله فرادرس و ارائه بازخورد سپاسگزاریم.
این مورد اصلاح شد.
برای شما آرزوی سلامتی و موفقیت داریم.