ES6 — Map vs Object — What and when?
Bài đăng này đã không được cập nhật trong 6 năm
Đặt vấn đề
Đọc xong tiêu đề này, bạn có thể tự hỏi - tại sao lại so sánh Map vs. Object mà không phải Map vs. Array, hay Object vs Set?
Chààà, cũng được thôi mà, song mình nhận thấy Map và Object có các trường hợp sử dụng rất giống nhau đòi hỏi chúng ta phải hiểu để có thể quyết định chọn dùng cái nào phù hợp trong mỗi trường hợp cụ thể.

Trong bài viết này chúng ta cùng nhau tìm hiểu nhé !
Concepts
What is Map?
Theo Mozilla:
Map is a data collection type (in a more fancy way — abstract data structure type), can iterate its elements in insertion order.
Với Map, dữ liệu được lưu theo dạng cặp: một unique key và value tương ứng với key đó.
Map được dùng để tra cứu và truy cập nhanh dữ liệu được lưu. Key và value có thể được lưu trong bất kì kiểu dữ liệu nào, không chỉ là chuỗi và số như ta hay gặp.
var myMap = new Map();
var keyString = 'a string',
    keyObj = {},
    keyFunc = function() {};
// đặt các values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, 'value associated with keyObj');
myMap.set(keyFunc, 'value associated with keyFunc');
myMap.size; // 3
// lấy các values
myMap.get(keyString);    // "value associated with 'a string'"
myMap.get(keyObj);       // "value associated with keyObj"
myMap.get(keyFunc);      // "value associated with keyFunc"
myMap.get('a string');   // "value associated with 'a string'"
                         // ?vì keyString === 'a string'
myMap.get({});           // undefined, vì keyObj !== {}
myMap.get(function() {}) // undefined, vì  keyFunc !== function () {}
What about Object?
Khi đọc bài viết này, mình nghĩ các bạn cũng chẳng xa lạ gì với Object nữa đúng không nào? 😸😸
Object là đối tượng? Hmm... Đúng nhưng chưa đủ 🤔🤔
Regular Objectis dictionary type of data collection.
Object cũng lưu data theo dạng key - value như Map. Với mỗi key trong Object (thông thường ta gọi là property) là độc nhất và là một giá trị đơn.
Ngoài ra, Object còn là một built-in prototype. Hầu hết tất cả các đối tượng đều là instances of Object, bao gồm cả Map.
Kết luận:
- Giống: lưu 
datadựa trên cùng một concept:key - value. - Khác:
Key:- In Object, the keys MUST be simple types (
integer||string||symbols). - In 
Map, the keys can be any data type (an object, an array, etc…). 
- In Object, the keys MUST be simple types (
 - Element order:
- Trong 
Map, thứ tự ban đầu của các phần tử được giữ nguyên,Objectthì không. - Inheritance: 
Mapis an instance ofObject 
 - Trong 
 
 
var map = new Map([[1,2],[3,4]]);
console.log(map instanceof Object); //true
var obj = new Object();
console.log(obj instanceof Map); //false
Đó mới chỉ là so sánh theo Concept thôi, tiếp theo chúng ta hãy xem cách khởi tạo Object và Map có gì đặc biệt nhé? 🤗🤗
How to construct
Object
Để tạo ra một đối tượng mới, đơn giản nhất ta chỉ cần dùng literal declaration:
var obj = {}; //Empty object
var obj = {id: 1, name: "Test object"}; 
//2 keys here: id maps to 1, and name maps to "Test object"
Hoặc qua constructor:
var obj = new Object(); //Empty Object
var obj = new Object;  //same result
Hay một cách khác với Object.prototype.create:
var obj = Object.create(null); //Empty Object
Special Notes
Không nên sử dụng Object.create(), trừ các trường hợp đặc biệt, ví dụ:
- Inheritance - Muốn kế thừa 
prototype objectđó, không cần phải định nghĩa lạiprototype: 
var Vehicle = {
    type: "General",
    display: function(){console.log(this.type);}
}
var Car = Object.create(Vehicle); //create a new Car inherits from Vehicle
Car.type = "Car"; //overwrite the property
Car.display();//"Car"
Vehicle.display();//still "General"
Những lý do ta nên tránh built-in constructor:
- Giảm performance
 - Dễ gây rối và dễ gây lỗi:
 
var obj = new Object(id: 1, name: "test") //Error - obviously
var obj1 = {id: 1, name: "test"};
var obj2 = new Object(obj1); //obj1 and obj2 points to the same one
obj2.id = 2;
console.log(obj1.id); //2
Map
Với Map, chỉ có một cách để tạo là sử dụng built-in constructor and new syntax.
var map = new Map(); //Empty Map
var map = new Map([[1,2],[2,3]]);
// map = {1=>2, 2=>3}
Map([iterable]) argument
Hàm Constructor() nhận vào một tham số hoặc là một mảng array hoặc là một iterable object (arrays with 2 elements [key, value]).
Basic capabilities
Accessing element
Với Map, lấy các giá trị phần tử qua Map.prototype.get(key):
map.get(1) // 2
Với Object thì chúng ta đã có quen thuộc:
obj.id //1
obj['id'] //1
Kiểm tra key có đang tồn tại hay không?
// MAP
map.has(1);//return boolean value:  true/false
// OBJECT
var isExist = obj.id === undefined;
// or
isExist = 'id' in obj;
//which will apply for inherited property as well.
// btw, using:
obj.hasOwnProperty()
// return boolean, NOT applied for inherited for that object
Add new element
// Map
map.set(4,5); //{1=>2, 2=>3, 4=>5}
// Object
obj['gender'] = 'female'; //{id: 1, name: "test", gender: "female"}
obj.gender = male; 
//Both is ok and will overwrite the existing mapped value if property already exists.
//{id: 1, name: "test", gender: "male"}
Như vậy, việc thêm một phần tử vào Map và Object, việc sử dụng key giúp ta không cần phải quét qua hết qua tất cả từng phần tử một 😛
Remove element
Object
Đối với một object, không có các built-in method nào để xóa các property của một object mà chúng ta sử dụng:
delete obj.id;
//{name: "test", gender: "male"}
delete operator trả về dạng boolean:
true: cho tất cả các trường hợp NGOẠI TRỪ đối với các own non-configurable propertyfalse: trongNON-strict mode; hoặc ném vềException errortrongstrict mode.
Một cách khác:
obj.id = undefined;
Hai cách này có khác nhau gì không? Xét về logic thì nó có khác một chút:
- 
delete(key): xóa hẳnkey(property) khỏiobjectđó luôn - 
setting
obj[key] = undefined: chỉ thay đổi giá trị củakeyđó thànhundefined,key(property) vẫn còn đóSide-effects:
Điều này dẫn tới một
side-effectvớifor...in..., nó sẽ thực hiện lặp qua tất cáckey, và kể cả cáckeycóvaluelàundefined. Và dĩ nhiên việc check property có tồn tại trong đối tượng đó hay không trong 2 cách trên sẽ cho ra 2 kết quả khác nhau 😄😄 ngoại trừ việc kiểm tra:obj.id === undefined;// (same result)Do đó, chúng ta nên chú ý khi quyết định sử dụng cách nào đó để xóa
propertycủa mộtobjectđể nâng caoperformance(giả sử vậy), và nói vui thì, tối ưu non đôi khi không đáng, mà dễ gây ra lỗi nữa đúng không nào 😀😀 
Map
Với Map, ta có các built-in methods hỗ trợ sẵn, chẳng hạn:
delete(key):methodnày trả về giá trị kiểuboolean, đúng khi key đó tồn tại trongMapvà đã được xóa thành công,falsekhikeyđó không tồn tại trongMap:
    var isDeleteSucceeded = map.delete(1); //{ 2=>3, 4=>5}
    console.log(isDeleteSucceeded); //true
- 
clear(): xóa TẤT CẢ các phần tử trongMap:map.clear(); //{}So sánh một chút, với case này,
Objectphải thực hiện lặp qua từngpropertyvà xóa nó lần lượt phải không nào 😄😄 
Nhận xét
Nhìn chung, cả hiệu năng của Map và Object trong việc Add/Remove element khá giống nhau. Xóa một key sẽ mất O(1), trong khi xóa tất cả các phần tử là O(n) với n là kích thước của Map/Object.
Tiếp theo, chúng ta xét thêm một khía cạnh nữa của Map và Object nhé 🤭🤭
Behaves
Getting the size
Điểm này Map khá giống với Array, Map sẽ tự động update size tự động, chúng ta chỉ cần gọi ra các build-in:
// Map
console.log(map.size);//0
Ngược lại, với Object, chúng ta phải dùng các build-in-chạy-bằng-cơm 😄😄:
let size = Object.keys(obj).length;
console.log(size); //2
Iterating
Map is built-in iterable — Object is not.
Cách bạn có thể check được loại đó có tính iterable hay không:
//typeof <obj>[Symbol.iterator] === 'function'
console.log(typeof obj[Symbol.iterator]); //undefined
console.log(typeof map[Symbol.iterator]); //function
Ouhh, khoan đã nào, ta cùng điểm qua một chút về đặc trưng của tính chất iterable nhé:
Ta nói Map có tính iterable nghĩa là trong Map ta có thể sử dụng for… of...:
//For map: { 2=>3, 4=>5}
for (const item of map){
    console.log(item); 
    //Array[2,3]
    //Array[4,5]
}
hay
for (const [key,value] of map){
    console.log(`key: ${key}, value: ${value}`);
    //key: 2, value: 3
    //key: 4, value: 5
}
hoặc built-in forEach():
map.forEach((value, key) => console.log(`key: ${key}, value: ${value}`));
//key: 2, value: 3
//key: 4, value: 5
Nhưng với Object, ta sử dụng for… in...:
{id: 1, name: "test"}
for (var key in obj){
   console.log(`key: ${key}, value: ${obj[key]}`);
   //key: id, value: 1
   //key: name, value: test
}
Hoặc dùng Object.keys(obj) để lấy về một mảng keys và tiến hành lặp .forEach() cho mảng đó:
Object.keys(obj).forEach(
    (key) => console.log(`key: ${key}, value: ${obj[key]}`)
);
//key: id, value: 1
//key: name, value: test
Okay, tới đây thì cho dù có khác về hướng xử lý đi chăng nữa, Object và Map vẫn có cấu trúc và hiệu năng như nhau. Được hỗ trợ bởi đa dạng các build-in methods thì có vẻ như Map chiếm ưu thế hơn. Bạn nghĩ như thế nào nếu ta ưu tiên dùng Map mà bỏ Object?
When to use Map? When to use Object?
Mặc dù ở phần trên, Map "được lòng" hơn Object một chút, song, vẫn có những trường hợp mà Object sẽ xử lý tốt hơn Map. Dù sao thì Object cũng là concept cơ bản nhất của Javascript cơ mà 😛😛
Khi nào dùng Object?
Object là lựa chọn tuyệt vời cho các case như:
- Structure Data: cấu trúc đơn giản, tất cả các 
keylà chuỗi || số nguyên. Bởi vì việc tạo một đối tượng đơn giản & truy cậppropertythuận tiện hơn nhiều so với việc tạo mộtMaprồi truy cập các phần tử qua.set(),.get()đúng không nào. - Logic separation: Dùng 
Objectkhi cần phân chia logic riêng biệt: 
var obj = {
    id: 1,
    name: "It's Me!",
    print: function(){
        return `Object Id: ${this.id}, with Name: ${this.name}`;
    }
}
console.log(obj.print());//Object Id: 1, with Name: It's Me.
- JSON support:
JSONhỗ trợ chuyển trực tiếp sangObjectvà ngược lại, cònMapthì chưa. Nên trường hợp mình cần thao tác nhiều vớiJSON(đa số là thế),Objectlà một lựa chọn phù hợp.
 
Khi nào dùng Map?
- 
Purely hash:
Maphoàn toàn là hàm băm;Objecthơn thế một chút vì còn hỗ trợ logic bên trong nữa. Song, việc dùngdelete operatorđôi khi gây ra nhiều vấn đề về hiệu năng (chúng ta sẽ cùng nhau tìm hiểu cụ thể nó trong một bài viết khác) - 
Key order:
Mapgiữ nguyên bản các thứ tự củakeys, nên sẽ được dùng trong các trường hợp thứ tự của cáckeycó ý nghĩa. Nó đảm bảostable iteration performancetrên tất cả các trình duyệt. - 
Large set of data:
Maphoạt động tốt hơn trong việc lưu trự các bộ dữ liệu lớn. 
Kết
Nhìn chung, việc sử dụng Map hay Object còn phụ thuộc vào nhiều yếu tố như kiểu dữ liệu, số lượng bản ghi hay các thao tác sẽ thực hiện với nó, từ đó ta sẽ có lựa chọn phù hợp.
Map và Object không thể thay thế nhau. Không có cái nào tốt hơn mà chỉ có cái phù hợp hơn với context thôi đúng không nào 😄😄
Cho tới thời điểm hiện tại, cá nhân mình sử dụng Object nhiều hơn, đa phần vì cũng tiện và dữ liệu của mình không quá phức tạp nữa. Khi bản ES6 ra đời thì thấy Map cũng hay ho và có vẻ thú vị nên mình viết bài này để chúng ta cùng nhau thảo luận.
Bạn thích dùng cái nào hơn, chia sẻ cho mình biết ở dưới comments nhé !

Cảm ơn vì đã đọc bài viết của mình. Nếu nó mang lại giá trị, tặng mình một upvote để có thêm động lực cho các bài tiếp theo nhé. Bạn có thể tham khảo thêm các bài viết về Tech tại đây.
Chúc bạn có một tuần làm việc hiệu quả !
Happy Coding ❤
Reference: Complete Javascript, Medium, Stackoverflow.
All rights reserved