کاربردهای var ،let و const در جاوا اسکریپت — به زبان ساده
در این نوشته به بررسی دو روش جدید برای ایجاد متغیرها در جاوا اسکریپت (ES6) میپردازیم که let و const هستند. در این مسیر تفاوتهای بین var ،let و const و همچنین موضوعاتی مانند حیطه تعریف تابع و بلوک، hoisting متغیرها و تغییرناپذیری (immutability) را بررسی میکنیم.
ES2015 (یا ES6) دو روش جدید برای ایجاد متغیرها معرفی کرده است که شامل var و const میشود. اما پیش از آن که عملاً تفاوتهای بین این موارد را بررسی کنیم، برخی موارد وجود دارند که با آنها آشنا شویم. این موارد شامل اعلان متغیر، مقداردهی اولیه متغیر، حیطه یا دامنه متغیر (به طور خاص حیطه تابع) و hoisting هستند.
اعلان یا مقداردهی اولیه متغیر
اعلان متغیر یک شناسه جدید را معرفی میکند:
1var declaration
در دستور فوق ما یک شناسه به نام declaration ایجاد کردیم. در جاوا اسکریپت متغیرها در زمان ایجاد شدن، به صورت تعریف نشده (undefined) مقداردهی اولیه میشوند. معنی این حرف آن است که اگر تلاش کنید متغیر declaration را چاپ کنیم، با مقدار undefined مواجه خواهیم شد.
1var declaration
2console.log(declaration)
بنابراین اگر متغیر declaration را نمایش دهیم، یک مقدار تعریف نشده به دست میآوریم. در زمان مقداردهی اولیه متغیر، برخلاف اعلان آن یک مقدار به متغیر نسبت داده میشود.
1var declaration
2console.log(declaration) // undefined
3declaration = 'This is an initialization'
بنابراین در کد فوق با انتساب یک رشته به متغیر declaration آن را «مقداردهی اولیه» (initialize) میکنیم.
حیطه متغیر
منظور از حیطه یا دامنه متغیر و یا تابع، مکانهایی است که متغیر درون برنامه از آنجا قابل دسترسی است. در جاوا اسکریپت دو نوع حیطه وجود دارند، حیطه سراسری (global scope) و حیطه تابع (function scope). بر اساس تعریف رسمی:
اگر عبارت متغیر درون اعلان یک تابع باشد، متغیر با حیطه همان تابع تعریف میشود.
معنی گفته فوق این است که اگر یک متغیر را با استفاده از کلیدواژه var ایجاد کنیم، حیطه آن درون همان تابعی خواهد بود که تعریف شده است و صرفاً از آن تابع یا هر تابع دیگری که درون آن تابع تعریف شده باشد قابل دسترسی خواهد بود.
1function getDate () {
2 var date = new Date()
3
4 return date
5}
6
7getDate()
8console.log(date) // ❌ Reference Error
در کد فوق تلاش کردهایم که به یک متغیر از خارج از تابعی که در آن اعلان شده دسترسی بیابیم. از آنجا که date دارای حیطهای درون تابع getDate است، تنها از درون آن یا تابعهای داخل آن قابل دسترسی است.
1function getDate () {
2 var date = new Date()
3
4 function formatDate () {
5 return date.toDateString().slice(4) // ✅
6 }
7
8 return formatDate()
9}
10
11getDate()
12console.log(date) // ❌ Reference Error
در ادامه مثال پیشرفتهتری را بررسی میکنیم. فرض کنید آرایهای از قیمتها (prices) داریم و میخواهیم تابعی تعریف کنیم که با بررسی مقادیر این آرایه و همچنین مقادیر تخفیف (discount)، مبالغ دارای تخفیف را به ما بازگشت دهد. هدف نهایی چیزی مانند زیر خواهد بود:
1discountPrices([100, 200, 300],.5)
و پیادهسازی آن میتواند چیزی مانند زیر باشد:
1function discountPrices (prices, discount) {
2 var discounted = []
3
4 for (var i = 0; i < prices.length; i++) {
5 var discountedPrice = prices[i] * (1 - discount)
6 var finalPrice = Math.round(discountedPrice * 100) / 100
7 discounted.push(finalPrice)
8 }
9
10 return discounted
11}
کد فوق به نظر ساده میآید؛ اما شاید از خود بپرسید چه ربطی به حیطه بلوکی دارد؟ اگر به حلقه for نگاهی بیندازید میبینید که متغیرهایی که درون آن تعریف شدهاند از خارج آن نیز قابل دسترسی هستند.
1function discountPrices (prices, discount) {
2 var discounted = []
3
4 for (var i = 0; i < prices.length; i++) {
5 var discountedPrice = prices[i] * (1 - discount)
6 var finalPrice = Math.round(discountedPrice * 100) / 100
7 discounted.push(finalPrice)
8 }
9
10 console.log(i) // 3
11 console.log(discountedPrice) // 150
12 console.log(finalPrice) // 150
13
14 return discounted
15}
اگر جاوا اسکریپت تنها زبانی باشد که با آن آشنا هستید ممکن است از چنین وضعیتی تعجب کرده باشید، اما اگر با زبانهای دیگر به خصوص آنهایی که دارای حیطه تعریف بلوکی هستند نیز آشنا باشید، احتمالاً بهتر متوجه این وضعیت میشوید.
گرچه منطقاً دلیلی وجود ندارد که بخواهیم خارج از حلقه for به متغیرهای i ،discountedPrice و finalPrice دسترسی داشته باشیم، چون علاوه بر این که این کار هیچ فایدهای برای ما ندارد، شاید در مواردی موجب بروز مشکل نیز بشود؛ اما از آنجا که متغیرها با var اعلان شدهاند، دارای حیطه تابعی هستند و چنین امکانی عملاً وجود دارد.
اکنون که مباحث اعلان، مقداردهی اولیه و حیطه تابع را تعریف کردیم، آخرین نکتهای که باید پیش از بررسی تفاوتهای let و const مورد بررسی قرار دهیم، hoisting است.
Hoisting
اگر به خاطر داشته باشید، در ابتدای مقاله بیان کردیم که «در جاوا اسکریپت، متغیرها در زمان ایجاد شدن، با مقدار تعریف نشده (undefined) مقداردهی اولیه میشوند» این همان معنی hoisting است. مفسر جاوا اسکریپت در زمان اعلان متغیر یک مقدار پیشفرض undefined، را در مرحلهای که فاز «Creation» نامیده میشود به آن انتساب میدهد.
در ادامه مثالی ارائه شده که طرز کار hoisting را در عمل نشان میدهد.
1function discountPrices (prices, discount) {
2 var discounted = undefined
3 var i = undefined
4 var discountedPrice = undefined
5 var finalPrice = undefined
6
7 discounted = []
8 for (var i = 0; i < prices.length; i++) {
9 discountedPrice = prices[i] * (1 - discount)
10 finalPrice = Math.round(discountedPrice * 100) / 100
11 discounted.push(finalPrice)
12 }
13
14 console.log(i) // 3
15 console.log(discountedPrice) // 150
16 console.log(finalPrice) // 150
17
18 return discounted
19}
دقت کنید که همه اعلانهای متغیر دارای مقدار انتسابی پیشفرض undefined هستند. به همین دلیل است که اگر بخواهید پیش از اعلان واقعی متغیر به آن دسترسی یابید با مقدار undefined مواجه میشوید.
1function discountPrices (prices, discount) {
2 console.log(discounted) // undefined
3
4 var discounted = []
5
6 for (var i = 0; i < prices.length; i++) {
7 var discountedPrice = prices[i] * (1 - discount)
8 var finalPrice = Math.round(discountedPrice * 100) / 100
9 discounted.push(finalPrice)
10 }
11
12 console.log(i) // 3
13 console.log(discountedPrice) // 150
14 console.log(finalPrice) // 150
15
16 return discounted
17}
اینک که همه چیز را در مورد var میدانید، نهایتاً به بررسی ایده اصلی این مقاله یعنی بررسی تفاوتهای var، let و const میپردازیم.
تفاوتهای var ،let و const
در ابتدا به مقایسه کلیدواژههای var و let میپردازیم. تفاوت اصلی بین var و let این است که var دارای حیطه تعریف تابعی است؛ اما let حیطه تعریف بلوکی دارد. معنی این حرف آن است که وقتی متغیری با کلیدواژه let ایجاد شده باشد، درون بلوکی که در آن تعریف شده و بلوکهای تو در توی آن قابل دسترسی خواهد بود. زمانی که از بلوک صحبت میکنیم، منظور ما هر چیزی است که در جاوا اسکریپت درون آکولادها ({}) تعریف میشود و این بلوکها میتوانند شامل حلقه for یا عبارت if باشند.
بنابراین دوباره نگاهی به تابع discountPrices که پیش معرفی کردیم خواهیم داشت.
1function discountPrices (prices, discount) {
2 var discounted = []
3
4 for (var i = 0; i < prices.length; i++) {
5 var discountedPrice = prices[i] * (1 - discount)
6 var finalPrice = Math.round(discountedPrice * 100) / 100
7 discounted.push(finalPrice)
8 }
9
10 console.log(i) // 3
11 console.log(discountedPrice) // 150
12 console.log(finalPrice) // 150
13
14 return discounted
15}
به خاطر دارید که در آن تابع میتوانستیم به متغیرهای i ،discountedPrice و finalPrice در خارج از حلقه for نیز دسترسی داشته باشیم. دلیل این مسئله آن بود که این متغیرها با کلیدواژه var تعریف شده بودند که دارای حیطه تابعی است. اما اینک متغیرها را به جای var با let اعلان میکنیم و سعی میکنیم آن را اجرا کنیم.
1function discountPrices (prices, discount) {
2 let discounted = []
3
4 for (let i = 0; i < prices.length; i++) {
5 let discountedPrice = prices[i] * (1 - discount)
6 let finalPrice = Math.round(discountedPrice * 100) / 100
7 discounted.push(finalPrice)
8 }
9
10 console.log(i) // 3
11 console.log(discountedPrice) // 150
12 console.log(finalPrice) // 150
13
14 return discounted
15}
16
17discountPrices([100, 200, 300], .5) // ❌ ReferenceError: i is not defined
همان طور که مشاهده میکنید ما با خطای ReferenceError: i is not defined مواجه شدیم. این خطا اعلام میکند که متغیر اعلان شده با کلیدواژه let دارای حیطه تابعی نیست. بنابراین تلاش برای دسترسی به i در خارج از بلوکی که در آن اعلان شده است، موجب بروز خطای رفرنس میشود.
1var VS let
2var: function scoped
3let: block scoped
تفاوت بعدی به مسئله Hoisting مربوط میشود. پیشتر گفتیم که منظور از Hoisting این است که مفسر جاوا اسکریپت در زمان ایجاد متغیرها به طور پیشفرض به آنها یک مقدار تعریف نشده میدهد. حتی دیدیم که در عمل با log گرفتن از متغیری که اعلان شده است با مقدار undefined مواجه میشویم.
1function discountPrices (prices, discount) {
2 console.log(discounted) // undefined
3
4 var discounted = []
5
6 for (var i = 0; i < prices.length; i++) {
7 var discountedPrice = prices[i] * (1 - discount)
8 var finalPrice = Math.round(discountedPrice * 100) / 100
9 discounted.push(finalPrice)
10 }
11
12 console.log(i) // 3
13 console.log(discountedPrice) // 150
14 console.log(finalPrice) // 150
15
16 return discounted
17}
البته بدیهی است که هیچ ایده منطقی برای استفاده از متغیری که اعلان نشده است، وجود ندارد. بنابراین به نظر میرسد که بهتر بود جاوا اسکریپت به جای نمایش مقدار undefined یک خطای ارجاع صادر میکرد.
در عمل این همان وضعیتی است که با let به دست میآوریم. اگر شما تلاش کنید به متغیری که با کلیدواژه let اعلان شده، قبل از محلی که اعلان شده است، دسترسی پیدا کنید، به جای دریافت مقدار undefined مانند آنچه در مورد var دیدیم، با یک خطای ارجاع (ReferenceError) مواجه میشوید.
1function discountPrices (prices, discount) {
2 console.log(discounted) // ❌ ReferenceError
3
4 let discounted = []
5
6 for (let i = 0; i < prices.length; i++) {
7 let discountedPrice = prices[i] * (1 - discount)
8 let finalPrice = Math.round(discountedPrice * 100) / 100
9 discounted.push(finalPrice)
10 }
11
12 console.log(i) // 3
13 console.log(discountedPrice) // 150
14 console.log(finalPrice) // 150
15
16 return discounted
17}
$(‘.project-percent’).each(function(){
var $this = $(this);
var percent = $this.attr(‘percent’);
$this.css(“width”,percent+’%’);
$({animatedValue: 0}).animate({animatedValue: percent},{
duration: 2000,
step: function(){
$this.attr(‘percent’, Math.floor(this.animatedValue) + ‘%’);
},
complete: function(){
$this.attr(‘percent’, Math.floor(this.animatedValue) + ‘%’);
}
});
});
سلام این برنامه به زبان جاوااسکریپت چه تغییراتی خواهد داشت ؟
با سلام
شما گفتید: «دقت کنید که تغییر دادن یک خصوصیت شیء به معنی انتساب مقدار به آن نیست. بدین ترتیب این که یک شیء با const اعلان شده باشد، به این معنی نیست که نمیتوان مشخصات آن را تغییر داد. بلکه صرفاً به این معنی است که نمیتوان به آن مقدار دیگری انتساب داد.»
یعنی نمیتوان به یک آرایه یا شی عضوی اضافه کرد که این درست نیست و مثلا با متد push میتوان عضو جدیدی به آرایه اضافه کرد. در تعریف object هم میتوان عضو دیگری اضافه کرد. البته جمله مقدار دیگری به آن انتساب داد هم معلوم نیست منظور شما چیست اما در مثال شما یک شی خالی را با همان نام دوباره تعریف میکنید که معلوم است که خطا میگیرد و اصلا با متن شما همخوانی ندارد ممنون
سلام و وقت بخیر علیرضای عزیز؛
به نظر میرسد در تفسیر جملهای که از متن نقل قول کردهاید، کمی شتاب داشتهاید. چنان که در متن اشاره شده مواردی که با کلیدواژه const اعلان میشوند، دیگر امکان rebinding یا «انتساب» (assign) مقدار دیگر ندارند و در این صورت با خطای TypeError مواجه میشوند. اما با این حال امکان تغییر دادن مشخصهها (Properties) در مورد آنها وجود دارد. در مورد مثالی که فرمودید، چنین چیزی در متن مشاهده نشد.
متشکر از توجه شما.
عالی بود توضیحات کافی و قابل فهم بود
خدا قوت