همهی ما از دوران ابتدایی چهار عمل اصلی را یاد داریم. البته عملهای دیگری مثل «توان»، «جذر» و غیره نیز علاوه بر چهار عمل اصلی وجود دارد. این عملها که شامل جمع +
، ضرب *
، تفریق -
و… میشود در برنامهنویسی هم حضور دارند. البته در جاوااسکریپت عملگرهای فراوان دیگری غیر از اینها هم وجود دارد.
در این بخش میخواهیم چیزهایی در مورد این عملها یاد بگیریم که در ابتدایی به ما نیاموختهاند.
اصطلاحات
برای اینکه بتوانیم راحت تر صحبت کنیم، لازم است در ابتدا با چند اصطلاح آشنا شویم.
- عملگر (Operator) – به علامتی که برای عملهای ریاضی استفاده میکنیم عملگر میگوییم. مثلاً
+
،-
یا*
عملگر هستند.
- عملوند (Operand) – چیزی است که عملگر، عمل خود را روی آن انجام میدهد. مثلاً در عمل ضرب
۵ * ۲
دو عملوند داریم. عملوند سمت چپ۵
است و عملوند سمت راست۲
است. خود*
هم عملگر است. گاهی اوقات به عملوندها «argument» نیز گفته میشود.
- عملگر یگانی (unary) – به عملگری که تنها یک عملوند داشته باشد عملگر یگانی میگوییم. مثلاً عملگر منفی یگانی (که عملگر قرینه نیز نام دارد) علامت منفی یا مثبت عدد را برعکس میکند:
let x = 1;
x = -x;
// عملگر منفی یگانی عدد را قرینه کرده است
alert( x ); // -1
- عملگر دودویی (binary) – به عملگری که دو عملوند داشته باشد عملگر دودویی میگوییم. مثلاً عملگر منفی دودویی (که عملگر تفریق نیز نام دارد) اعداد را از یکدیگر تفریق میکند:
let x = 1, y = 3;
// عملگر منفی دودویی اعداد را از هم تفریق میکند
alert( y - x ); // 2
در واقع دو مثال اخیر دو عملگر متفاوت را نشان میدهند که وظایف متفاوتی دارند. وظیفهی عملگر منفی یگانی قرینه کردن عدد است، در حالی که وظیفهی عملگر منفی دودویی تفریق دو عدد است.
عملگر مثبت دودویی: چسباندن رشتهها به هم
حالا میخواهیم در مورد قابلیّتهای خاص عملگرهای جاوااسکریپت صحبت کنیم که فراتر از حد ریاضیات ابتدایی است.
معمولاً عملگر مثبت +
برای محسابهی مجموع اعداد به کار میرود.
ولی اگر این عملگر را روی رشتهها به کار ببرید، رشتهها را با هم ترکیب میکند، یعنی پشت سر هم آنها را مینویسد.
let s = "my" + "string";
alert(s); // mystring
حالا اگر یک طرف رشته بود و طرف دیگر رشته نبود، طرف دوم به رشته تبدیل میشود. مثلاً:
alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"
ببینید اصلاً مهم نیست که سمت راست رشته است یا سمت چپ. اگر یکی از طرفین رشته وجود داشت، طرف دیگر نیز به رشته تبدیل میشود.
امّا حواستان باشد به هر حال عملگرها از سمت چپ به راست اجرا میشوند. اگر دو عدد و یک رشته را پشت سر هم با یکدیگر جمع کنید، مشابه مثال زیر، ابتدا عملگر مثبت سمت چپی وارد عمل میشود که هر دو طرفش عدد است. بنابراین مثل دو عدد معمولی با هم جمعشان میکند. سپس عملگر مثبت دوم وارد عمل میشود که چون سمت راستش رشته است، سمت چپ را نیز به رشته تبدیل میکند و در نهایت رشتهها را سر هم میکند:
alert(2 + 2 + '1' ); // می شود «۴۱» و نمی شود «۲۲۱»
عملگر مثبت +
دودویی تنها عملگری است که برای رشتهها کاری متفاوت انجام میدهد. بقیّهی عملگرهای ریاضی همیشه و در هر صورتی هر دو طرف را به عدد تبدیل میکنند. برای مثال تفریق و تقسیم را ببینید:
alert( 2 - '1' ); // 1
alert( '6' / '2' ); // 3
عملگر مثبت یگانی: تبدیل نوع به عدد
دو نوع عملگر مثبت +
داریم. یکی نوع دودویی آن است که صحبت کردیم. دیگری نوع یگانیاش است.
عملگر مثبت یگانی یا همان مثبتی که تنها به یک مقدار داده میشود، هیچ کاری روی اعداد نمیکند. یعنی اگر به یک عدد اعمال شود خروجی میشود همان عدد. ولی اگر به مقداری غیرعددی اعمال شود تبدیلش میکند به عدد. مثلاً:
// روی اعداد کاری نمیکند
let x = 1;
alert( +x ); // 1
let y = -2;
alert( +y ); // -2
// مقادیر غیر عددی را به عدد تبدیل میکند
alert( +true ); // 1
alert( +"" ); // 0
این عملگر دقیقاً همان کار تابع Number(...)
را میکند، ولی راحتتر است.
زیاد پیش میآید که بخواهیم رشته را به عدد تبدیل کنیم. مثلاً اگر در صفحهی html یک کادر متنی برای وارد کردن عدد گذاشته باشیم و مقدار آن را بگیریم، این مقدار از نوع رشته است. حالا اگر بخواهیم این مقدار را با عدد دیگری جمع کنیم چه میشود؟ عملگر مثبت دودویی آنها را مثل رشتههای معمولی دنبال سر هم مینویسد:
let apples = "2";
let oranges = "3";
// مثبت دودویی رشتهها را دنبال سر هم مینویسد
alert( apples + oranges ); // "23"
اگر بخواهیم این رشتهها مثل اعداد معمولی با هم جمع شوند، باید قبل از اینکه آنها را با هم جمع کنیم تبدیل به عدد کنیم:
let apples = "2";
let oranges = "3";
// قبل از استفاده از مثبت دودویی هر دو را به عدد تبدیل کردیم
alert( +apples + +oranges ); // 5
// معادل طولانی ترش اینگونه می شد
// alert( Number(apples) + Number(oranges) ); // 5
اگر کسی از دیدگاه ریاضی کد بالا را نگاه کند، با دیدن این همه علامت «+» قاطی میکند ?. ولی از دیدگاه یک برنامهنویس اصلاً چیز خاص و عجیبی نیست؛ ابتدا عملگرهای مثبت یگانی رشتهها را به عدد تبدیل میکنند، بعد عملگر مثبت دودویی آنها را با هم جمع میکند.
حالا چرا ابتدا عملگر مثبت یگانی اعمال میشود و بعد دودویی؟ چون اولویت مثبت یگانی بیشتر از دودویی است. در ادامه در مورد اولویّتها صحبت میکنیم.
اولویت عملگرها
اگر در عبارتی بیش از یک عملگر داشته باشیم، ترتیب اجرای عملگرها بر اساس اولویّت عملگرها تعیین میشود. هر چه اولویّت عملگر بیشتر باشد زودتر عمل خود را انجام میدهد.
از همان دوران ابتدایی میدانیم در عبارت ۱ + ۲ * ۲
ابتدا باید عمل ضرب را انجام دهیم و بعد نتیجه را با ۱ جمع کنیم. دلیلش دقیقاً همین است که اولویّت ضرب بیشتر از جمع است.
با گذاشتن پرانتز میشود هر اولویّتی را تغییر داد. پس اگر دوست نداریم عبارت طبق اولویّت عادی اجرا شود از پرانتز کمک میگیریم، مثلاً اگر در همان مورد قبلی بخواهیم ابتدا عمل جمع انجام شود و بعد نتیجه در ۲ ضرب شود، آن را به این شکل مینویسیم: (۱ + ۲) * ۲
.
عملگرهای فراوانی در جاوااسکریپت داریم. اولویّت هر عملگر را با یک عدد نشان میدهیم. هر چه این عدد بزرگتر باشد اولویت عملگر بیشتر است. اگر اولویت دو عملگر یکسان باشد ترتیب اجرایشان از چپ به راست است.
جدول زیر بخشی از جدول اولویّت عملگرهای جاوااسکریپت است (لازم نیست جدول را حفظ کنید، فقط همین قدر بدانید که عملگرهای یگانی اولویّت بیشتری از عملگرهای دودویی دارند).
اولویت | نام | علامت |
---|---|---|
… | … | … |
۱۶ | مثبت یگانی | + |
۱۶ | منفی یگانی | - |
۱۴ | ضرب | * |
۱۴ | تقسیم | / |
۱۳ | جمع | + |
۱۳ | تفریق | - |
… | … | … |
۳ | انتساب | = |
… | … | … |
همان طور که میبینید «مثبت یگانی» دارای اولویت ۱۶ است، که بیشتر از ۱۳ یعنی اولویت «جمع» است. به همین دلیل در «+apples + +oranges
» مثبت یگانی قبل از جمع اجرا میشود.
انتساب
جالب است بدانید مساوی =
نیز یک عملگر است، که به آن عملگر انتساب میگوییم. زیرا مقداری را به متغیّر نسبت میدهد. همان طور که در جدول میبینید، اولویّت عملگر انتساب ۳ است که بسیار پایین به حساب میآید.
به همین دلیل وقتی عبارتی مثل x = 2 * 2 + 1
را به متغیّری نسبت میدهیم، ابتدا محاسبات ریاضی انجام میشود و بعدش عملگر «=
» اجرا میشود و نتیجه را در متغیّر x
میریزد.
let x = 2 * 2 + 1;
alert( x ); // 5
میتوان چند عملگر انتساب را پشت سر هم نوشت:
let a, b, c;
a = b = c = 2 + 2;
alert( a ); // 4
alert( b ); // 4
alert( c ); // 4
در انتسابهای پشت سر هم ترتیب اجرا از راست به چپ است. یعنی ابتدا سمت راستترین =
عمل میکند و ۲ + ۲
را درون c
میریزد. بعد b
و بعد c
. در نهایت همهی متغیّرها دارای همان مقدار یکسان میشوند.
عملگر انتساب =
مقدار بر میگرداند.
در جاوااسکریپت هر عملگری مقداری را به عنوان نتیجه بر میگرداند. این موضوع برای عملگرهایی مثل جمع +
یا ضرب *
مشخّص است. ولی جالب است بدانید عملگر انتساب هم از این قاعده خارج نیست.
عبارت x = value
مقدار value
را درون x
میریزد و بعد همان مقدار را بر می گرداند.
در مثال زیر در دل عبارتی پیچیده از عملگر انتساب هم استفاده کردیم:
let a = 1;
let b = 2;
let c = 3 - (a = b + 1);
alert( a ); // 3
alert( c ); // 0
در این مثال نتیجهی عبارت (a = b + 1)
مقداری است که درون a
ریخته شده است (که اینجا میشود ۳). بعد برای بقیهی کار از این خروجی استفاده میشود. منظور ما هم از برگرداندن مقدار دقیقاً همین بود.
این طور کد نوشتن خیلی مسخره است. ولی باید موضوع را خوب درک کنید، چون گاهی در کتابخانههای جاوااسکریپتی با چنین کدهایی مواجه خواهید شد. ولی خود ما هیچ وقت نباید به این شکل کد بنویسیم. چون مبهم و ناخوانا میشود.
باقیماندهی تقسیم %
عملگر باقیمانده %
برخلاف ظاهرش ربطی به درصد ندارد.
نتیجهی a % b
میشود باقیماندهی تقسیم عدد صحیح a
بر b
.
مثلاً:
alert( 5 % 2 ); // باقیماندهی تقسیم ۵ بر ۲ میشود ۱
alert( 8 % 3 ); // باقیماندهی تقسیم ۸ بر ۳ میشود ۲
alert( 6 % 3 ); // باقیماندهی تقسیم ۶ بر ۳ میشود ۰
توان **
عملگر توان **
به تازگی به جاوااسکریپت اضافه شده است.
نتیجهی a ** b
معادل این است که a
را b
مرتبه در خودش ضرب کنیم. البته به شرطی که b
عددی طبیعی باشد. مثلاً:
alert( 2 ** 2 ); // 4 (2 * 2)
alert( 2 ** 3 ); // 8 (2 * 2 * 2)
alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2)
این عملگر با اعداد اعشاری هم درست کار میکند. مثلاً:
// توان یک دوم همان جذر گرفتن است
alert( 4 ** (1/2) ); // 2
// توان یک سوم معادل ریشهی سوم عدد است
alert( 8 ** (1/3) ); // 2
افزایش و کاهش
در بین عملیاتهای مرتبط با اعداد، یکی از پرکاربردترین آنها این است که به عددی یک واحد اضافه کنیم، یا یکی از آن کم کنیم.
از این رو عملگرهایی مخصوص همین کار در جاوااسکریپت ساختهاند:
- عملگر افزایش
++
به متغیّر یک واحد اضافه میکند:
let counter = 2;
counter++; // فرقی با counter = counter + 1 ندارد، فقط کوتاهتر است
alert( counter ); // 3
- عملگر کاهش
--
از متغیّر یک واحد کم میکند:
let counter = 2;
counter--; // فرقی با counter = counter - 1 ندارد، فقط کوتاهتر است
alert( counter ); // 1
عملگر افزایش و کاهش را فقط برای متغیّرها به کار ببرید. اگر برای مقادیر معمولی از آن استفاده کنید، مثلاً بنویسید ۵++
خطا میدهد.
عملگر ++
و --
را هم میتوان قبل از نام متغیّر نوشت، و هم بعدش.
- حالت پسوندی – نوشتن عملگر بعد از نام متغیّر:
counter++
- حالت پیشوندی – نوشتن عملگر قبل از نام متغیّر :
++counter
در هر دو حالت متغیّر counter
یک واحد زیاد میشود.
آیا این دو حالت فرقی با هم دارند؟ بله، ولی فقط اگر بخواهید از مقداری که عملگر ++
یا --
بر میگرداند استفاده کنید.
حالا قضیه را بازتر میکنیم. همان طور که گفتیم هر عملگری مقدار بر میگرداند، و عملگر ++
و --
هم استثنا نیستند. تفاوت این جاست که حالت پیشوندی مقدار جدید متغیّر را بر میگرداند، ولی حالت پسوندی مقدار قدیمی را بر میگرداند (قبل از اینکه یک واحد به آن اضافه شود).
برای درک بهتر این تفاوت مثال زیر را ببینید:
let counter = 1;
let a = ++counter;
alert(a); // 2
در این مثال در خط شماره ۲ حالت پیشوندی یعنی ++counter
را داریم که یک واحد به counter
اضافه میکند و مقدار جدید یعنی ۲
را بر میگرداند. بنابراین alert
عدد ۲
را نشان می دهد.
حالا حالت پسوندی را نگاهی میاندازیم:
let counter = 1;
let a = counter++; // قبلا counter++ بود
alert(a); // 1
در این جا در خط شماره ۲ حالت پسوندی یعنی counter++
را داریم که یک واحد به counter
اضافه میکند، ولی مقدار قدیمی یعنی ۱
را بر میگرداند. بنابراین alert
عدد ۱
را نشان میدهد.
عملگر افزایش یا کاهش در بین سایر عملگرها
اولویّت عملگرهای ++
و --
معمولاً از سایر عملگرها بیشتر است. مثلاً:
let counter = 1;
alert( 2 * ++counter ); // 4
مقایسه کنید با این:
let counter = 1;
// در اینجا مقدار «قدیمی» برمیگردد، ولی همچنان اولویت افزایش بالاتر از ضرب است
alert( 2 * counter++ ); // 2
اگرچه از لحاظ فنی چنین کدهایی مشکل ندارد، ولی کد ناخوانا میشود. انجام چند کار در یک خط کار خوبی نیست.
وقتی کسی نگاهی سریع به کد میاندازد به احتمال زیاد در همان ابتدا متوجّه ++
نمیشود، و نمیفهمد متغیّر دارد در این خط افزایش مییابد.
توصیه میکنیم در هر خط فقط یک کار انجام دهید:
let counter = 1;
alert( 2 * counter );
counter++;
عملگرهای بیتی
عملگرهای بیتی، عملوندهای خود را اعداد صحیح ۳۲ بیتی در نظر میگیرند، و عملیّاتی را در در دستگاه دودویی روی آنها انجام میدهند.
این عملگرها مخصوص جاوااسکریپت نیست. در بیشتر زبانهای برنامهنویسی این عملگرها وجود دارند.
لیست عملگرهای بیتی:
- AND (
&
) - OR (
|
) - XOR (
^
) - NOT (
~
) - LEFT SHIFT (
<<
) - RIGHT SHIFT (
>>
) - ZERO-FILL RIGHT SHIFT (
>>>
)
این عملگرها به ندرت به کار میآیند. برای درک کار این عملگرها لازم است در زمینهی دستگاه اعداد دودویی و اینکه کامپیوتر چگونه با اعداد کار میکند و مسائلی از این قبیل بحث کنیم. این مسائل آموزش ما را بیدلیل سنگین میکند، چرا که حتّی اگر یادشان هم بگیرید کاربرد چندانی برایتان نخواهد داشت. ولی باز هم اگر خیلی اصرار دارید میتوانید نگاهی به ویکیپدیا بیندازید. اگر دنبال یک آموزش کامل برای این عملگرها هستید MDN عالیست. فقط مشکل این است که کسی ترجمهاش نکرده است. بهتر است فعلاً بی خیال این عملگرها شوید تا زمانی که واقعاً در یک پروژه به آنها احساس نیاز کنید.
ترکیب انتساب با عملگرهای دیگر
خیلی وقتها لازم است عملگری را روی متغیّری اعمال کنیم و نتیجه را در همان متغیّر بریزیم. مثلاً:
let n = 2;
n = n + 5;
n = n * 2;
روشی سادهتر برای نوشتن چنین چیزی داریم، یعنی عملگرهای +=
و *=
:
let n = 2;
n += 5; // الآن n = 7 است (مثل همان n = n + 5)
n *= 2; // الآن n = 14 است (مثل همان n = n * 2)
alert( n ); // 14
عملگر انتساب به همین شکل میتواند با هر کدام از عملگرهای ریاضی و بیتی ترکیب شود، مثلاً /=
، -=
و غیره.
اولویّت عملگرهای انتساب ترکیبی نیز مثل اولویت انتساب معمولی است. از این رو معمولاً بعد از اینکه محاسبات دیگر انجام شد اعمال میشوند:
let n = 2;
n *= 3 + 5;
// ابتدا سمت راست محاسبه میشود، مثل این است که بنویسیم n *= 8
alert( n ); // 16
ویرگول
عملگر کاما یا همون ویرگول انگلیسی ,
یکی از به درد نخورترین عملگرهاست ?. گاهی اوقات بعضیها از این عملگر برای کوتاهتر شدن کدشان استفاده میکنند. برای همین مجبوریم این عملگر را یاد بگیریم تا مفهوم کد را درک کنیم.
عملگر ویرگول، عملگری دودویی است که دو عبارت را محاسبه میکند، و در نهایت عبارت سمت راستش را بر میگرداند. مثلاً:
let a = (1 + 2, 3 + 4);
alert( a ); // 7 (حاصل ۳ + ۴)
در این جا، ابتدا عبارت ۱ + ۲
محاسبه میشود، ولی از حاصل آن هیچ استفادهای نمیشود. بعد ۳ + ۴
محاسبه میشود و حاصلش به عنوان خروجی اصلی بر میگردد.
اولویت ویرگول بسیار پایین است.
اولویّت عملگر ویرگول حتّی از =
نیز پایین تر است. برای همین در مثال بالا حتماً لازم بود پرانتز بگذاریم.
اگر پرانتز نمیگذاشتیم و مینوشتیم a = 1 + 2, 3 + 4
ابتدا عملگر +
به خاطر بالاتر بودن اولویّتش اعمال میشد و نتیجه میشد a = 3, 7
بعد به خاطر بالاتر بودن اولویت =
از ,
ابتدا a = 3
میشد و بقیهی عبارت نادیده گرفته میشود. میتوان گفت مثل این است که نوشته باشیم (a = 1 + 2), 3 + 4
.
سؤال اینجاست که چنین عملگری واقعاً به چه درد میخورد؟ جواب اینجاست که گاهی اوقات برای اینکه چند دستور را در یک خط بنویسند از این عملگر استفاده میکنند. مثلاً:
// سه دستور پشت سر هم در یک خط نوشته شده است
for (a = 1, b = 3, c = a * b; a < 10; a++) {
...
}
این ترفند در بسیاری از فریمورکهای مختلف جاوااسکریپتی به کار رفته است. به همین دلیل لازم دانستیم اشارهای به آن داشته باشیم. ولی خود ما در کدهایی که خواهیم نوشت از این عملگر استفاده نخواهیم کرد، زیرا خوانایی کد را کاهش میدهد.