آخرین نسخه استاندارد زبان جاوااسکریپت نسخه چهاردهم ECMAScript 2023 است. این به روز رسانی شامل متدهای جدیدی در array prototype می‌باشد. در این مقاله قصد داریم تا تمام ویژگی‌های این متدهای جدید، از جمله رفتار آن‌ها با آرایه‌های sparse و آبجکت‌های شبه آرایه را باهم بررسی کنیم.

آیا حفظ آرایه اصلی بدون mutation مهم است؟

یک موضوع مشترکی که بین متدهای جدید array prototype وجود دارد، تمرکز بر روی عدم تغییر آرایه اصلی، و return کردن یک آرایه کاملاً جدید است.

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

متد ()toReversed

متد toReversed()مشابه متد reverse()است، اما یک تمایز قابل توجه وجود دارد. toReversed()عناصر موجود در یک آرایه را بدون mutation در آرایه اصلی معکوس می‌کند. آرایه fruitsزیر را در نظر بگیرید:

const fruits = ["apple", "orange", "banana"]

حالا fruitsرا با استفاده از متد reverse()معکوس می‌کنیم:

// Reverse the array 
const result = fruits.reverse()
console.log(result) 

// ['banana', 'orange', 'apple']

console.log(fruits)
// ['banana', 'orange', 'apple']
// original array is mutated

هنگام استفاده از متد reverse()، آرایه اصلی دچار mutation می‌شود.

برای معکوس کردن آرایه بدون mutation، می‌توانیم از متد toReversed()استفاده کنیم.

// Reverse the array 
const result = fruits.toReversed()
console.log(result) 

// ['banana', 'orange', 'apple']

console.log(fruits)
// ["apple", "orange", "banana"] 
// original array is preserved

رفتار متد ()toReversed با آرایه‌های sparse

آرایه‌های sparse آرایه‌هایی بدون عناصر متوالی هستند. برای مثال موارد زیر را در نظر بگیرید:

const numbers = [1,2,3]
// Assign an item to index 11
numbers[11] = 12

console.log(numbers)
// [۱, ۲, ۳, empty × ۸, ۱۲]

در مثال بالا، numbersدارای هشت slot خالی است. numbersیک آرایه sparse می‌باشد. اکنون به متد Reversed()برمی‌گردیم. این متد چگونه با آرایه‌های sparse کار می‌کند؟

toReversed()هرگز یک آرایه sparse را return نمی‌کند. اگر آرایه اصلی دارای slotهای خالی بود، آن‌ها به صورت undefinedبرمی‌گرداند.

فراخوانی متد toReversed()را در آرایه numbersزیر در نظر بگیرید:

const numbers = [1,2,3]
// Assign an item to index 11
numbers[11] = 12

numbers.toReversed()
// [۱۲, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 3, 2, 1]

همانطور که انتظار داشتیم، تمام slotهای خالی به عنوان مقادیر آیتم آرایه undefinedبرگردانده می‌شوند.

رفتار متد ()toReversed با آبجکت‌های شبه آرایه

حتی اگر متد toReversed()به طور خاص در prototype Arrayوجود داشته باشد، ممکن است در آبجکت‌های شبه آرایه نیز فراخوانی شود.

یک آبجکت شبه آرایه معمولا دارای یک ویژگی length، و به صورت اختیاری دارای ویژگی‌هایی با نام ایندکس‌های عدد صحیح می‌باشد. آبجکت‌های String نمونه‌ای از آبجکت‌های شبه آرایه هستند.

تابع toReversed()ابتدا ویژگی lengthآبجکتی را که بر روی آن فراخوانی شده است می‌خواند و سپس از طریق keyهای عدد صحیح آبجکت، از انتها تا ابتدا تکرار می‌شود که به معنی length - 1تا ۰است. این تابع مقدار هر ویژگی را به انتهای یک آرایه جدید اضافه کرده سپس آن را return می‌کند.

اکنون می‌خواهیم این موضوع را امتحان کنیم. کاربرد اشتباه toReversed()روی یک رشته را در نظر بگیرید:

const s = "Ohans Emmanuel"

// call `toReversed` directly on the string
s.toReversed()

//Uncaught TypeError: s.toReversed is not a function

حتی اگر یک آبجکت string یک آبجکت شبه آرایه باشد، این کدی که نوشیم اشتباه است. ما نمی‌توانیم آن را به شیوه string.toReversed()فراخوانی کنیم زیرا toReversedدر prototype stringوجود ندارد.

با این حال، ممکن است از متد call()مانند شکل زیر استفاده کنیم:

const s = "Ohans Emmanuel"

// Array.prototype.toReversed.call(arrayLike)
Array.prototype.toReversed.call(s)

//['l', 'e', 'u', 'n', 'a', 'm', 'm', 'E', ' ', 's', 'n', 'a', 'h', 'O']

این موضوع درمورد یک آبجکت شبه آرایه self-constructed چگونه عمل می‌کند؟ به مثال زیر توجه کنید:

// Has a length property and integer index property.
const arrayLike = {
 length: 5,
 ۲: "Item #2"
}

اگر این یک آرایه استاندارد بود، یک آرایه sparse می‌شد، یعنی طول آرایه ۵ است و در ایندکس شماره ۲ مقدار Item #2وجود دارد.

نتیجه فراخوانی toReversedرا در این مثال در نظر بگیرید:

console.log(Array.prototype.toReversed.call(arrayLike))

// [undefined, undefined, 'Item #2', undefined, undefined]

تابع toReversed()یک آرایه معکوس بدون ایجاد یک آرایه sparse تولید می‌کند. همانطور که انتظار داشتیم، slotهای خالی به صورت undefinedبرگردانده می‌شوند.

متد ()toSorted

.toSorted() همتای متد .sort()است. همانطور که ممکن است حدس زده باشیم، متد .toSorted()برخلاف.sort()، آرایه اصلی را تغییر نخواهد داد. عملیات مرتب‌سازی با استفاده از متد .sort()را در نظر بگیرید:

const list = [1, 5, 6, 3, 7, 8, 3, 7]
//Sort in ascending order 
const result = list.sort()

console.log(result)
// [۱, ۳, ۳, ۵, ۶, ۷, ۷, ۸]
console.log(list)
// [۱, ۳, ۳, ۵, ۶, ۷, ۷, ۸]

همانطور که در مثال بالا می‌بینیم، متد sort()آرایه را در جای خود مرتب کرده و در نتیجه آرایه را دچار mutation می‌کند. اکنون نتیجه را با متد toSorted()بررسی می‌کنیم:

const list = [1, 5, 6, 3, 7, 8, 3, 7]
// Sort in ascending order 
const result = list.toSorted()

console.log(result)
// [۱, ۳, ۳, ۵, ۶, ۷, ۷, ۸]
console.log(list)
// [۱, ۵, ۶, ۳, ۷, ۸, ۳, ۷]

همانطور که می‌بینیم، toSorted()یک آرایه جدید با عناصر مرتب شده return می‌کند.

باید به این نکته توجه داشته باشیم که toSorted()همان سینتکس sort()را حفظ می‌کند. برای مثال، ممکن است تابعی را تعیین کنیم که ترتیب مرتب‌سازی را مشخص می‌کند، به عنوان مثال، list.toSorted(compareFn).

به مثال زیر توجه کنید:

const list = [1, 5, 6, 3, 7, 8, 3, 7]
//Sort the array in descending order 
list.toSorted((a,b) => a < b ? 1 : -1)
// [۸, ۷, ۷, ۶, ۵, ۳, ۳, ۱]

رفتار متد ()toSorted با آرایه‌های sparse

slotهای خالی همیشه به صورت undefinedبرگردانده می‌شوند. در واقع به گونه‌ای با آن‌ها رفتار می‌شود که گویی دارای مقدار undefined هستند. با این حال، compareFnبرای این slotها فراخوانی نمی‌شود و همیشه در انتهای آرایه بازگشتی قرار می‌گیرند.

مثال زیر را برای یک آرایه که اولین slot آن خالی است در نظر می‌گیریم:

// Note the empty initial slot 
const fruits = [, "apple", "orange", "banana"]

console.log(fruits.toSorted())

// ['orange', 'banana', 'apple', undefined]

این رفتار با زمانی که مقدار اولیه undefinedباشد، یکسان است. به عنوان مثال:

const fruits = [undefined, "apple", "orange", "banana"]

console.log(fruits.toSorted())

// ['orange', 'banana', 'apple', undefined]

همچنین باید به این موضوع توجه داشته باشیم که slotهای خالی (یا slotهای undefined) بدون توجه به موقعیت آن‌ها در آرایه اصلی، همیشه به انتهای آرایه بازگشتی منتقل می‌شوند. مثلا:

// empty slot is in index 2
const fruits = ["apple", "orange", , "banana"]

console.log(fruits.toSorted())

// returned last 
// ['orange', 'banana', 'apple', undefined]

// undefined value is in index 2

const otherFruits = ["apple", "orange", undefined , "banana"]

console.log(otherFruits.toSorted())

// returned last 
// ['orange', 'banana', 'apple', undefined]

رفتار متد ()toSorted با آبجکت‌های شبه آرایه

هنگامی که همراه با آبجکت‌ها از تابع toSorted()استفاده می‌کنیم، ابتدا ویژگی lengthآبجکت thisرا می‌خواند. سپس keyهای عدد صحیح آبجکت را از ابتدا تا انتها جمع‌آوری می‌کند، یعنی از ۰تا length - 1. پس از مرتب‌سازی آن‌ها، مقادیر مربوطه را در یک آرایه جدید return می‌کند. به عنوان مثال:

const s = "Ohans Emmanuel"

// Array.prototype.toSorted.call(arrayLike)
Array.prototype.toSorted.call(s)
(۱۴) [' ', 'E', 'O', 'a', 'a', 'e', 'h', 'l', 'm', 'm', 'n', 'n', 's', 'u']

همینطور مثال زیر که با یک آبجکت شبه آرایه ساخته شده است:

// Has a length property and integer index property.
const arrayLike = {
 length: 5,
 ۲: "Item #2"
۱۰: "Out of bound Item" // This will be ignored since the length is 5
}

console.log(Array.prototype.toSorted.call(arrayLike))
// ['Item #2', undefined, undefined, undefined, undefined]

متد toSpliced(start, deleteCount, …items)

متد .toSpliced()یکی دیگر از متدهای جدید array prototype است که همتای متد .splice()می‌باشد. همانند روش‌هایی که درمورد آن‌ها صحبت کردیم،toSpliced()برخلاف .splice()آرایه‌ای را که فراخوانی شده است تغییر نمی‌دهد. همینطور سینتکس متد toSplicedبا .spliceیکسان است. مثلا:

toSpliced(start)
toSpliced(start, deleteCount)
toSpliced(start, deleteCount, item1)
toSpliced(start, deleteCount, item1, item2, itemN)

همانطور که در مثال زیر داریم، با استفاده از متد .splice()یک آیتم جدید به آرایه اضافه می‌کنیم:

const months = ["Feb", "Mar", "Apr", "May"] 
// Insert item "Jan" at index 0 and delete 0 items
months.splice(0, 0, "Jan")

console.log(months) 
//  ['Jan', 'Feb', 'Mar', 'Apr', 'May']

متد splice()آیتم جدید آرایه را وارد کرده و آرایه اصلی را دچار mutation می‌کند. برای ایجاد یک آرایه جدید بدون ایجاد mutation در آرایه اصلی، باید از متد toSpliced()استفاده کنیم.

این بار مثال بالا را با استفاده از toSpliced()بازنویسی می‌کنیم:

const months = ["Feb", "Mar", "Apr", "May"] 
// Insert item "Jan" at index 0 and delete 0 items
const updatedMonths = months.toSpliced(0, 0, "Jan")

console.log(updatedMonths)
// ['Jan', 'Feb', 'Mar', 'Apr', 'May']
console.log(months)
// ['Feb', 'Mar', 'Apr', 'May']

toSpliced()یک آرایه جدید بدون mutation در آرایه اصلی return می‌کند. باید به این موضوع توجه داشته باشیم که سینتکس برای toSpliced()و splice()یکسان است.

رفتار متد ()toSpliced با آرایه‌های sparse

toSpliced()هرگز یک آرایه sparse را return نمی‌کند. به این ترتیب، slotهای خالی به صورت undefinedبرگردانده می‌شوند. به عنوان مثال:

const arr = ["Mon", , "Wed", "Thur", , "Sat"];
// Start at index 1, and delete 2 items
console.log(arr.toSpliced(1, 2)); 

// ['Mon', 'Thur', undefined, 'Sat']

رفتار متد ()toSpliced با آبجکت‌های شبه آرایه

هنگام کار با آبجکت‌های شبه آرایه، متد toSplicedطول آبجکت thisرا می‌گیرد، key عدد صحیح مورد نیاز را می‌خواند و نتیجه را در یک آرایه جدید می‌نویسد:

const s = "Ohans Emmanuel"

// Start at index 0, delete 1 item, insert the other items
console.log(Array.prototype.toSpliced.call(s, 0, 1, 2, 3));

// [۲, ۳, 'h', 'a', 'n', 's', ' ', 'E', 'm', 'm', 'a', 'n', 'u', 'e', 'l']

متد with(index, value)

متد آرایه .with()بسیار جالب است. ابتدا، نماد براکت را برای تغییر مقدار یک ایندکس خاص از آرایه در نظر می‌گیریم:

const favorites = ["Dogs", "Cats"]
favorites[0] = "Lions"

console.log(favorites)
//(۲) ['Lions', 'Cats']

با نماد براکت، آرایه اصلی همیشه دچار mutation می‌شود. متد .with()همان عمل درج یک عنصر در یک ایندکس خاص را انجام می‌دهد، اما آرایه را تغییر نمی‌دهد. در عوض، آرایه جدیدی را با ایندکس جایگزین شده return می‌کند.

مثالی که داشتیم را با استفاده از .with()بازنویسی می‌کنیم:

const favorites = ["Dogs", "Cats"]
const result = favorites.with(0, "Lions")

console.log(result)
// ['Lions', 'Cats']
console.log(favorites)
// ["Dogs", "Cats"]

رفتار متد ()with با آرایه‌های sparse

with()هرگز یک آرایه sparse را return نمی‌کند. به این ترتیب، slotهای خالی به صورت undefinedبرگردانده می‌شوند. به عنوان مثال:

const arr = ["Mon", , "Wed", "Thur", , "Sat"];
arr.with(0, 2)
// [۲, undefined, 'Wed', 'Thur', undefined, 'Sat']

رفتار متد ()with با آبجکت‌های شبه آرایه

مشابه سایر متدها، with()ویژگی lengthآبجکت thisرا دریافت می‌کند. پس هر ایندکس صحیح مثبت (کمتر از length) آبجکت را می‌خواند. با دسترسی به به این موارد، مقادیر ویژگی آن‌ها را در ایندکس آرایه بازگشتی ذخیره می‌کند. در نهایت، indexو valueدر signature فراخوانی with(index, value)بر روی آرایه بازگشتی تنظیم می‌شود. مثلا:

const s = "Ohans Emmanuel"

// Set the value of the first item
console.log(Array.prototype.with.call(s, 0, "F"));

// ['F', 'h', 'a', 'n', 's', ' ', 'E', 'm', 'm', 'a', 'n', 'u', 'e', 'l']

جمع‌بندی

استاندارد ECMAScript مدام در حال بهبود است و استفاده از ویژگی‌های جدید آن می‌تواند ایده بسیار خوبی باشد. در این مقاله سعی کردیم تا با متدهای جدید array prototype که عبارتند از: toReversed، toSorted، toSplicedو withبیشتر آشنا شویم تا بتوانیم با استفاده از آن‌ها کدهای جاوااسکریپتی واضح‌تر بنویسیم.