Mapها در JavaScript ES6

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 10 مرداد 1397
دسته بندی ها : جاوا اسکریپت

معرفی

ES6 تعداد زیادی ابزار جدید را معرفی کرد و شما میتوانید از طریق دوره آموزش جاوااسکریپت ES۶ وبسایت راکت در مورد این امکانات اطلاعات بیشتری کسب کنید و آنها را یادبگیرید. امروز، Mapها را بررسی خواهیم کرد. Mapها چه هستند؟ یک Map، لیستی بی نظم از جفت‌های مقادیر کلیدی است که در آن‌ها،‌ کلید و مقدار می‌توانند از هر نوعی باشند.

مشکلات حل شده

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

let map = {}

map[5] = 4
map[{}] = 'An object'

// { '5': 4, '[object Object]': 'An object' }

همانطور که می‌توانید ببینید، مقدار «5»، تبدیل به «’5’» شد، و آبجکت خالی ما تبدیل به ‘[object Object]’ شد. این یک محدودیت جدی است.

در ES6، Mapها از متد Object.is() برای مقایسه کلیدها استفاده می‌کنند، همانطور که Setها مقادیر خود را مقایسه می‌کنند. همچنین Mapها هر کلیدی را تبدیل به یک رشته نمی‌کنند؛ هر نوعی قابل قبول است.

Object.is(5, '5') // false
Object.is({}, {}) // false

سازنده

پس چگونه یک Map جدید بسازیم؟ با استفاده از new Map(). همچنین می‌توانید با استفاده از آرایه‌ای از آرایه‌ها، یک Map را راه‌اندازی کنید:

const map = new Map()
// Map {}

const map = new Map([[5, 42], ["name", "Paul"], ["age", 45]])
// Map { 5 => 42, 'name' => 'Paul', 'age' => 45 }

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

متدهای Mapها

برای تعامل با یک Map، چند متد دارید، که می‌توانید استفاده کنید.

  • متد set (key, value)، یک جفت را به Map مورد نظر اضافه می‌کند.
  • متد get (key)، یک مقدار را از Map مورد نظر دریافت می‌کند. اگر چیزی پیدا نشود، متد get مقدار undefined را بر می‌گرداند.
  • متد has (key)، بررسی می‌کند تا ببیند که کلید مورد نظر در Map مورد نظر وجود دارد یا نه، و سپس مقدار True یا False را بر می‌گرداند.
  • متد delete (key)، کلید و مقدار آن را از Map مورد نظر حذف می‌کند.
  • متد clear()، تمام کلیدها و مقادیر را از Map مورد نظر حذف می‌کنید.
  • در آخر، Mapها ویژگی‌ای به نام size دارند که تعداد جفت‌های کلید / مقدار موجود در Map را بر می‌گرداند.
const map = new Map()

map.set(5, "Hello")
map.set("5", "World")
map.set("John", "The revelator")
map.size // 3
// Map { 5 => 'Hello', '5' => 'World', 'John' => 'The revelator' }

map.get(5) // Hello
map.has('5') // true
map.get('Random') // undefined
map.has('John') // true

map.delete('5')
map.size // 2
// Map { 5 => 'Hello', 'John' => 'The revelator' }

map.clear()
map.size // 0
// Map {}

کلیدهای آبجکت‌ها در Mapها

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

const map = new Map()
let obj1 = {}
let obj2 = {}

map.set(obj1, 12)
map.set(obj2, "OBJECT")
map.size // 2
// Map { {} => 12, {} => 'OBJECT' }

همانطور که می‌توانید ببینید، گرچه در حال استفاده از دو آبجکت خالی به عنوان کلید هستیم، از reference آن آبجکت‌ها در Map مورد نظر استفاده می‌کنید. از این رو، Object.is() که برای مقایسه کلیدها استفاده می‌شود، و مقدار False را بر می‌گرداند. باز هم دقت کنید که آبجکت‌ها مجبور نیستند به رشته تبدیل شوند.

حلقه

می‌توانید با استفاده از forEach()، در طی یک Map، به صورت یک حلقه پش رفته و یک عمل را تکرار کنید. Callback منتقل شده، سه آرگومان را دریافت می‌کند: مقدار، کلید و Map مورد استفاده.

const map = new Map([[5, 42], ["name", "Paul"], ["age", 45]])

map.forEach((value, key, thisMap) => {
    console.log(`${key} => ${value}`)
    console.log(thisMap === map)
})

//5 => 42
//true

//name => Paul
//true

//age => 45
//true

Mapهای ضعیف (Weak)

Mapهای ضعیف، از همان اصول setهای ضعیف، پیروی می‌کنند. در یک Map ضعیف، تمام کلیدها باید یک آبجکت باشند. Mapهای ضعیف برای ذخیره Object referenceهای ضعیف استفاده می‌شوند. این به چه معناست؟

const map = new Map()
let obj1 = {}
map.set(obj1, 12)
//Map { {} => 12 }
obj1 = null // I remove the obj1 reference
// Map { {} => 12 } // But the reference still exists in the map anyway

در این مورد، Object reference ما هنوز در Map مورد نظر وجود دارد. پاک کردن reference در هر جای دیگر، آن را از Map مورد نظر حذف نمی‌کند. در مواردی خاص، شاید بخواهید استفاده از حافظه را بهینه‌سازی کنید و از مشکلات جلوگیری کنید. این کاری است که WeakMap برای شما انجام می‌دهد. هر reference به یک object مه در هر جایی از برنامه شما ناپدید شود، از WeakSet نیز حذف خواهد شد.

const map = new WeakMap()

let obj = {} // اشاره‌ای به آبجکت می‌سازد

map.set(obj, 12) // اشاره را به عنوان یک کلید در محل اشاره ذخیره می‌کند

map.has(obj) // true

map.get(obj) // 12

obj = null

map.has(obj) // false

map.get(obj) // undefined

console.log(map) // WeakMap {}

// آبجکت مورد نظر از مپ ضعیف حذف شده است

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

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

نکته: WeakMap ویژگی size را ندارد.

Mapهای ضعیف از caseها استفاده می‌کنند

یکی از موارد احتمالی استفاده از WeakMap، زمانی است که می‌خواهید یک عنصر DOM را track کنید. با استفاده از WeakMap، می‌توانید عناصر DOM را به عنوان کلید ذخیره کنید. به محض این که آن عنصر حذف شود، آن آبجکت برای آزاد‌سازی فضا در حافظه، حذف می‌شود.

const map = new WeakMap()

const element = document.querySelector(".button")

map.set(element, "Buttons")

map.get(element) // "Buttons"

element.parentNode.removeChild(element) // عنصر را حذف می‌کند

element = null // اشاره را حذف می‌کند

// حال مپ ضعیف خالی است!

یکی دیگر از موارد احتمالی استفاده از WeakMap، برای ذخیره داده‌های خصوصی آبجکت است. تمام ویژگی‌های دیگر ES6، عمومی هستند. پس درباره آن چه خواهید کرد؟ در ES5، می‌توانید چنین کاری انجام دهید:

var Car = (function(){

    var privateCarsData = {}
    var privateId = 0

    function Car(name, color){
        Object.defineProperty(this, "_id", {value: privateId++})

        privateCarsData[this._id] = {
            name: name,
            color: color
        }
    }

    Car.prototype.getCarName = function(){
        return privateCarsData[this._id].name
    }

    Car.prototype.getCarColor = function(){
        return privateCarsData[this._id].color
    }

    return Car
}())

این نزدیک‌ترین جایی است که می‌توانید به داده‌های خصوصی در ES5 برسید. در اینجا، تعریف Car، در داخل یک IIFE (تابع سریعا فراخوانی شده = Immediately Invoked Function Expression) قرار گرفته است. در اینجا دو متغیر خصوصی داریم: privateCardData و privateID. PrivateCarsData اطلاعات خصوصی را برای هر نمونه Car ذخیره می‌کند و privateID برای هر نمونه، یک آیدی منحصل به فرد تولید می‌کند.

وقتی که Car (name, color) را فراخوانی می‌کنیم، ویژگی the_id به privateCarsData اضافه می‌شود و آبجکتی با ویژگی‌های نام (name) و رنگ (color) را دریافت می‌کند. GetCarName و getCarColor با استفاده از this._id به عنوان کلید، داده‌ها را دریافت می‌کند.

این داده‌ها امن هستند، زیرا privateCarsData خارج از IIFE قابل دسترسی نیست؛ گرچه، this._id قابل دسترسی است. مشکل در اینجاست که هیچ راهی نداریم تا وقتی یک نمونه Car نابود می‌شود، خبردار شویم. از این رو، نمی‌توانیم privateCardData را وقتی که یک نمونه ناپدید می‌شود به درستی بروزرسانی کنیم، و همیشه داده‌های اضافی را شامل خواهد بود.

const Car = (function(){

    const privateCarsData = new WeakMap()

    function Car(name, color){
        // this => Car instance
        privateCarsData.set(this, {name, color})
    }

    Car.prototype.getCarName = function(){
        return privateCarsData.get(this).name
    }

    Car.prototype.getCarColor = function(){
        return privateCarsData.get(this).color
    }

    return Car
}())

این نسخه از WeakMap برای privateCarsData به جای یک آبجکت استفاده می‌کند. ما از نمونه Car به عنوان کلید استفاده خواهیم کرد، پس نیازی نیست که یک id منحصل به فرد برای هر نمونه قرار دهیم. این کلید، this خواهد بود و مقدار نیز، آبجکتی است که شامل نام و رنگ می‌شود. GetCarName و getCarColor با انتقال this به متد get، مقادیر خود را دریافت می‌کنند. و حالا، هر زمان که یک نمونه Car از بین برود، کلیدی که به آن مربوط است و در privateCarsData قرار دارد نیز، در جهت آزاد‌سازی حافظه، حذف می‌شود.

نتیجه گیری

هر زمان که فقط بخوهید از  از کلیدهای ابجکت استفاده کنید، Mapهای ضعیف بهترین گزینه شما خواهند بود. به این صورت، حافظه شما نیز بهینه‌سازی خواهد شد. گرچه، Mapهای ضعیف محدودیت‌هایی نیز دارند. مثلا نمی‌توانید از forEach() استفاده کنید، ویژگی size را نداید و همچنین متد clear() را نیز ندارید.

منبع

مقالات پیشنهادی

دییسبا فریمورکی بر پایه css و javascript

دییسبا یک سیستم طراحی وب است برای برنامه نویسی آسان و راحت برای کسانی که کمترین آشنایی با وب را دارند، یا حتی برای افراد حرفه ای دییسبا بر پایه سی اس...

15 کتابخانه جالب javascript و css

ماموریت ما در راکت این است که شما را بر اساس تکنولوژی روز طراحی وب به روز نگه داریم . به همین خاطر هم هست که ما تقریبا ماهانه یا چند هفته در میان پستی...

15 کتابخانه جالب javascript و css دی ۹۵

ماموریت ما در راکت این است که شما را بر اساس تکنولوژی روز طراحی وب به روز نگه داریم . به همین خاطر هم هست که ما تقریبا ماهانه یا چند هفته در میان پستی...

با استفاده از Billboardjs.js، نمودارهای داده‌ای بر پایه JavaScript بسازید

گرافیک و ویژگی‌های بصری، نقش حیاتی‌ای در پیشرفت محتویات وب بازی می‌کنند. با فناوری وب مدرن، اضافه کردن ویژگی‌های بصری سفارشی مانند آیکون‌های SVG در صف...