آموزش 10 ترفند کاربردی در جاوااسکریپت نسخهٔ ES6 به همراه مثال

آموزش 10 ترفند کاربردی در جاوااسکریپت نسخهٔ ES6 به همراه مثال

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

اولین کسی باشید که به این سؤال پاسخ می‌دهید

در این مقاله سعی شده تا ویژگی‌های کلیدی ES6 به طور مختصر بیان شود به طوری که می‌توان گفت در پایان این آموزش مهارت‌های پایه‌ای کار با این نسخه از زبان محبوب جاوااسکریپت را یاد گرفته و قادر خواهید بود آن‌ها را در پروژه‌های واقعی اِعمال کنید. همچنین در نظر داشته باشید که این آموزش را نمی‌بایست به عنوان یک راهنما یا داکیومنشن JS تلقی کنید؛ بلکه هدف اصلی در اینجا تشویق شما به عمیق‌تر شدن و آشنایی بیشتر با ES6 است (لازم به ذکر است برای تست کدها، علاوه بر کنسول مرورگری همچون گوگل کروم می‌توانید از وب‌سایت ES6Console نیز می‌توانید استفاده نمایید.)

کلمات کلیدی const و let
کلیدواژهٔ const به شما امکان تعریف کانستَنت‌ها را می‌دهد (خودِ این کلمه کوتاه‌شدۀ Constant به معنای «ثابت» است.) و با let هم می‌توان دست به تعریف متغیر زد اما ممکن است این سؤال برایتان پیش بیاید که مگر پیش از این در زبان جاوااسکریپت امکان تعریف متغیر را نداشتیم؟

درست است اما متغیرهایی که با var تعریف می‌شوند اِسکوپ وسیعی دارند اما آنچه از طریق let و const ایجاد می‌گردد، اِسکوپ (محدوده) مشخصی خواهد داشت که همین مسئله احتمال ایجاد باگ را به حداقل می‌رساند:

function f() {
  var x = 1
  let y = 2
  const z = 3
  {
    var x = 100
    let y = 200
    const z = 300
    console.log('x in block scope is', x)
    console.log('y in block scope is', y)
    console.log('z in block scope is', z)
  }
  console.log('x outside of block scope is', x)
  console.log('y outside of block scope is', y)
  console.log('z outside of block scope is', z)
}

f()

به عنوان خروجی اسکریپت فوق داریم:

x in block scope is 100 
y in block scope is 200 
z in block scope is 300 
x outside of block scope is 100 
y outside of block scope is 2 
z outside of block scope is 3 

به طور کلی، مهم‌ترین تفاوت بین متغیرهایی که با let و const تعریف می‌شوند با متغیرهایی که با var تعریف می‌شوند قابلیت دسترسی آن‌ها است. به عبارت دیگر، متغیرهایی که با let و const تعریف گردند تنها در داخل بلوک‌هایی که تعریف شده‌اند اعتبار دارند (در جاوااسکریپت یک بلوک از کد با علائم آکولاد مشخص می‌شود. به طور مثال، در کد بالا می‌بینید که x در بیرون از بلوک دارای مقدار 1 است و در درون بلوک مقداری معادل با 100 دارد و وقتی می‌خواهیم x را چه در داخل بلوک چه در بیرون بلوک در کنسول نشان دهیم، عدد 100 را نمایش می‌دهد در حالی که مقدار متغیرهای تعریف شده توسط let و const در داخل و بیرون بلوک با هم تفاوت دارند.)

هِلپِرهای مرتبط با آرایه‌
یکسری هِلپِر فانکشن جدید در ES6 عرضه شده است که کار با آرایه‌های جاوااسکریپت را در بسیاری از موارد آسان می‌کنند. تا به حال چندین بار برایتان پیش آمده که بخواهید کارهایی همچون فیلتر کردن، چک‌ کردن همهٔ اِلِمان‌های آرایه مطابق یک شرط و یا تغییر اِلِمان‌ها را انجام دهید؟ در ES6 به فیچرهایی کاربردی از این زبان دسترسی دارید که این کارها را برایتان انجام می‌دهد. برای مثال، در ادامه چند نمونه از این هِلپِرها را معرفی کرده‌ایم:

()forEech
خروجی این فانکشن روی هر اِلِمان از آرایه اِعمال می‌شود. به عبارت دیگر، هر اِلِمان از آرایه را به عنوان یک مقدار یا آرگومان پاس می‌دهد:

var colors = ['red', 'green', 'blue']

function print(val) {
  console.log(val)
}

colors.forEach(print)

به عنوان خروجی داریم:

red 
green 
blue

برای درک بهتر این موضوع، فرض کنید هر فانکشن مانند یک کارخانه می‌ماند و وقتی ورودی‌ها به این کارخانه وارد می‌شوند، فانکشن ()forEach به جای اینکه روی کل ورودی به یک‌باره اِعمال شود، روی تک‌تک ورودی‌ها اِعمال می‌گردد. در مثال بالا، آرایهٔ colors دارای مقادیر green ،red و blue است و وقتی فانکشن ()print استفاده شود، کل خانه‌های این آرایه را به یک‌باره و با هم نشان می‌دهد یعنی خروجی به صورت پیوسته red green blue را نشان خواهد داد اما وقتی فانکشن ()forEach استفاده شود، این بدان معنا است که فانکشن ()print برای هر اِلِمان اِعمال می‌گردد و بدین ترتیب فانکشن ()print به طور جداگانه یک‌ بار برای red، یک‌ بار برای green و یک‌ بار هم برای blue اجرا خواهد گشت.

()map
این فانکشن آرایه‌‌ای جدید با همان اِلِمان‌ها ایجاد می‌کند به طوری که فانکشن ()map فقط هر یک از اِلِمان‌های آرایه را به چیز دیگری تغییر می‌دهد:

var colors = ['red', 'green', 'blue']

function capitalize(val) {
    return val.toUpperCase()
}

var capitalizedColors = colors.map(capitalize)

console.log(capitalizedColors)

به عنوان خروجی داریم:

["RED","GREEN","BLUE"] 

در مثال بالا آرایهٔ colors دارای مقادیر green ،red و blue است که با حروف کوچک نوشته شده‌اند اما با استفاده از ()map گفته‌ایم که این سه رنگ با حروف بزرگ نوشته شوند؛ یعنی فقط مقادیر آرایه‌ را تغییر داده است و از حروف کوچک به حروف بزرگ تبدیل کرده است.

()filter
این فانکشن یک آرایهٔ جدید شامل بخشی از آرایه‌ٔ اصلی را می‌سازد. به عبارت دیگر، یک آرایۀ جدید حاوی زیرمجموعه‌ای از آرایه‌ اصلی ایجاد می‌کند:

var values = [1, 60, 34, 30, 20, 5]

function lessThan20(val) {
    return val < 20
}

var valuesLessThan20 = values.filter(lessThan20)

console.log(valuesLessThan20)

به عنوان خروجی داریم:

[1,5]

در کد بالا، آرایهٔ value شامل تعدادی اعداد است که در ادامه خواسته شده تا اعداد کمتر از 20 را نشان دهد. به عبارتی دیگر، می‌خواهیم این اعداد را از یک فیلتر، که اینجا اعداد کمتر از 20 است، عبور دهیم.

()find
این فانکشن اولین اِلِمانی که در شرط قبول ‌شود را پیدا کرده و در پایان خروجی true یا false را برمی‌گرداند:

var people = [
  {name: 'Jack', age: 50},
  {name: 'Michael', age: 9}, 
  {name: 'John', age: 40}, 
  {name: 'Ann', age: 19}, 
  {name: 'Elisabeth', age: 16}
]

function teenager(person) {
    return person.age > 10 && person.age < 20
}

var firstTeenager = people.find(teenager)

console.log('First found teenager:', firstTeenager.name)

به عنوان خروجی داریم:

First found teenager: Ann 

در مثال بالا، آرایهٔ people دارای نام و سن افراد است و در ادامه خواسته شده افراد نوجوان را نشان دهد و گفته شده که افراد نوجوان کسانی هستند که سن بیش از 10 و کمتر از 20 دارند. با استفاده از ()find اولین نوجوان، که Ann است، را برمی‌گرداند و با استفاده از ()console.log در خروجی نشان می‌دهد.

()every
این فانکشن هر اِلِمان از آرایه را چک می‌کند تا ببیند از تستی که توسط فانکشن ارائه شده است عبور می‌کند یا خیر و در پایان true یا false را برمی‌گرداند:

var people = [
  {name: 'Jack', age: 50},
  {name: 'Michael', age: 9}, 
  {name: 'John', age: 40}, 
  {name: 'Ann', age: 19}, 
  {name: 'Elisabeth', age: 16}
]

function teenager(person) {
    return person.age > 10 && person.age < 20
}

var everyoneIsTeenager = people.every(teenager)

console.log('Everyone is teenager: ', everyoneIsTeenager)

به عنوان خروجی داریم:

Everyone is teenager:  false 

کد بالا هم مانند مثال قبلی است با این تفاوت که خواسته شده تا تست روی هر اِلِمان از آرایه اِعمال شود. بدین منظور، برای فردی با نام Jack چون سنش در محدودهٔ سن نوجوان که از قبل تعریف شده قرار نگرفته است، false را برگردانده است. به عبارتی،‌ فرق ()find با ()every در اینجا است که ()find اولین اِلِمانی که تست در آن صادق باشد را نشان می‌دهد اما ()every روی هر اِلِمان اِعمال می‌گردد.

()some
این فانکشن چک می‌کند که آیا برخی از اِلِمان‌های آرایه توسط فانکشن ارائه شده قبول می‌شوند یا خیر که در غیر این صورت مقدار false را ریترن می‌کند:

var people = [
  {name: 'Jack', age: 50},
  {name: 'Michael', age: 9}, 
  {name: 'John', age: 40}, 
  {name: 'Ann', age: 19}, 
  {name: 'Elisabeth', age: 16}
]

function teenager(person) {
    return person.age > 10 && person.age < 20
}

var thereAreTeenagers = people.some(teenager)

console.log('There are teenagers:', thereAreTeenagers)

به عنوان خروجی داریم:

There are teenagers: true 

تا اینجا به فانکشن‌هایی اشاره شد که چک می‌کردند آیا مقادیر آرایه در تست قبول می‌شوند یا خیر اما فانکشن ()some دنبال مقادیری می‌گردد که درون تست قبول نمی‌شوند. در مثال بالا، Jack در محدودهٔ سنی نوجوان‌ها قرار نمی‌گیرد پس مورد قبول نیست و عبارت true را برمی‌گرداند.

()reduce
زمانی از ()reduce استفاده می‌کنیم که یک آرایه داریم و می‌خواهیم تمام مقادیرش را جمع کنیم. در اینجا به جای اینکه از حلقه برای جمع مقادیر استفاده کنیم، از فانکشن ()reduce استفاده می‌کنیم:

var array = [1, 2, 3, 4]

function sum(acc, value) {
  return acc + value
}

function product(acc, value) {
  return acc * value
}

var sumOfArrayElements = array.reduce(sum, 0)
var productOfArrayElements = array.reduce(product, 1)

console.log('Sum of', array, 'is', sumOfArrayElements)
console.log('Product of', array, 'is', productOfArrayElements)

به عنوان خروجی داریم:

Sum of [1,2,3,4] is 10 
Product of [1,2,3,4] is 24 

در این کد، آرایه‌ای داریم تحت عنوان array که حاوی تعدادی عدد است. acc چپ‌ترین عدد یعنی 1 است و value عدد سمت راست acc است (یعنی عدد 2) که برای جمع کردن مقادیر هم دستور داده شده که هر بار مقدار value را با acc جمع شود که برای همین منظور برای بار اول 1 با 2 جمع می‌شود و حاصل 3 می‌شود. value یک عدد به سمت راست حرکت می‌کند و روی 3 قرار می‌گیرد و این بار 3 را با 3 جمع می‌کند و حاصل 6 را در acc ذخیره می‌کند و برای آخرین عدد، 4 را با 6 جمع کرده و حاصل 10 را نگاه می‌دارد. حال چون دیگر عددی باقی نمانده، حاصل 10 را که ذخیره کرده بود نشان می‌دهد.

فانکشن‌های <=
اجرای فانکشن‌های بسیار ساده مانند ()sum یا ()product که در مثال‌های قبلی ذکر شد، نیاز به کدهای تکراری زیادی دارد. برای رفع این معضل، می‌توان از مفهومی تحت عنوان Arrow Function استفاده کرد که با استفاده از این نوع فانکشن‌ها می‌توان کدها را کوتاه‌تر نوشت. به عنوان نمونه، مثال قبلی که در دوازده خط نوشته شده بود، در کد زیر با هفت خط نوشته شده است:

var array = [1, 2, 3, 4]

const sum = (acc, value) => acc + value
const product = (acc, value) => acc * value

var sumOfArrayElements = array.reduce(sum, 0)
var productOfArrayElements = array.reduce(product, 1)

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

var array = [1, 2, 3, 4]

var sumOfArrayElements = array.reduce((acc, value) => acc + value, 0)
var productOfArrayElements = array.reduce((acc, value) => acc * value, 1)

نیاز به توضیح است که Arrow Function همچنین می‌تواند پیچیده بوده و خطوط کد زیادی را شامل گردد:

var array = [1, 2, 3, 4]

const sum = (acc, value) => {
  const result = acc + value
  console.log(acc, ' plus ', value, ' is ', result)
  return result
}

var sumOfArrayElements = array.reduce(sum, 0)

کلاس‌ها
دولوپرهایی که تجربهٔ کدنویسی با سایر زبان‌های شیئ‌گرا مثل جاوا، پی‌اچ‌پی، سی‌شارپ و غیره را دارند و شروع به کدنویسی با زبان جاوااسکریپت می‌کنند، مسلماً خلاء نبود کلاس‌ها را احساس کرده‌اند و همین مسئله منجر به این گشت تا توسعه‌دهندگان زبان محبوب جاوااسکریپت مفهوم کلاس را در نسخهٔ ES6 این زبان بگنجانند. در واقع‌، در این نسخه مفهوم وراثت تغییری نکرده بلکه تنها سینتکس بهتری برای ارث‌بری پروتوتایپ‌ها ارائه شده است:

class Point {
    constructor(x, y) {
        this.x = x
        this.y = y
    }

    toString() {
        return '[X=' + this.x + ', Y=' + this.y + ']'
    }
}

class ColorPoint extends Point {
    static default() {
        return new ColorPoint(0, 0, 'black')
    }

    constructor(x, y, color) {
        super(x, y)
        this.color = color
    }

    toString() {
        return '[X=' + this.x + ', Y=' + this.y + ', color=' + this.color + ']'
    }
}

console.log('The first point is ' + new Point(2, 10))
console.log('The second point is ' + new ColorPoint(2, 10, 'green'))
console.log('The default color point is ' + ColorPoint.default())

به عنوان خروجی داریم:

The first point is [X=2, Y=10] 
The second point is [X=2, Y=10, color=green] 
The default color point is [X=0, Y=0, color=black] 

بهبود مفهوم آبجکت‌ لیترال‌
در ES6 مفهوم آبجکت‌ لیترال‌ بهبود یافته‌ است به طوری که از این پس می‌توانیم کارهایی همچون تعریف فیلدها با اختصاص‌ دادن متغیرهایی با همان نام، تعریف فانکشن‌ها و تعریف پِراپِرتی‌های داینامیک (پویا) را بسیار راحت‌تر انجام دهیم:

const color = 'red'
const point = {
  x: 5,
  y: 10,
  color,
  toString() {
    return 'X=' + this.x + ', Y=' + this.y + ', color=' + this.color
  },
  [ 'prop_' + 42 ]: 42
}

console.log('The point is ' + point)
console.log('The dynamic property is ' + point.prop_42)

به عنوان خروجی داریم:

The point is X=5, Y=10, color=red 
The dynamic property is 42 

اِسترینگ تِمپلیت 
کمتر دولوپری را می‌توان یافت که علاقه داشته باشد با وصل کردن متغیرهای مختلف، استرینگ‌های طولانی ایجاد کند و این در حالی است که خیلی از دولوپرها از خواندن سورس‌کدهایی که به این‌گونه نوشته شده‌اند هم تنفر دارند. ES6 تِمپلیت‌های بسیار ساده‌ای برای استفاده از استرینگ‌ها به همراه Placeholder برای متغیرها معرفی کرده است:

function hello(firstName, lastName) {
  return `Good morning ${firstName} ${lastName}! 
How are you?`
}

console.log(hello('Jan', 'Kowalski'))

به عنوان خروجی داریم:

Good morning Jan Kowalski! 
How are you? 

آرگومان‌های دیفالت فانکشن‌ها
اگر تمایلی نداشته باشید تا همهٔ پارامترهای ممکن فانکشن را ذکر کنید، می‌توانید در نسخهٔ ES6 از مقادیر دیفالت استفاده کنید:

function sort(arr = [], direction = 'ascending') {
  console.log('I\'m going to sort the array', arr, direction)
}

sort([1, 2, 3])
sort([1, 2, 3], 'descending')

به عنوان خروجی داریم:

I'm going to sort the array [1,2,3] ascending 
I'm going to sort the array [1,2,3] descending 

اپراتور ...
علائم ... می‌توانند جهت ایجاد یک کپی از روی چیزی همچون یک آرایه مورد استفاده قرار گیرد:

var array = ['red', 'blue', 'green']
var copyOfArray = [...array]

console.log('Copy of', array, 'is', copyOfArray)
console.log('Are', array, 'and', copyOfArray, 'same?', array === copyOfArray)

به عنوان خروجی داریم:

Copy of ["red","blue","green"] is ["red","blue","green"] 
Are ["red","blue","green"] and ["red","blue","green"] same? false 

به منظور ادغام آرایه‌ها هم خواهیم داشت:

var defaultColors = ['red', 'blue', 'green']
var userDefinedColors = ['yellow', 'orange']

var mergedColors = [...defaultColors, ...userDefinedColors]

console.log('Merged colors', mergedColors)

به عنوان خروجی داریم:

Merged colors ["red","blue","green","yellow","orange"] 

اگر بخواهیم چند پارامتر اولیهٔ فانکشنی را به متغیرها متصل کنیم و پارامترهای دیگر که باقی می‌مانند را به عنوان آرایه به متغیرهای تکی متصل کنیم، در ES6 به راحتی چنین امکانی فراهم شده است:

function printColors(first, second, third, ...others) {
  console.log('Top three colors are ' + first + ', ' + second + ' and ' + third + '. Others are: ' + others)
}
printColors('yellow', 'blue', 'orange', 'white', 'black')

به عنوان خروجی داریم:

Top three colors are yellow, blue and orange. Others are: white,black 

در کد بالا، پارمترهای اول، دوم و سوم که به ترتیب blue ،yellow و orange می‌باشند را به متغیرها متصل کرده و هرچه باقی‌ مانده را به عنوان یک آرایه در متغیرهای تکی گنجانده‌ایم.

Destructuring
این مفهوم می‌تواند اِلِمان‌های درخواستی از آرایه را استخراج کند (بیرون بکشد) و به متغیرها اختصاص دهد:

function printFirstAndSecondElement([first, second]) {
    console.log('First element is ' + first + ', second is ' + second)
}

function printSecondAndFourthElement([, second, , fourth]) {
    console.log('Second element is ' + second + ', fourth is ' + fourth)
}

var array = [1, 2, 3, 4, 5]

printFirstAndSecondElement(array)
printSecondAndFourthElement(array)

به عنوان خروجی داریم:

First element is 1, second is 2 
Second element is 2, fourth is 4 

همچنین این مفهوم می‌تواند پِراپِرتی‌های درخواستی از آبجکت را استخراج کند و آن‌ها را به متغیرهایی با اسامی مشابه به عنوان پِراپِرتی اختصاص دهد:

function printBasicInfo({firstName, secondName, profession}) {
	console.log(firstName + ' ' + secondName + ' - ' + profession)
}

var person = {
  firstName: 'John',
  secondName: 'Smith',
  age: 33,
  children: 3,
  profession: 'teacher'
}

printBasicInfo(person)

به عنوان خروجی داریم:

John Smith - teacher 

Promise
در برخی از پروژه‌ها نیاز است تا یکسری از عملیات‌ به ترتیب انجام گیرند (مثلاً تَسک اول انجام شود و پس از آن تَسک دوم و الی‌آخر) اما در بسیاری از پروژه‌ها مدیریت این دست کارها به سرعت از کنترل خارج شده و خواندن سورس‌کد سخت می‌شود که برای حل مشکل فراخوانی کدهای هم‌زمان در جاوااسکریپت، از مفهومی تحت عنوان Promise استفاده می‌شود.

Promise یکسری قرارداد است که نتایج تَسک‌هایی که اجرای آن‌ها زمان زیادی طول می‌کشد را در آینده نشان می‌دهد که دارای دو نوع ورودی است: ورودی اول برای نتایج و ورودی دوم برای خطاهای احتمالی که برای به دست آوردن نتایج باید یک فانکشن به اصطلاح Callback را به عنوان پارامتر then قرار دهید و برای مدیریت خطاها هم باید فانکشن Callback را به عنوان پارامتر catch قرار داد:

function asyncFunc() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
          const result = Math.random();
          result > 0.5 ? resolve(result) : reject('Oppps....I cannot calculate')
        }, 1)
    });
}

for (let i=0; i<10; i++) {
	asyncFunc()
    	.then(result => console.log('Result is: ' + result))
    	.catch(result => console.log('Error: ' + result))
}

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

Result is: 0.7930997430022211 
Error: Oppps....I cannot calculate 
Result is: 0.6412258210597288 
Result is: 0.7890325910244533 
Error: Oppps....I cannot calculate 
Error: Oppps....I cannot calculate 
Result is: 0.8619834683310168 
Error: Oppps....I cannot calculate 
Error: Oppps....I cannot calculate 
Result is: 0.8258410427354488 

چه ویژگی‌های دیگری از ES6 سراغ دارید که کدنویسی دولوپرهای جاوااسکریپت را به مراتب راحت‌تر کرده است؟ نظرات، دیدگاه‌ها و تجربیات خود را با سایر کاربران سکان آکادمی به اشتراک بگذارید.

منبع