۷ موردی که ممکن است در رابطه با جاوااسکریپت ندانید
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 7 دقیقه

۷ موردی که ممکن است در رابطه با جاوااسکریپت ندانید

کار کردن با جاوااسکریپت زمان بسیار لذت‌بخشی را به شما می‌بخشد. حتی برای توسعه‌دهندگانی که هر روز با آن سر و کار دارند قسمت‌هایی از آن وجود دارد که برای‌شان بسیار خوشایند است. در این مطلب قصد دارم شما را با ۷ موردی که ممکن است تا به حال در رابطه با جاوااسکریپت ندانسته باشید آشنا کنم.

جاوا اسکریپت

نکته: در این آموزش قصد دارم که از اکمااسکریپت ۲۰۱۵ استفاده کنم به همین دلیل بهتر است که کامپایلر مربوطه را اجرا کنید و یا اینکه خودتان به صورت دستی سینتکس آن را تغییر دهید.

۱. دو «صفر» وجود دارد

معمولا ما از مقدار مثبت صفر استفاده می‌کنیم. اما در کنار این ما یک منفی صفر نیز داریم. به این دلیل که حالت رشته‌ای هر دوی آن‌ها برابر صفر خواهد بود ما نمی‌توانیم آن را مشاهده کنیم:

+0
→ 0
 
-0
→ 0

این بدان دلیل است که جواب (-0).toString() و (+0).toString() هر دو برابر با صفر خواهد بود.

اگر عمیق‌تر نیز به آن‌ها نگاه کنیم متوجه می‌شویم که در حقیقت هر دو مورد با همدیگر برابر هستند:

+0 === -0
→ true
 
+0 > -0
→ false
 
+0 < -0
→ false

همچنین اگر از indexOf در یک آرایه استفاده کنیم باز هم نمی‌شود آن‌ها را به صورت درست از همدیگر تشخیص داد. زیرا در نهایت مقادیر آن‌ها برابر است. 

در کد زیر انتظار یک را داریم؟ درسته؟

[+0, -0, 42].indexOf(-0)
→ 0
 
[-0, +0, 42].indexOf(+0)
→ 0

برای اینکه تفاوت آن‌ها را بهتر متوجه شویم می‌توانیم با تقسیم یک عدد بر دو حالت صفر متوجه تفاوت آن‌ها شویم:

42 / 0
→ Infinity
 
42 / -0
→ -Infinity

بنابراین می‌توانیم به سادگی به صورت زیر متوجه تفاوت‌ها شویم:

دو شرط:

ورودی باید صفر باشد (چه مثبت یا منفی)

در این حالت با تقسیم کردن باید مقداری منفی را دریافت کنیم:

let isNegativeZero = input => input === 0 && 1 / input < 0;

بیاید این موضوع را تست کنیم:

isNegativeZero(0)
→ false
 
isNegativeZero(+0)
→ false
 
isNegativeZero(-0)
→ true

۲. NaN (Not a Number) در حقیقت یک عدد منحصر به فرد است

بله درست است NaN خود یک مقدار عددی است. 

typeof NaN
→ "number"

می‌توانیم از راه‌های مختلفی به این عدد منحصر به فرد دست پیدا کنیم:

  • ۰/۰ (صفر تقسیم بر صفر)
  • +'سلام' (تبدیل یک رشته غیر عددی به عدد)
  • Infinity – Infinity
  • و راه‌های دیگر ...

واقعیت این است که NaN یک عدد منحصر به فرد است.

NaN با خودش مساوی نیست.

اگر تا به حال مقدار if (x !== x) {…} را دیده باشید متوجه می‌شوید که قضیه از چه قرار است.

به صورت ساده NaN با NaN یکی نیست. 

NaN === NaN
→ false

حتی اگر آن را در یک متغیر قرار دهید باز هم ماجرا تغییری نخواهد کرد.

let x = NaN
x === x
→ false

اگر بخواهید مقداری که NaN در یک آرایه خواهد داشت و بعد از آدرس دهی برمی‌گرداند چه مقدار است می‌توانید از قطعه کد زیر استفاده کنید:

let values = [7, NaN, 42];

 پیدا کردن ایندکس ۴۲

values.indexOf(42);
→ 2

پیدا کردن ایندکس NaN:

values.indexOf(NaN)
→ -1
let myIndexOf = (arr, value, start) => {
  if (value !== value) {
    start = start || 0;
    for (let i = start; i < arr.length; ++i) {
       if (arr[i] !== arr[i]) {
          return i;
       }
    }
    return -1;
  }
  return arr.indexOf(value, start);
};
 
// Now, it will work!
myIndexOf(values, NaN)
→ 1

نکته جالب اینجاست که می‌توانید بجای نوشتن دستور if x !==x برای بدست آوردن اینکه x یک NaN است، از دستور isNaN(x) استفاده کنید. البته این مورد کمی کندتر اجرا می‌شود.

اما چرا به این صورت است؟ واقعیت این است که این رفتار از طرف IEEE 754 تعیین شده:

هر NaN باید به صورت تصادفی با همه چیز از جمله خودش مقایسه شود.

اگر از اکمااسکریپت ۲۰۱۵ استفاده می‌کنید دستور includes را به صورت زیر استفاده نمایید:

[42, NaN].includes(NaN)
→ true

NaN یک عدد محدود یا نامحدود نیست

NaN یک عدد محدود نیست، البته یک عدد نامحدود نیز نیست. همانطور که گفته شد NaN یک عدد کاملا منحصر به فرد است. بنابراین خروجی تمام دستورات زیر false خواهد بود.

isFinite(NaN)
→ false
 

Infinity > NaN
→ false
 
> Infinity < NaN
→ false
 
-Infinity < NaN
→ false
 
> -Infinity > NaN
→ false

NaN منفی یا مثبت نیست

NaN همان NaN است. پس خبری از +NaN یا -NaN نیست:

NaN
→ NaN
 
-NaN
→ NaN
 
+NaN
→ NaN

۳. استفاده از عملگرهای بیتی

این موضوع در زبان‌های برنامه‌نویسی مختلف وجود دارد، و از آن‌جایی که در جاوااسکریپت نیز وجود دارد و خیلی به کمی تا به حال دیده شده، ارزش آن را دارد که در رابطه با آن نیز نکاتی گفته شود.

انجام سریع عملیات ضرب و تقسیم یک عدد صحیح

استفاده از این مورد معمولا زمانی انجام می شود که شما مشغول انجام یک پروسه رندرینگ برای یک انیمیشن سه‌بعدی پیچیده هستید. وقتی می‌خواهید عملیات ضرب و تقسیم را براساس یک مقدار بیتی انجام دهید بهترین و سریع‌ترین راه‌حل استفاده کردن از اینگونه عملگرهاست:

// Same with 21 * 2, but faster
21 << 1
→ 42
  
// Same with 5 * 4
5 << 2
→ 20

چنین چیزی را ما در حالت تقسیم نیز می‌توانیم مشاهده کنیم:

84 >> 1
→ 42

براساس تست‌های من این حالت ۱.۰۴ برابر سریع‌تر از عملگر‌های معمولی است. 

ارسال پیام‌های رمزنگاری شده

عملگر ^  یا XOR را در رمزنگاری استفاده می‌کنند. شما می‌توانید با استفاده از این مورد پیام‌هایی را رمزنگاری یا رمزگشایی کنید. شیوه کاری آن به صورت زیر است:

A   B   ^
=========
0   0   0
0   1   1
1   0   1
1   1   0

روش کار با این حالت به صورت زیر است:

دو نفر یک کلید خصوصی را با همدیگر به اشتراک می‌گذارند:

let key = 123;

فرد اول می‌خواهد پیام‌ش را با دیگری به اشتراک بگذارد:

let msg = 42;

اما قبل از ارسال آن را رمزنگاری می‌کند:

msg = msg ^ key // or directly: msg ^= key
→ 81

فرد دوم کلید را دریافت کرده و با آن مقدار پیام را بازگشایی می‌کند. 

81 ^ key
→ 42

حال فرد دوم می‌تواند پیام فرد اول را مشاهده کند.

استفاده از if در یک آرایه

با استفاده از عملگر دو بیتی Not که به صورت ~ است می‌توانید موجودیت یک داده در یک آرایه را بررسی کنید:

let fruits = ["apple", "pear", "orange"];
 
if (fruits.indexOf("pear") !== -1) {
   ...
}
 

if (~fruits.indexOf("pear")) {
   ...
}

برای اینکار می‌توانید از دستور includes نیز استفاده کنید:

["pear", "apple"].includes("apple")
→ true

۴. نمایش رشته با استفاده از یونیکد/هگزادسیمال

اگر بخواهید بنا به یکسری از دلایل می‌توانید رشته‌های‌تان را در پشت یکسری از کدها مخفی نگه دارید. این کار بدون فراخوانی هیچگونه تابعی انجام می‌شود و کاملا محلی است. برای مثال:

// Hex: '2a' (in base 16) is 42—representing the ASCII code of '*'
'\x2a'
→ '*'
 
// Unicode: it expects 4 hex digits
'\u002a'
→ '*'

بیاید تابعی برای این حالت ایجاد کنیم:

let strToHex = input => input.split('').map(
    // Convert each character into its hex code
    c => `\x${c.charCodeAt(0).toString(16)}`
).join('');

تبدیل کردن:

strToHex('hello world!')
→ '\\x68\\x65\\x6c\\x6c\\x6f\\x20\\x77\\x6f\\x72\\x6c\\x64\\x21'

بعد از داشتن خروجی بالا حال تنها کافی‌ست که علامت‌های اضافی را حذف کنیم:

$ echo '\\x68\\x65\\x6c\\x6c\\x6f\\x20\\x77\\x6f\\x72\\x6c\\x64\\x21' | awk 'gsub("\\\\\\\\", "\\")'
→ \x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21

در نهایت خروجی پایین را خواهیم داشت:

\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21
→ 'hello world!'

۵. جابجایی کوتاه عملگرهای منطقی باینتری

در یک عبارت باینری در صورتی که عملوند اول شرایط دستور را داشته باشد دیگر عملوند دومی بررسی نمی‌شود. برای مثال:

true && 'hello'
→ 'hello'

در پایین ما مقدار hello را دریافت نمی‌کنیم. به این دلیل که مقدار false در هر شرایط false است:

false && 'hello'
→ false

یک or منطقی:

false || 'hello'
→ 'hello'

وقتی فراتر از دو عملوند داشته باشیم در نهایت باز هم مطابق با نمونه‌های گذشته از طرف چپ به راست بررسی می‌شود:

0 && false && 'hello'
→ 0
 
42 && false && 'hello'
→ false
 
42 && true && 'hello'
→ 'hello'

موضوع جالب این جاست که چنین چیزی در توابع نیز به همین صورت است:

دریافت یک عدد تصادفی:

let rand = () => Math.random() + 1;

ایجاد یک شئ برای جمع کردن داده‌ها:

let data = {};

یک تابع برای قرار دادن عدد تصادفی در یکسری از keyها:

let add = key => !data[key] && (data[key] = rand()) || data[key];

اضافه کردن یک عدد تصادفی به 'a':

برای 'b' نیز به همین صورت:

add('b')
→ 1.4267915083378722

و 'c':

add('c')
→ 1.495289329665785

حال داده‌ها را در داخل مشاهده کنید:

{ a: 1.0398168717659242,
  b: 1.4267915083378722,
  c: 1.495289329665785 }

جادو در قسمت اضافه کردن تابع اتفاق می‌افتد. برای بیشتر تشریح آن به کدهای زیر نگاه کنید:

// Let's see what's going on here
let add = key => !data[key] && (data[key] = rand()) || data[key];
 
// Looks more human-readable, but it's longer :D
add = key => {
 
   // If the value is not yet set...
   if (!data[key]) {
      // set it!
      data[key] = rand();
   }
 
   // Always, do return the value
   return data[key];
};

۶. اجرای Eval در حالت Strict Mode خیلی بد نیست

استفاده از eval می‌تواند ویژگی‌های خوبی را در پروژه‌های علمی برای‌مان داشته باشد. 

// Let's see what's going on here
let add = key => !data[key] && (data[key] = rand()) || data[key];
 
// Looks more human-readable, but it's longer :D
add = key => {
 
   // If the value is not yet set...
   if (!data[key]) {
      // set it!
      data[key] = rand();
   }
 
   // Always, do return the value
   return data[key];
};

ما متغیر y را درون eval تعیین کرده‌ایم. بنابراین خارج از آن نمی تواند اشاره شود:

"use strict";
 
let x = 35;
 
eval('var y = x + 7');
//        ^
// ReferenceError: y is not defined
console.log(y);

۷. ایجاد تابع به صورت پویا

می‌توانیم با استفاده از سازنده جدید توابع، آن‌ها را به صورت پویا ایجاد کنیم:

let square = new Function('x', 'return x * x');
 
// Let's see how it looks like:
console.log(square.toString());
function anonymous(x
/**/) {
return x*x
}
 
square(4)
→ 16

البته به یاد داشته باشید که شما نباید از این شیوه تابع و حتی حالت eval برای ترجمه و پردازش داده‌های JSON و یا دریافت داده به صورت پویا از یک کلید شئ استفاده کنید.

امیدوارم این آموزش مورد توجه‌تان قرار گرفته باشد و توانسته باشید ویژگی‌های جدیدی را در این نسخه از جاوااسکریپت مشاهده کرده باشید.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
در انتظار ثبت رای

/@arastoo
ارسطو عباسی
کارشناس تولید و بهینه‌سازی محتوا

کارشناس ارشد تولید و بهینه‌سازی محتوا و تکنیکال رایتینگ - https://arastoo.net

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید