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

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

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

اگرچه ویژگی‌های ES6 خیلی جدید نیستند، اما بسیاری از دولوپرهای جاوااسکریپت هنوز با ویژگی‌های آن به خوبی آشنا نبوده که به نظر می‌رسد دلیل اصلی احتمالاً پشتیبانی نه چندان کامل این نسخه از زبان جی‌اس در مرورگرها باشد. در حال حاضر، ES6 سنی بیش از 2 سال دارد و بسیاری از مرورگرهای وب به خوبی با آن کار می‌کنند؛ حتی اگر شما (یا مخاطبانتان) از آخرین ورژن‌ مرورگرهای وب هم استفاده نکنید، می‌توانید در طی روند ساخت اپلیکیشن از Transpilerهایی مثل Babel که سورس‌های ES6 را به سورس‌های ES5 تبدیل می‌کنند، استفاده کنید (این یعنی زمان برداشتن یک قدم به جلو و یادگیری ES6 است).

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

کلمات کلیدی const و let
const به شما امکان تعریف ثابت‌ها را می‌دهد (خودِ کلمۀ کلیدی const کوتاه‌شدۀ کلمۀ Constant به معنای «ثابت» است.) let هم به شما توانایی تعریف متغیرها را می‌دهد. این فیچر عالی است، اما ممکن است این سؤال برایتان پیش بیاید که مگر پیش از این در زبان جاوااسکریپت امکان تعریف متغیر را نداشتیم؟ درست است اما متغیرهایی که با var تعریف می‌شوند، دسترسی وسیعی دارند؛ این بدان معنا است که از یک متغیر می‌توان قبل از اینکه تعریف شود، استفاده کرد. اما آنچه از طریق let و const ایجاد می‌گردد، محدودهٔ (Scope) مشخصی دارند و هرگز نمی‌توان قبل از آنکه تعریف شوند، از آنها استفاده کرد:

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 تعریف گردند، تنها در داخل Blockیی که تعریف شده‌اند اعتبار دارند (در جاوااسکریپت یک Block از کد با علائم { } مشخص می‌شود؛ مثلاً در کد بالا می‌بینید که x در بیرون از بلوک دارای مقدار 1 است و در درون بلوک مقداری معادل با 100 دارد و وقتی می‌خواهیم x را چه در داخل بلوک چه در بیرون بلوک در کنسول نشان دهیم، عدد 100 را نمایش می‌دهد؛ در حالی که مقدار متغیرهای تعریف شده توسط let و const در داخل و بیرون بلوک با هم تفاوت دارند.)

هِلپِر استفاده از آرایه‌ها
یک هِلپِر فانکشن جدید و جالبی در ES6 عرضه شده است که کار با آرایه‌های جاوااسکریپت را در بسیاری از موارد آسان می‌کند. تا به حال چندین بار برایتان پیش آمده که کارهای منطقی مانند فیلتر کردن، چک‌ کردن همهٔ اِلِمان‌های مطابق شرط if یا تغییر اِلِمان‌ها را انجام دهید؟ احتمالاً خیلی زیاد این اتفاق برایتان پیش آمده باشد و این در حالی است که از این پس در 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 استفاده شود، کل خانه‌های آرایهٔ colors را به یک‌باره و با هم نشان می‌دهد یعنی خروجی به صورت پیوسته 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 است (یعنی عدد ۲). برای جمع کردن مقادیر هم دستور داده شده که هر بار مقدار value را با acc جمع شود؛ برای همین، برای بار اول 1 با 2 جمع می‌شود و حاصل 3 می‌شود. value یک عدد به سمت راست حرکت می‌کند و روی 3 قرار می‌گیرد و این بار 3 را با 3 جمع می‌کند و حاصل 6 را در acc ذخیره می‌کند و برای آخرین عدد، 4 را با 6 جمع کرده و حاصل 10 را نگاه می‌دارد. حال چون دیگر عددی باقی نمانده، حاصل 10 را که ذخیره کرده بود نشان می‌دهد.

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

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)

کلاس‌ها
دولوپرهایی که تجربهٔ کدنویسی با سایر زبان‌های OOP مثل جاوا، پی‌اچ‌پی، سی‌شارپ و غیره را دارند و شروع به کدنویسی با زبان جاوااسکریپت می‌کنند، مسلماً خلاء نبود کلاس‌ها را احساس کرده‌اند! و همین مسئله منجر به این گشت تا توسعه‌دهندگان زبان محبوب جاوااسکریپت مفهوم Class را در نسخهٔ 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 یکسری قرارداد است و نتایج تسک‌هایی که طولانی مدت طول می‌کشند را در آینده نشان می‌دهند. 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 سراغ دارید که کدنویسی دولوپرهای جاوااسکریپت را به مراتب راحت‌تر کرده است؟ نظرات، دیدگاه‌ها و تجربیات خود را با ما و سایر کاربران سکان آکادمی به اشتراک بگذارید.

منبع


سحر شاکر