وبلاگ

معرفی فیچرهای رایج اکما اسکریپت 6 (ES6)


رقیه اباذری رقیه اباذری

جاوا اسکریپت در سال‌های گذشته خیلی پیشرفت کرده است. اگر سال 2017 جاوا اسکریپت یاد گرفته‌اید و هنوز اصطلاح ES6 به گوش شما ناآشناست و با آن کار نکرده‌اید، باید گفت، یک راه راحت و سریع برای خواندن و نوشتن کدهای جاوا اسکریپت را از دست داده‌اید. اگر در جاوا اسکریپت حرفه‌ای نیستید، نگران نباشید. برای بهره بردن از فیچرهای ES6 نیازی نیست که جاوا اسکریپت را خیلی حرفه‌ای بلد باشید. در این مقاله قصد داریم تا با 8 فیچر اکما اسکریپت 6 آشنا شویم که روزانه مورد استفاده دولوپرها قرار می‌گیرند.

لیست فیچرهای ES6

ابتدا به خاطر داشته باشید که اکمااسکریپت 6، آپدیتی بزرگ برای جاوا اسکریپت است.

  • Arrows
  • Classes
  • Enhanced object literals
  • Template strings
  • Destructuring
  • Default + rest + spread
  • Let + const
  • Iterators + for…of
  • Generators
  • Unicode
  • Modules
  • Module loaders
  • Map + set + weakmap + weakset
  • Proxies
  • Symbols
  • Subclassable built-ins
  • Promises
  • Math + number + string + array + object apis
  • Binary and octal literals
  • Reflect api
  • Tail calls

اجازه ندهید این اصطلاحات و لیست طولانی شما رو بترساند و از یاد گرفتن ES6 منصرفتان کند. قرار نیست بلافاصله همه چیز رو کامل و دقیق یاد بگیرید. ما در کنار شما هستیم تا 8 مورد از این لیست رو با هم بررسی کنیم.

  1. Let and const
  2. Arrow functions
  3. Default parameters
  4. Destructuring
  5. Rest parameter and spread operator
  6. Enhanced object literals
  7. Template literals
  8. Promises

و حالا اصل مطلب و بررسی آیتم اول از لیست:

کلمه کلیدی let و کلمه کلیدی const

در نسخه قدیمی‌تر جاوا اسکریپت یا همان ES5، متغیرها با استفاده از کلمه کلیدی var تعریف می‌شدند. در حال حاضر یعنی در ES6، دیگر خبری از var نیست. از const و let استفاده می‌کنیم.
اجازه بدید همین ابتدای کار فرق بین دو کلیدواژه var و let توضیح بدهیم تا متوجه شوید چرا let و const بهتر هستند.

مقایسه Let و var

خب اول برویم سراغ var که اصطلاح نام آشناتری است.
اول از همه می‌توانیم متغیرها را با کلید واژه var مشخص کنیم. بعد از این کار این کلمه کلیدی می‌تواند در هر بخشی از اسکوپ جاری مورد استفاده قرار بگیرد. (scope محدوده‌ای است که متغییر در آن قابل استفاده است، برای مثال اگر متغییر داخل یک فانکشن تعریف شود، محدوده استفاده از آن همان فانکشن است و خارج از فانکشن تعریف نشده است.)

var me = 'Zell'
console.log(me) // Zell

در قطعه کد بالا me به عنوان متغیر گلوبال تعریف شده است. با توجه به تعریفی که از var داشتم، متغیر گلوبال me می‌تواند در در یک فانکشن یا تابع به شکل زیر مورد استفاده قرار بگیرد.

var me = 'Zell'
function sayMe () {
  console.log(me)
}

sayMe() // Zell

با این حال عکس این موضوع امکان پذیر نیست.یعنی ما نمی‌توانیم یک متغیر را در یک فانکشن تعریف کنیم و بیرون از فانکشن از آن استفاده کنیم.

function sayMe() {
  var me = 'Zell'
  console.log(me)
}

sayMe() // Zell
console.log(me) // Uncaught ReferenceError: me is not defined

بنابراین ما می‌توانیم بگوییم که var یک function-scoped است. یعنی متغیری که با کلمه کلیدی var تعریف شود به فانکشن محدود می‌شود. به بیانی دیگر هر زمان که یک متغیر با استفاده از کلمه کلیدی var در یک تابع ساخته شده باشد، تنها داخل فانکشن وجود خواهد داشت.

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

var me = 'Zell' // global scope

function sayMe () {
  var me = 'Sleepy head' // local scope
  console.log(me)
}

sayMe() // Sleepy head
console.log(me) // Zell

let مقابل var است. یعنی block-scoped یا محدود شده به بلاک است. به این معنا که هر جایی یک متغیر با let ایجاد شود، فقط و فقط داخل بلاک خودش وجود دارد.

خب اولین سوالی که در این قسمت باید برای شما پیش بیاید این است که بلاک چیست و چرا با رسم شکل؟!!

یک بلاک در جاوا اسکریپت تمام آن چیزی است که در براکت گنجانده شده است. کدی که در ادامه ملاحظه می‌کنید شامل مثال‌هایی از بلاک است.

{
  // new scope block
}

if (true) {
  // new scope block
}

while (true) {
  // new scope block
}

function () {
  // new block scope
}

تفاوت بین متغیرهای block-scope و function-scoped زیاد است.
وقتی از یک متغیر function-scoped استفاده می‌کنید، ممکن است به طور تصادفی متغیر را بدون قصد انجام این کار بازنویسی کنید. یک مثال داریم:

var me = 'Zell'

if (true) {
  var me = 'Sleepy head'
}

console.log(me) // 'Sleepy head'

در این مثال مشاهده می‌کنید که me به Sleepy head تبدیل شده است. این مثال مشکلی برای شما ایجاد نخواهد کرد زیرا احتمالاً شما متغیرهایی را با همین نام دوباره تعریف نمی‌کنید.

اما ممکن است کسی که با var در یک حلقه for کار کند، در رابطه با حوزه و محدوده متغیرها دچار کمی سردرگمی و ابهام شود.

کد زیر را در نظر بگیرید که چهار بار متغیر i را لاگ می‌کند، سپس i دوباره با یک فانکشن setTimeout لاگ می‌شود.

for (var i = 1; i < 5; i++) {
  console.log(i)
  setTimeout(function () {
    console.log(i)
  }, 1000)
};

تصویر زیر خروجی قطعه کد بالا است.

متغیر i به عنوان 5 ، چهار بار در تابع timeout بار لاگ شد.
متغیر i به عنوان 5 ، چهار بار در تابع timeout بار لاگ شد.

حالا سوال اینجاست که متغیر i چطور 4 بار در فانکشن timeout معادل 5 تبدیل شد. خب روشن است که متغیری که با var تعریف شده، function-scoped است. قبل از اینکه فانکشن timeout اجرا شود، مقدار i برابر با 4 شد.

برای گرفتن مقدار صحیح i، در فانکشن setTimeout که بعدا اجرا می‌شود. باید یک فانکشن دیگر ایجاد کنیم. نام این فانکشن رو logLater می‌گذاریم. هدف از ساخت این فانکشن اطمینان از این است که قبل از اجرای setTimeout، مقدار i به وسیله حلقه for تغییر نمی‌کند.

در واقع، چون متغییر تعریف شده با var در فانکشن logLater قابل تغییر نیست، در دورهای بعدی حلقه for مقدار i از تغییرات ناخواسته i در فانکشن setTimeout در امان خواهد بود.

function logLater (i) {
  setTimeout(function () {
    console.log(i)
  })
}

for (var i = 1; i < 5; i++) {
  console.log(i)
  logLater(i)
};
i الان درست و صحیح اجرا شده است. 1 و 2 و 3 و 4
i الان درست و صحیح اجرا شده است. 1 و 2 و 3 و 4

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

خبر خوب این که اتفاق عجیب function-scoped مانند چیزی که در مثال حلقه for دیدیم با استفاده از دستور let اتفاق نخواهد افتاد.

مثال timeout که بالاتر نوشته بودیم را می‌توان به شکل زیر هم نوشت. این قطعه کد به درستی کار می‌کند و نیازی به نوشته هیچ فانکشن اضافه‌ای هم نیست.

for (let i = 1; i < 5; i++) {
  console.log(i)
  setTimeout(function () {
    console.log(i)
  }, 1000)
};
loop right
i به درستی اجرا شده است. 1 و 2 و 3 و 4

همانطور که مشاهده می‌کنید متغیرهای block-scoped با حذف مشکلات پیش‌بینی نشده متداول متغییرهای function-scoped، کدنویسی را خیلی راحت کرده‌اند.
در نهایت برای اینکه از کدنویسی لذت بیشتری ببرید، پیشنهاد می‌کنیم از این به بعد هر جا که نیاز داشتید کد جاوا اسکریپت بزنید، از let بالای var استفاده کنید.

حالا که می‌دانیم let چه کاری انجام می‌دهد، اجازه دهید به تفاوت بین let و const بپردازیم.

Let در مقابل const

const هم مانند let یک blocked-scoped هست. تفاوت در این است که مقادیر const بعد از تعریف، قابل تغییر و تعریف مجدد نیست.

const name = 'Zell'
name = 'Sleepy head' // TypeError: Assignment to constant variable.

let name1 = 'Zell'
name1 = 'Sleepy head'
console.log(name1) // 'Sleepy head'

این ویژگی const برای متغیرهایی خوب است که قرار نیست مقادیر آنها تغییر کند. در واقع به این اسامی که به خانه‌ای از حافظه اشاره دارند و حاوی یک مقدار هستند، اگر قابل تغییر باشند متغییر و اگر قابل تغییر نباشند، ثابت می‌گویند. در اینجا name یک ثابت و name1 یک متغییر است.

یک مثال ملموس‌تر بزنیم. فرض کنید دکمه‌ای در وب سایت ما وجود دارد که قرار است یک مدال را باز کند. می‌دانیم که این دکمه همیشه یک دکمه است و هیچ تغییری نخواهد داشت. بنابراین بهترین روش استفاده کردن از const است.

const modalLauncher = document.querySelector('.jsModalLauncher')
در حالت کلی هر جایی که متغیر تعریف می‌کنیم، ترجیح‌مان این است که در حد ممکن از const به جای let استفاده کنیم. چون پیغام "نمی‌توان مقدار متغیرها را تغییر داد" دریافت می‌کنیم. برای تمامی موقعیت‌های دیگر از let استفاده می‌کنیم.

فانکشن arrow

فانکشن arrow با نماد (=>) مشخص می‌شود. این فانکشن، کدنویسی برای فانکشن‌های بی نام (anonymous functions) را کوتاه می‌کند. در واقع می‌توان گفت باعث مختصرنویسی می‌شود. هر جایی که کلمه کلیدی function استفاده شده باشد می‌توان از آن‌ها استفاده کرد. مانند مثال زیر :

let array = [1,7,98,5,4,2]

// ES5 way
var moreThan20 = array.filter(function (num) {
  return num > 20
})

// ES6 way
let moreThan20 = array.filter(num => num > 20)

فانکشن arrow خیلی محبوب است. نظر شما را به چند مزیت این فانکشن جلب می‌کنیم.
1- این فانکشن کدها را کوتاه‌تر می‌کند. مسلما کوتاه‌تر شدن کدها یعنی کمتر شدن خطا و سریع‌تر شدن فرآیند کدنویسی.
2- این فانکشن به شما کمک می‌کنند تا کدی بنویسید که درک آن راحت‌تر باشد.
برویم سراغ سروکله زدن با Arrow Functions تا کامل آن‌ها را یاد بگیریم.

مفاهیم مربوط arrow functions

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

function namedFunction() {
  // Do something
}

// using the function
namedFunction()

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

var namedFunction = function() {
  // Do something
}

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

// Using an anonymous function in a callback
button.addEventListener('click', function() {
  // Do something
})

از آن جایی که فانکشن‌های arrow در اکمااسکریپت 6، شکل کوتاه شده فانکشن‌های بی نام هستند، همیشه می‌توانید فانکشن‌های arrow را جایگزین فانکشن‌های بی نام کنید.

// Normal Function
const namedFunction = function (arg1, arg2) { /* do your stuff */}

// Arrow Function
const namedFunction2 = (arg1, arg2) => {/* do your stuff */}

// Normal function in a callback
button.addEventListener('click', function () {
  // Do something
})

// Arrow function in a callback
button.addEventListener('click', () => {
  // Do something
})

در این مثال ساده شدن کد کاملا قابل مشاهده است. کلمه کلیدی function را حذف می‌کنید و کمی آن طرف‌تر => را جایگزین آن می‌کنید.
اما کارهای بزرگتر دیگری هم می‌توانیم با فانکشن arrow انجام بدهیم. فقط قرار نیست function را با => جایگزین کنیم.
نحوه عملکرد این فانکشن با دو فاکتور می‌تواند تغییر کند:

  1. تعداد آرگومنت‌های ضروری
  1. اینکه آیا می‌خواهید یک ایمپلیسیت ریترن (implicit return) داشته باشید.

فاکتور اول تعداد آرگومنت های داده شده به فانکشن است. اگر فقط یک آرگومنت را داده باشید، می‌توانید پرانتزهای آرگومنت را بردارید. حالا اگر هیچ آرگومنتی نیاز نداشته باشید چه؟!! در این صورت می‌توانید پرانتزها را با آندراسکور (_) جدا کنید.
چند خط کدی که در ادامه می‌بینید در فانکشن arrow معتبر است.

const zeroArgs = () => {/* do something */}
const zeroWithUnderscore = _ => {/* do something */}
const oneArg = arg1 => {/* do something */}
const oneArgWithParenthesis = (arg1) => {/* do something */}
const manyArgs = (arg1, arg2) => {/* do something */}

و اما فاکتور دوم. اگر کد فقط یک خط را بگیرد، فانکشن‌های arrow به صورت خودکار کلمه کلیدی return را ایجاد می‌کنند و در یک بلاک قرار نمی‌گیرد.
بنابراین این دو خط معادل هستند:

const sum1 = (num1, num2) => num1 + num2
const sum2 = (num1, num2) => { return num1 + num2 }

اینکه می‌گوییم می‌توانید کدهای کوتاه‌تری بنویسید، همین دو فاکتور هستند.

let array = [1,7,98,5,4,2]

// ES5 way
var moreThan20 = array.filter(function (num) {
  return num > 20
})

// ES6 way
let moreThan20 = array.filter(num => num > 20)

در ادامه باید یکی دیگر از ویژگی فانکشن arrow یعنی the lexical this را توضیح دهیم.

The lexical this

this یک کلمه کلیدی منحصر به فرد است که مقدار آن بستگی به این دارد که کجا و به چه صورت فراخوانی شود. اگر خارج از فانکشن فراخوانی شود، به صورت پیش‌فرض به آبجکت Window در مرورگر اشاره می‌کند.
console.log(this) // Window
به صورت پیش‌فرض به آبجکت Window در مرورگر اشاره می‌کند.
به صورت پیش‌فرض به آبجکت Window در مرورگر اشاره می‌کند.

اگر this در یک فانکشن ساده فراخوانی شود، در یک گلوبال آبجکت ست می‌شود. در مورد مرورگرها this همیشه Window می‌شود.

function hello () {
  console.log(this)
}

hello() // Window

جاوا اسکریپت همیشه داخل یک فانکشن ساده، this را برای آبجکتwindow ست می‌کند.
زمانی که this به عنوان یک object method فراخوانی می‌شود، خودش یک آبجکت محسوب می‌شود.

let o = {
  sayThis: function() {
    console.log(this)
  }
}

o.sayThis() // o
این به آبجکت اشاره دارد، هنگامی که فانکشن در یک آبجکت متد فراخوانی شده است.
این به آبجکت اشاره دارد، هنگامی که فانکشن در یک آبجکت متد فراخوانی شده است.

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

function Person (age) {
  this.age = age
}

let greg = new Person(22)
let thomas = new Person(24)

console.log(greg) // this.age = 22
console.log(thomas) // this.age = 24
این آبجکت به constructed object فراخوانی شده با کلمه کلیدی new یا Object.create() اشاره دارد
این آبجکت به constructed object فراخوانی شده با کلمه کلیدی new یا Object.create() اشاره دارد

زمانی که در یک event listener فراخوانی شود، this به المانی که فایر شده اشاره می‌کند. (اونت لسنر یا EventListener یک فانکشن است که منتظر می‌ماند تا اتفاقی رخ دهد تا عملی را در واکنش به آن اتفاق انجام دهد.)

let button = document.querySelector('button')

button.addEventListener('click', function() {
  console.log(this) // button
})

همانطور که در مثال‌های بالا مشاهده کردید، مقدار this توسط فانکشنی که آن را فراخوانی می‌کند ست می‌شود. هر فانکشن مقدار this مربوط به خودش را تعریف می‌کند.
در فانکشن‌های fat arrow، مهم نیست که this به چه صورت فراخوانی می‌شود، this به مقدار جدید محدود نمی‌شود.
مقدار this همیشه برابر با مقدار کدهای اطراف آن خواهد بود. (به هر حال، lexical به معنی مرتبط با است که می‌توانیم حدس بزنیم چرا lexical this این نام را گرفته).
صد البته که توضیحات کمی گیج کننده بود، پس بهترین راه بررسی با مثال است.

اول از همه اینکه شما هرگز نمی‌خواهید از arrow functions برای اعلام object methods استفاده کنید، چون دیگر نمی‌توانید آبجکت را به this ارجاع دهید.

let o = {
  // Don't do this
  notThis: () => {
    console.log(this) // Window
    this.objectThis() // Uncaught TypeError: this.objectThis is not a function
  },
  // Do this
  objectThis: function () {
    console.log(this) // o
  }
  // Or this, which is a new shorthand
  objectThis2 () {
    console.log(this) // o
  }
}

دوم اینکه ممکن است نخواهید، برای ایجاد کردن event listeners از arrow functions استفاده کنید چون this به عنصری که شما به event listener متصل کرده‌اید، وصل نمی‌شود.
به هر حال همیشه می‌توانید this را با event.currentTarget دریافت کنید.

button.addEventListener('click', function () {
  console.log(this) // button
})

button.addEventListener('click', e => {
  console.log(this) // Window
  console.log(event.currentTarget) // button
})

سوم اینکه ممکن است شما بخواهید از کلمات در جاهایی استفاده کنید که this بدون اینکه شما بخواهید آن‌ها را تغییر دهد.
مثال تابع timeout را در ادامه داریم که شما مجبور نیستید با this یا that یا self سروکله بزنید.

let o = {
  // Old way
  oldDoSthAfterThree: function () {
    let that = this
    setTimeout(function () {
      console.log(this) // Window
      console.log(that) // o
    })
  },
  // Arrow function way
  doSthAfterThree: function () {
    setTimeout(() => {
      console.log(this) // o
    }, 3000)
  }
}

این مورد در مواقعی که شما به اضافه یا حذف کردن یک کلاس بعد از گذشت مدت زمانی نیاز دارید، مفید است.

let o = {
  button: document.querySelector('button')
  endAnimation: function () {
    this.button.classList.add('is-closing')
    setTimeout(() => {
      this.button.classList.remove('is-closing')
      this.button.classList.remove('is-open')
    }, 3000)
  }
}

در نهایت از fat arrow function استفاده کنید تا کدهای شما کوتاه و کوتاه‌تر شود. مانند مثال moreThan20 که بالاتر داشتیم.

let array = [1,7,98,5,4,2]
let moreThan20 = array.filter(num => num > 20)

پارامترهای پیش‌فرض

Default parameters راهی برای تعیین پارامترهای پیش فرض هنگام تعریف فانکشن‌ها می‌دهند. برویم سراغ یک مثال تا ببینید این آیتم چقد مفید است.فانکشنی ایجاد می‌کنیم که نام یک بازیکن از یک تیم را اعلام کند.

اگر شما این تابع را در ES5 تعریف کنید، شبیه قطعه کد پایین خواهد بود:

function announcePlayer (firstName, lastName, teamName) {
  console.log(firstName + ' ' + lastName + ', ' + teamName)
}

announcePlayer('Stephen', 'Curry', 'Golden State Warriors')
// Stephen Curry, Golden State Warriors

در نگاه اول این کد اوکی هست. ولی اگر ما مجبور باشیم بازیکنی که در هیچ تیمی نباشد چطور؟
اگر teamName را مقداردهی نکنیم، کد فعلی با شکست مواجه می‌شود

announcePlayer('Zell', 'Liew')
// Zell Liew, undefined

من کاملا مطمئنم که undefined ، یک تیم نیست. :)

اگر بازیکن وابسته به هیچ تیمی نباشد، اعلام Zell Liew, unaffiliated بسیار منطقی‌تر از Zell Liew, undefined است. موافقید؟

برای اینکه فانکشن Zell Liew, unaffiliated را اعلام کند، تنها راه، پس کردن استرینگ unaffiliated به عنوان teamName است:

announcePlayer('Zell', 'Liew', 'unaffiliated')
// Zell Liew, unaffiliated

اگر چه این کد درست کار می‌کند. با ریفکتور کردن unaffiliated در announcePlayer به وسیله چک کردن اینکه آیا teamName تعریف شده است، می‌توانیم کد بهتری بنویسیم. (ریفکتور یا refactor به معنی تغییر ساختار کد بدون تغییر عملکرد آن است. در واقع کد ریفکتور شده همان خروجی سابق را دارد، منتها تکمیل یا بهبود یافته).
در ورژن ES5 می‌توانید کد را به شکل زیر ریفکتور کنید:

function announcePlayer (firstName, lastName, teamName) {
  if (!teamName) {
    teamName = 'unaffiliated'
  }
  console.log(firstName + ' ' + lastName + ', ' + teamName)
}

announcePlayer('Zell', 'Liew')
// Zell Liew, unaffiliated

announcePlayer('Stephen', 'Curry', 'Golden State Warriors')
// Stephen Curry, Golden State Warriors

یا اگر می‌خواهید با استفاده از ترنری اپراتورها (ternary operators) کد کمتری بنویسید، می‌توانید فانکشن را به صورت زیر تعریف کنید:

function announcePlayer (firstName, lastName, teamName) {
  var team = teamName ? teamName : 'unaffiliated'
  console.log(firstName + ' ' + lastName + ', ' + team)
}

در اکما اسکریپت 6 با پارامترهای پیش‌فرض می‌توانیم هر جایی که خواستیم پارامتر تعریف کنیم، یک علامت مساوی = اضافه کنیم. اگر ما این کار را انجام دهیم وقتی پارامتری تعریف نشده باشد ES6 به صورت پیش‌فرض آن را مقداردهی می‌کند.
بنابراین طبق کدهای زیر وقتی که teamName تعریف نشده باشد، به صورت پیش‌فرض مقدار unaffiliated برای آن در نظر گرفته می‌شود.

const announcePlayer = (firstName, lastName, teamName = 'unaffiliated') => {
  console.log(firstName + ' ' + lastName + ', ' + teamName)
}

announcePlayer('Zell', 'Liew')
// Zell Liew, unaffiliated

announcePlayer('Stephen', 'Curry', 'Golden State Warriors')
// Stephen Curry, Golden State Warriors

یک مورد دیگر اینکه اگر می‌خواهید مقدار پیش‌فرض را فراخوانی کنید، می‌توانید به صورت دستی مقدار undefined را به آن پس کنید. پس کردن دستی undefined هنگامی که پارامتر پیش‌فرض شما آخرین آرگومان نیست، به شما کمک می‌کند.

announcePlayer('Zell', 'Liew', undefined)
// Zell Liew, unaffiliated

این تمام چیزی بود که شما باید درباره default parameters می‌دانستید. خیلی مختصر و مفید.

Destructuring

Destructuring یک روش راحت برای خروجی گرفتن مقادیر از آرایه ها و آبجکت‌ها است. بین آبجکت‌ها و آرایه‌های destructuring تفاوت‌های جزئی وجود دارد، بنابراین اجازه دهید جداگانه درباره آن‌ها صحبت کنیم.

آبجکت‌های ساختارشکنانه (Destructuring objects)

با فرض اینکه شما آبجکت زیر را دارید:

const Zell = {
  firstName: 'Zell',
  lastName: 'Liew'
}

برای گرفتن firstName و lastName از Zell، شما باید 2 متغیر بسازید بعد مقدار هر متغیر را به خودش اختصاص دهید. شبیه قطعه کد زیر:

let firstName = Zell.firstName // Zell
let lastName = Zell.lastName // Liew

با Destructuring می‌توانید این کار را در یک خط کد انجام دهید.

let { firstName, lastName } = Zell

console.log(firstName) // Zell
console.log(lastName) // Liew

با اضافه کردن براکت ها ({}) زمانی که متغیرها را تعریف می‌کنیم، به جاوا اسکریپت می‌گوییم که متغیرهای مذکور را ایجاد کند. سپس Zell.firstName را به firstName و Zell.lastName را به lastName اختصاص دهد.

// What you write
let { firstName, lastName } = Zell

// ES6 does this automatically
let firstName = Zell.firstName
let lastName = Zell.lastName

حالا اگر نام یک متغیر استفاده شده باشد، نمی‌توانیم دوباره متغیر را دوباره اعلام کنیم. به ویژه اگر از دستور let یا const استفاده کرده باشیم.
قطعه کد زیر با شکست مواجه می‌شود:

let name = 'Zell Liew'
let course = {
  name: 'JS Fundamentals for Frontend Developers'
  // ... other properties
}

let { name } = course // Uncaught SyntaxError: Identifier 'name' has already been declared

اگر شما در شرایطی شبیه بالا قرار گرفتید، می‌توانید هنگام destructuring، متغیرها را با استفاده از کولن (:) تغییر نام دهید.
در مثال زیر یک متغیر با عنوان courseName ایجاد شده است و course.name به آن اختصاص داده شده است.

let { name: courseName } = course

console.log(courseName) // JS Fundamentals for Frontend Developers

// What ES6 does under the hood:
let courseName = course.name

نکته

اگر یک متغیر را درون یک شی که وجود ندارد destructure کردید نگران نباشید. undefined را بر می‌گرداند.

let course = {
  name: 'JS Fundamentals for Frontend Developers'
}

let { package } = course

console.log(package) // undefined

یک نکته جالب دیگر اینکه شما می‌توانید default parameters را برای متغیرهای destructured خود بنویسید. سینتکس آن شبیه تعریف کردن توابع است.

let course = {
  name: 'JS Fundamentals for Frontend Developers'
}

let { package = 'full course' } = course

console.log(package) // full course

حتی می‌توانید متغیرها را هنگام تهیه پیش‌فرض‌ها تغییر نام دهید. فقط این دو را ترکیب کنید.

let course = {
  name: 'JS Fundamentals for Frontend Developers'
}

let { package: packageName = 'full course' } = course

console.log(packageName) // full course

آرایه‌های ساختارشکنانه (Destructuring arrays)

Destructuring arrays و destructuring objects شبیه به هم هستند. با این تفاوت که از ([]) به جای ({}) استفاده می‌کنیم.
وقتی یک آرایه را destructure می‌کنید:

  • اولین متغییر، اولین آیتم در آرایه است.
  • دومین متغییر، دومین آیتم در آرایه است.
  • و .....
let [one, two] = [1, 2, 3, 4, 5]
console.log(one) // 1
console.log(two) // 2

اگر تعداد متغییرها بیشتر از تعداد آیتم‌ها باشد می‌توان آن‌ها را از بین برد. اگر چنین شرایطی پیش بیاید، مقدار undefined برگردانده می‌شود.

let [one, two, three] = [1, 2]
console.log(one) // 1
console.log(two) // 2
console.log(three) // undefined

هنگام destructuring arrays ما فقط متغیرهای مورد نیاز خود را destructure می‌کنیم.
در صورت نیاز به بقیه آرایه، می‌توانید از اپراتور (...) استفاده کنید ، مانند این:

let scores = ['98', '95', '93', '90', '87', '85']
let [first, second, third, ...rest] = scores

console.log(first) // 98
console.log(second) // 95
console.log(third) // 93
console.log(rest) // [90, 87, 85]

درباره اپراتور rest در سکشن زیر بیشتر صحبت خواهیم کرد.

تعویض متغیرها با destructured arrays

فرض کنید شما دو متغیر دارید. a و b

let a = 2
let b = 3

می‌خواهید مقادیر این دو متغیر را جابجا کنید. یعنی a = 3 و b = 2
در ES5 شما به یک متغیر سومی نیاز دارید تا این جابجایی را انجام دهید.

let a = 2
let b = 3
let temp

// swapping
temp = a // temp is now 2
a = b // a is now 3
b = temp // b is now 2

درست است که این قطعه کد صحیح عمل می‌کند ولی خب ممکن کمی گیج کننده باشد. حالا ببینید در ES6 با استفاده از destructured arrays چطور این کار را انجام می‌دهیم.

let a = 2
let b = 3; // semicolon required because next line begins with a square bracket

// Swapping with destructured arrays
[a, b] = [b, a]

console.log(a) // 3
console.log(b) // 2

خیلی راحت تر از روش قبلی :))
حالا برویم سراغ destructuring arrays و objects در یک تابع

Destructuring arrays and objects هنگام تعریف فانکشن‌ها

یک ویژگی جالب destructuring این است که می‌توان از آن‌ها هر جایی استفاده کرد حتی در توابع.
تابعی داریم که آرایه‌ای از امتیازها را می‌گیرد و یک آبجکت با سه امتیاز بالا برمی‌گرداند. این تابع مشابه کاری است که ما در هنگام destructuring arrays انجام داده‌ایم.

// Note: You don't need arrow functions to use any other ES6 features
function topThree (scores) {
  let [first, second, third] = scores
  return {
    first: first,
    second: second,
    third: third
  }
}

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

function topThree ([first, second, third]) {
  return {
    first: first,
    second: second,
    third: third
  }
}

یک کوئیز کوچک سریع برای شما داریم. هنگام تعریف فانکشن، می‌توانیم پارامترهای پیش‌فرض و destructure را ترکیب کنیم، کد زیر چه می‌گوید؟

function sayMyName ({
  firstName = 'Zell',
  lastName = 'Liew'
} = {}) {
 console.log(firstName + ' ' + lastName)
}

در این بخش چند ویژگی را با هم ترکیب کرده‌ایم.

  1. می‌بینیم که این فانکشن یک آرگومنت می‌پذیرد که یک آبجکت است. این آبجکت اختیاری است و در صورت عدم تعریف، مقدار پیش‌فرض آن {} است.
  2. سعی می‌کنیم firstName و lastName را از آبجکت داده شده destructure کنیم. اگر این پراپرتی‌ها یافت شوند، از آن‌ها استفاده می‌کنیم.
  3. اگر firstName و یا lastName تعریف نشده باشند، به ترتیب Zell و Liew قرار داده می‌شوند.

بنابراین این تابع نتایج زیر را تولید می‌کند:

sayMyName() // Zell Liew
sayMyName({firstName: 'Zell'}) // Zell Liew
sayMyName({firstName: 'Vincy', lastName: 'Zhang'}) // Vincy Zhang

پارامتر rest و اپراتور spread

این دو پارامتر و اپراتور شبیه به هم هستند. هر دو با سه نقطه (...) نشان داده می‌شوند. اینکه این دو مورد چه کاری انجام می‌دهند، بستگی به این دارد که برای چه چیزی استفاده می‌شوند. بیایید کمی دقیق‌تر این دو پارامترو اپراتور را بررسی کنیم.

پارامتر rest

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

بیایید در عمل نگاهی به عملکرد پارامتر rest بیاندازیم. فرض کنید تابع add را داریم که آرگومنت‌ها را جمع می‌کند.

sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 55

در ES5، هنگامی که با فانکشنی سروکار داشتیم که تعداد نامشخصی متغییر می‌پذیرفت، باید از متغییر arguments استفاده می‌کردیم.

متغییر arguments یک سیمبل شبه آرایه (array-like) است.

function sum () {
  console.log(arguments)
}

sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
arguments یک آرایه نیست بلکه Symbol است.
arguments یک آرایه نیست بلکه Symbol است.

یکی از راه‌های محاسبه کردن مجموع این آرگومنت‌ها، تبدیل کردن آن به آرایه با استفاده از Array.prototype.slice.call(arguments) و سپس سپس استفاده از حلقه forEach یا reduce است. مسلما حلقه forEach را خودتان می‌توانید بنویسید. به همین علت در اینجا مثال reduce را داریم.

// ES5 way
function sum () {
  let argsArray = Array.prototype.slice.call(arguments)
  return argsArray.reduce(function(sum, current) {
    return sum + current
  }, 0)
}

با پارامتر rest در ES6 می‌توانیم تمام آرگومان‌های جدا شده از کاما را مستقیما درون یک آرایه بچسبانیم.

// ES6 way
const sum = (...args) => args.reduce((sum, current) => sum + current, 0)

// ES6 way if we didn't shortcut it with so many arrow functions
function sum (...args) {
  return args.reduce((sum, current) => sum + current, 0)
}

کدها تمیزتر نشدند؟!!

در ادامه ما یک آرایه از امتیازها را به سه امتیاز بالا destructure کردیم.

let scores = ['98', '95', '93', '90', '87', '85']
let [first, second, third] = scores

console.log(first) // 98
console.log(second) // 95
console.log(third) // 93

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

let scores = ['98', '95', '93', '90', '87', '85']
let [first, second, third, ...restOfScores] = scores

console.log(restOfScores) // [90, 87, 85]

اگر توضیحات گیج کننده بود، این موضوع را به یاد داشته باشید که پارامتر rest همه چیز را در یک آرایه پک و بسته‌بندی می‌کند. آن در پارامترهای تابع و زمانی که آرایه‌ها destructuring می شوند، ظاهر می‌شود.

برویم سراغ اپراتور بعدی.

اپراتور spread

این اپراتور برعکس پارامتر rest کار می‌کند. یک آرایه را میگیرد و آن را به لیستی از آرگومنت‌های جداشده با کاما گسترش میدهد (شبیه فشردگی)

let array = ['one', 'two', 'three']

// These two are exactly the same
console.log(...array) // one two three
console.log('one', 'two', 'three') // one two three

این اپراتور اغلب اوقات برای راحت‌تر و خواناتر کردن آرایه‌ها مورد استفاده قرار می‌گیرد.

برای مثال شما می‌خواهید آرایه‌های زیر را concatenate یا به هم وصل کنید.

let array1 = ['one', 'two']
let array2 = ['three', 'four']
let array3 = ['five', 'six']

راهی که در ES5 پیشنهاد می‌شد این بود که دو آرایه از متد Array.concat استفاده کنند. شما با این متد می‌توانید هر تعداد آرایه‌ای را به هم متصل کنید.

// ES5 way
let combinedArray = array1.concat(array2).concat(array3)
console.log(combinedArray) // ['one', 'two', 'three', 'four', 'five', 'six']

در ES6 اپراتور spread وارد عمل می‌شود. می‌توانید آرایه‌ها را در یک آرایه جدید منتشر کنید. شبیه به مثال زیر که خواندن آن هم بسیار راحت‌تر است.

// ES6 way
let combinedArray = [...array1, ...array2, ...array3]
console.log(combinedArray) // ['one', 'two', 'three', 'four', 'five', 'six']

این اپراتور یک کارایی دیگر هم دارد و آن اینکه می‌توانید یک آیتم را از یک آرایه حذف کنید. بدون اینکه نیازی به mutating کردن آرایه داشته باشید. این روش معمولا در ریداکس (Redux) استفاده می‌شود.

Enhanced object literals

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

const anObject = {
  property1: 'value1',
  property2: 'value2',
  property3: 'value3',
}

ES6 enhanced object literals سه آیتم ارتقا و بهبود را در آبجکت‌هایی که می‌شناسید، به ارمغان آورده است.

  1. Property value shorthands
  2. Method shorthands
  3. The ability to use computed property names

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

شکل کوتاه Property value

حتما برای شما پیش آمده که مقداری را به یک متغیر اختصاص داده باشید که همنام با آبجکت پراپرتی باشد. چیزی شبیه به قطعه کد زیر:

const fullName = 'Zell Liew'

const Zell = {
  fullName: fullName
}

خب با توجه به اینکه پراپرتی و متغییر حاوی مقدار همنام هستند، دوست نداشتید این کار را به روش ساده‌تری انجام می‌دادید؟

خبر خوب اینکه می‌توانید :)

اگر نام متغیر و نام پراپرتی یکی است، می‌توانید فقط متغیر را بنویسید. چیزی شبیه به قطعه زیر

const fullName = 'Zell Liew'

// ES6 way
const Zell = {
  fullName
}

// Underneath the hood, ES6 does this:
const Zell = {
  fullName: fullName
}

زیبا نیست؟ تعداد کلمات کمتری داریم و خیلی لذت‌بخش بود.

شکل کوتاه Method

متدها فانکشن‌هایی هستند که با یک پراپرتی همراه هستند. در ادامه یک مثال از متد داریم:

const anObject = {
  aMethod: function () { console.log("I'm a method!~~")}
}

در ES6 می‌خواهیم متدها را خلاصه نویسی کنیم. می‌توانیم function را از اعلان متد حذف کنیم و خواهیم دید که مانند قبل به درستی کار می‌کند.

const anObject = {
  // ES6 way
  aShorthandMethod (arg1, arg2) {},

  // ES5 way
  aLonghandMethod: function (arg1, arg2) {},
}

با استفاده از این بروزرسانی، آبجکت‌ها شکل کوتاه shorthand را دریافت می‌کنند. بنابراین لطفا هنگام تعریف آبجکت‌ها از arrow functions استفاده نکنید. زیرا این چارچوب را درهم می‌شکنید. (پیشنهاد می‌کنیم کمی بالاتر مفاهیم arrow functions را مجددا مطالعه کنید.)

const dontDoThis = {
  // Noooo. Don't do this
  arrowFunction: () => {}
}

Computed object property names

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

// ES5
const newPropertyName = 'smile'

// Create an object first
const anObject = { aProperty: 'a value' }

// Then assign the property
anObject[newPropertyName] = ':D'

// Adding a slightly different property and assigning it
anObject['bigger ' + newPropertyName] = 'XD'

// Result
// {
//   aProperty: 'a value',
//   'bigger smile': 'XD'
//   smile: ':D',
// }

در ES6 می‌توانید زمانی که آبجکت را می‌سازید، مستقیما اسامی ویژگی داینامیک را اختصاص دهید.

تنها نکته‌ای که باید رعایت کنید این است که ویژگی داینامیک را در [ ] قرار دهید.

const newPropertyName = 'smile'

// ES6 way.
const anObject = {
  aProperty: 'a value',
  // Dynamic property names!
  [newPropertyName]: ':D',
  ['bigger ' + newPropertyName]: 'XD',
}

// Result
// {
//   aProperty: 'a value',
//   'bigger smile': 'XD'
//   smile: ':D',
// }

Template literals

کار کردن با رشته‌ها در جاوا اسکریپت یک تجربه دست و پا گیر است. قبل‌تر هنگام تعریف default parameters کمی با رشته‌ها سر و کار داشتیم. رشته‌هایی با فضای خالی ایجاد کردیم و با + آن را به هم وصل کردیم.

function announcePlayer (firstName, lastName, teamName) {
  console.log(firstName + ' ' + lastName + ', ' + teamName)
}

در ES6 با template literals مشکل برطرف شده است. با استفاده از backticks به یک مکان نگهدارنده ویژه (${}) دسترسی پیدا می‌کنید که می‌توانید به طور عادی از جاوا اسکریپت استفاده کنید.

const firstName = 'Zell'
const lastName = 'Liew'
const teamName = 'unaffiliated'

const theString = `${firstName} ${lastName}, ${teamName}`

console.log(theString)
// Zell Liew, unaffiliated

همانطور که ملاحظه می‌کنید می‌توان همه چیز را با template literals گروه‌بندی کرد.
بهترین بخش template literals این است که می‌توانید به راحتی استرینگ‌های چند خطی ایجاد کنید.

const multi = `One upon a time,
In a land far far away,
there lived a witich,
who could change night into day`
multilaine
استرینگ‌های چند خطی به درستی کار می‌کنند.

یک ترفند به اصطلاح تمیز، استفاده از این رشته‌ها برای ایجاد کدهای HTML در جاوا اسکریپت است. درست است که شاید بهترین روش نباشد ولی بهتر از ایجاد یک به یک کدهای جاوا اسکریپت و HTML است.

const container = document.createElement('div')
const aListOfItems =
  `
  • Point number one
  • Point number two
  • Point number three
  • Point number four
`

container.innerHTML = aListOfItems

document.body.append(container)

فیچر بعدی تگ‌ها هستند. تگ‌ها توابعی هستند که اگر خواستید هر استرینگی را جایگزین کنید، اجازه می‌دهند template literal را دستکاری کنید.

const animal = 'lamb'

// This a tag
const tagFunction = () => {
  // Do something here
}

// This tagFunction allows you to manipulate the template literal.
const string = tagFunction `Mary had a little ${animal}`

 


برچسب‌ها: جاوااسکریپت

رقیه اباذری
رقیه اباذری

مطالب مرتبط


6 قطعه کد جاوا اسکریپت برای حل مشکلات رایج

مانند بسیاری از زبان‌های برنامه نویسی، جاوا اسکریپت دارای نقص‌ها و علائم مربوط به آن است.