آخرین نسخه استاندارد زبان جاوااسکریپت نسخه چهاردهم ECMAScript 2023 است. این به روز رسانی شامل متدهای جدیدی در array prototype میباشد. در این مقاله قصد داریم تا تمام ویژگیهای این متدهای جدید، از جمله رفتار آنها با آرایههای sparse و آبجکتهای شبه آرایه را باهم بررسی کنیم.
یک موضوع مشترکی که بین متدهای جدید array prototype وجود دارد، تمرکز بر روی عدم تغییر آرایه اصلی، و return کردن یک آرایه کاملاً جدید است.
به طور کلی، همانطور که این چهار متد جدید نشان میدهد، عدم تغییر دادهها مزایای زیادی دارد. این مزایا فقط محدود به آرایهها نمیشود، بلکه تمام آبجکتهای جاوااسکریپت را هم دربرمیگیرد. در ادامه برخی از مهمترین مزایا را بررسی میکنیم:
متد 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
آرایههای sparse آرایههایی بدون عناصر متوالی هستند. برای مثال موارد زیر را در نظر بگیرید:
const numbers = [1,2,3] // Assign an item to index 11 numbers[11] = 12 console.log(numbers) // [1, 2, 3, empty × 8, 12]
در مثال بالا، 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() // [12, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 3, 2, 1]
همانطور که انتظار داشتیم، تمام slotهای خالی به عنوان مقادیر آیتم آرایه undefinedبرگردانده میشوند.
حتی اگر متد toReversed()به طور خاص در prototype Arrayوجود داشته باشد، ممکن است در آبجکتهای شبه آرایه نیز فراخوانی شود.
یک آبجکت شبه آرایه معمولا دارای یک ویژگی length، و به صورت اختیاری دارای ویژگیهایی با نام ایندکسهای عدد صحیح میباشد. آبجکتهای String نمونهای از آبجکتهای شبه آرایه هستند.
تابع toReversed()ابتدا ویژگی lengthآبجکتی را که بر روی آن فراخوانی شده است میخواند و سپس از طریق keyهای عدد صحیح آبجکت، از انتها تا ابتدا تکرار میشود که به معنی length - 1تا 0است. این تابع مقدار هر ویژگی را به انتهای یک آرایه جدید اضافه کرده سپس آن را 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,
2: "Item #2"
}
اگر این یک آرایه استاندارد بود، یک آرایه sparse میشد، یعنی طول آرایه 5 است و در ایندکس شماره 2 مقدار Item #2وجود دارد.
نتیجه فراخوانی toReversedرا در این مثال در نظر بگیرید:
console.log(Array.prototype.toReversed.call(arrayLike)) // [undefined, undefined, 'Item #2', undefined, undefined]
تابع toReversed()یک آرایه معکوس بدون ایجاد یک آرایه sparse تولید میکند. همانطور که انتظار داشتیم، slotهای خالی به صورت undefinedبرگردانده میشوند.
.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) // [1, 3, 3, 5, 6, 7, 7, 8] console.log(list) // [1, 3, 3, 5, 6, 7, 7, 8]
همانطور که در مثال بالا میبینیم، متد sort()آرایه را در جای خود مرتب کرده و در نتیجه آرایه را دچار mutation میکند. اکنون نتیجه را با متد toSorted()بررسی میکنیم:
const list = [1, 5, 6, 3, 7, 8, 3, 7] // Sort in ascending order const result = list.toSorted() console.log(result) // [1, 3, 3, 5, 6, 7, 7, 8] console.log(list) // [1, 5, 6, 3, 7, 8, 3, 7]
همانطور که میبینیم، 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) // [8, 7, 7, 6, 5, 3, 3, 1]
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()استفاده میکنیم، ابتدا ویژگی lengthآبجکت thisرا میخواند. سپس keyهای عدد صحیح آبجکت را از ابتدا تا انتها جمعآوری میکند، یعنی از 0تا length - 1. پس از مرتبسازی آنها، مقادیر مربوطه را در یک آرایه جدید return میکند. به عنوان مثال:
const s = "Ohans Emmanuel" // Array.prototype.toSorted.call(arrayLike) Array.prototype.toSorted.call(s) (14) [' ', '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,
2: "Item #2"
10: "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()یکی دیگر از متدهای جدید 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 را 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طول آبجکت 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)); // [2, 3, 'h', 'a', 'n', 's', ' ', 'E', 'm', 'm', 'a', 'n', 'u', 'e', 'l']
متد آرایه .with()بسیار جالب است. ابتدا، نماد براکت را برای تغییر مقدار یک ایندکس خاص از آرایه در نظر میگیریم:
const favorites = ["Dogs", "Cats"] favorites[0] = "Lions" console.log(favorites) //(2) ['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 را return نمیکند. به این ترتیب، slotهای خالی به صورت undefinedبرگردانده میشوند. به عنوان مثال:
const arr = ["Mon", , "Wed", "Thur", , "Sat"]; arr.with(0, 2) // [2, undefined, 'Wed', 'Thur', undefined, 'Sat']
مشابه سایر متدها، 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بیشتر آشنا شویم تا بتوانیم با استفاده از آنها کدهای جاوااسکریپتی واضحتر بنویسیم.