۹ روش برای کار با اشیا در جاوا اسکریپت — راهنمای کاربردی
جاوا اسکریپت مانند بسیاری از زبانهای دیگر دارای ترفندهای زیادی برای اجرای وظایف آسان و دشوار است. در این مقاله با 9 روش برای کار با اشیا در جاوا اسکریپت آشنا میشویم. در این مقاله فهرست کوتاهی از روشهای کار با اشیا ارائه میکنیم. برخی موارد جالب هستند، برخی موارد مشهور هستند و برخی دیگر نیز صرفاً به منظور داشتن اطلاعات ارائه شدهاند. اگر به کدنویسی جاوا اسکریپت علاقهمند باشید، احتمالاً موافق هستید که کار با اشیا نسبت به کار با انواع دیگر جذابیت بیشتری دارد. با ما تا انتهای این راهنما همراه باشید.
1. ایجاد واقعی شیئ خالی
همه میدانیم که امکان ایجاد شیء در جاوا اسکریپت وجود دارد، اما آیا میدانید که امکان ایجاد شیء خالی نیز وجود دارد؟
به مثال زیر توجه کنید:
1const myEmptyObject = {}
با استفاده از دستور فوق میتوانید اشیای خالی سادهای ایجاد کنید. با این حال به صورت داخلی این اشیا واقعاً خالی نیستند زیرا ما در عمل کاری مانند زیر انجام دادهایم:
1Object.create(Object.prototype)
دستور فوق یک شیء ایجاد میکند که به مشخصههای درون Object.prototype دسترسی داشته باشیم که در ابتدای زنجیره پروتوتایپ قرار دارد. این بدان معنی است که میتوانید از متدهایی مانند زیر استفاده کنید:
1myEmptyObject.toString()
برای ایجاد واقعی شیء خالی باید در زمان استفاده از آن null ارسال شود:
1const myTrulyEmptyObject = Object.create(null)
زمانی که اشیا با استفاده از رویکرد فوق ایجاد میشوند، هیچ مشخصهای در عمل وجود نخواهد داشت تا این که خودتان آن را اضافه کنید. البته روش فوق معمولاً توصیه نمیشود، زیرا عدم استفاده از یک پروتوتایپ پایه هیچ مزیتی ندارد.
2. ادغام اشیا - روش شماره یک (Object.assign)
1const novice = { username: 'henry123', level: 10, hp: 100 }
2
3function transform(target) {
4 return Object.assign(target, {
5 fireBolt(player) {
6 player.hp -= 15
7 return this
8 },
9 })
10}
11
12const sorceress = transform(novice)
13const lucy = { username: 'iamlucy', level: 5, hp: 100 }
14
15sorceress.fireBolt(lucy)
زمانی که از متد Object.assign استفاده میکنید، باید یک شیء target به عنوان شیئی برای ادغام اشیای اضافی و/یا مشخصهها داشته باشید. شیء هدف آرگومان نخست Object.assign است. هر آرگومان بعد از آن در نهایت در شیء هدف ادغام میشود. مستندات رسمی موزیلا در مورد این متد به صورت زیر است:
متد ()Object.assign همه مشخصههای شمارش پذیر خود را از یک یا چند شیء منبع به یک شیء هدف کپی میکند. در نهایت شیء مقصد بازگشت مییابد.
3. ادغام اشیا – روش شماره دو (Spread Syntax)
1const novice = { username: 'henry123', level: 10, hp: 100 }
2
3function transform(target) {
4 return {
5 ...target,
6 fireBolt(player) {
7 player.hp -= 15
8 return this
9 },
10 }
11}
12
13const sorceress = transform(novice)
14const lucy = { username: 'iamlucy', level: 5, hp: 100 }
15
16sorceress.fireBolt(lucy)
زمانی که اشیا را به این روش ادغام میکنید، در واقع از عملگر اسپرد روی یک لفظ شیء بهره میگیرید. این ساختار در نسخه رسمی ECMAScript 2018 معرفی شده است و از این رو جدید محسوب میشود. استفاده از این روش برای ادغام چندین شیء کاملاً ساده است و افراد زیادی استفاده از آن را توصیه میکنند زیرا کد همچنان خوانا و تمیز میماند. در واقع تنها کاری که باید انجام دهید تایپ کردن سهنقطه است.
گسترش تابعهای IIFE
تابعها در جاوا اسکریپت از جنبههای مختلف قدرتمند هستند. با استفاده از آنها میتوان هر کاری انجام داد. دلیل این مسئله به ماهیت تابعهای جاوا اسکریپت بازمیگردد. تابعها در جاوا اسکریپت عناصر درجه اولی محسوب میشوند و از این رو میتوان آنها را در هر جایی به خدمت گرفت.
برای نمونه از آنجا که تابعها در جاوا اسکریپت همچنان شیء هم محسوب میشوند، میتوان با تابعها مانند اشیا رفتار کرد. معنی این حرف آن است که میتوان آنها را به اطراف ارسال کرد و کارهای جالبی با آنها انجام داد. حتی میتوان از تابعها برای ادغام در لفظهای شیء به روشهای عجیب مانند زیر استفاده کرد:
1import React from 'react'
2import {
3 EditIcon,
4 DeleteIcon,
5 ResetIcon,
6 TrashIcon,
7 UndoIcon,
8} from '../lib/icons'
9import * as utils from '../utils
10
11export const audioExts = ['mp3', 'mpa', 'ogg', 'wav']
12
13const icons = {
14 edit: {
15 component: EditIcon,
16 onClick: () => window.alert('You clicked the edit component'),
17 name: 'edit',
18 },
19 delete: {
20 component: DeleteIcon,
21 name: 'delete',
22 },
23 // Audio icons
24 // IIFE returning an object
25 ...(function() {
26 return audioExts.reduce((acc, ext) => {
27 acc[ext] = {
28 component: MdAudiotrack,
29 title: 'Audio Track',
30 }
31 return acc
32 })(),
33}
از آنجا که IIFE-ها خود فراخوان هستند، بیدرنگ شیئی را بازگشت میدهیم که باید در شیء icons قرار گیرد. نتیجه همان شیء خواهد بود، اما ادغام خواهد داشت:
1export const audioExts = ['mp3', 'mpa', 'ogg', 'wav']
2
3const icons = {
4 edit: {
5 component: EditIcon,
6 onClick: () => window.alert('You clicked the edit component'),
7 name: 'edit',
8 },
9 delete: {
10 component: DeleteIcon,
11 name: 'delete',
12 },
13 // Merged with audio icons
14 mp3: {
15 component: MdAudiotrack,
16 title: 'Audio Track',
17 },
18 mpa: {
19 component: MdAudiotrack,
20 title: 'Audio Track',
21 },
22 ogg: {
23 component: MdAudiotrack,
24 title: 'Audio Track',
25 },
26 wav: {
27 component: MdAudiotrack,
28 title: 'Audio Track',
29 },
30}
4. بررسی مشخصههای موجود در 2020
یک قابلیتی که قطعاً سروصدای زیادی در جامعه توسعهدهندگان جاوا اسکریپت ایجاد خواهد کرد، «زنجیرهسازی اختیاری» (optional chaining) است. عملگر جدید به شکل ?. است و بدون نیاز به اعتبارسنجی صریح تک تک حلقههای زنجیره، امکان خواندن مقدار مشخصهای را فراهم میسازد که در اعماق یک زنجیره از اشیای به هم متصل قرار دارد. این بدان معنی است که اگر یک ساختمان شیء عمیقاً تودرتو مانند زیر داشته باشید:
1const food = {
2 fruits: {
3 apple: {
4 dates: {
5 expired: '2019-08-14',
6 },
7 },
8 },
9}
دیگر لازم نیست کدهای تکراری مانند زیر بنویسید:
1function getAppleExpirationDate(obj) {
2 if (food.fruits && food.fruits.apple && food.fruits.apple.dates) {
3 return food.fruits.apple.dates.expired
4 }
5}
بدین ترتیب کار هنگام استفاده از زنجیرهسازی اختیاری بسیار آسانتر میشود:
1function getAppleExpirationDate(obj) {
2 return food?.fruits?.apple?.dates?.expired
3}
استفاده از این روش در هر جای کد موجب ایجاد کد بسیار تمیزتری میشود. مثلاً تابعی مانند زیر:
1function findFatDogs(dog, result = []) {
2 if (dog && dog.children) {
3 return dog.children.reduce((acc, child) => {
4 if (child && child.weight > 100) {
5 return acc.concat(child)
6 } else {
7 return acc.concat(findFatDogs(child))
8 }
9 }, result)
10 }
11 return result
12}
میتواند به سادگی به تابع زیر تبدیل شود و در عین حال خوانایی آن نیز حفظ شود:
1function findFatDogs(dog, result = []) {
2 if (dog?.children) {
3 return dog.children.reduce((acc, child) => {
4 return child?.weight > 100
5 ? acc.concat(child)
6 : acc.concat(findFatFrogs(child))
7 }, result)
8 }
9 return result
10}
نکته: در زمان نگارش این مقاله، هنوز همه مرورگرهای مدرن از این قابلیت پشتیبانی نمیکنند. اما میتوانید از تایپ اسکریپت استفاده کنید تا زنجیرهسازی اختیاری را به ساختاری کامپایل کند که مرورگرهای قدیمی هم میتوانند بخوانند.
5. فراخوانی اشیا با Overriding
زمانی که اشیا به صورت کلیدهای لفظهای شیئی انتساب مییابند، به صورت رشته درمیآیند. این حالت کاربردهای بسیار زیبایی دارد.
به مثال زیر توجه کنید:
1function Command(name, execute) {
2 this.name = name
3 this.execute = execute
4}
5
6Command.prototype.toString = function() {
7 return this.name
8}
9
10const createCommandHub = function() {
11 const commands = {}
12 return {
13 add(command) {
14 commands[command.name] = command
15 },
16 execute(command, ...args) {
17 return commands[command].execute(...args)
18 },
19 }
20}
21
22const cmds = createCommandHub()
23const talkCommand = new Command('talk', function(wordsToSay) {
24 console.log(wordsToSay)
25})
26const destroyEverythingCommand = new Command('destroyEverything', function() {
27 throw new Error('Destroying everything')
28})
29
30cmds.add(talkCommand)
31cmds.add(destroyEverythingCommand)
32cmds.execute(talkCommand, 'Is talking a talent?')
اگر قطعه کد فوق را اجرا کنید، میبیند که کد کار میکند و نتیجه به صورت زیر است:
اگر دقیقاً به روش اضافه شدن این دستور نگاه کنید، میبینید که باید خطایی مانند زیر صادر کند:
دلیل این که این خطا صادر نمیشود، این است که وقتی سازنده Command تعریف شد، ما میتوانیم متد پروتوتایپ toString را مانند زیر override کنیم: