Bản đồ so với đối tượng trong JavaScript


290

Tôi mới phát hiện ra chromestatus.com và sau khi mất vài giờ trong ngày, tôi đã tìm thấy mục tính năng này :

Bản đồ: Các đối tượng bản đồ là các bản đồ khóa / giá trị đơn giản.

Điều đó làm tôi bối rối. Các đối tượng JavaScript thông thường là từ điển, vậy Maptừ điển khác với từ điển như thế nào? Về mặt khái niệm, chúng giống hệt nhau (theo Sự khác biệt giữa Bản đồ và Từ điển là gì? )

Các tài liệu tham khảo chromestatus không giúp được gì:

Các đối tượng bản đồ là tập hợp các cặp khóa / giá trị trong đó cả khóa và giá trị có thể là các giá trị ngôn ngữ ECMAScript tùy ý. Một giá trị khóa riêng biệt chỉ có thể xảy ra trong một cặp khóa / giá trị trong bộ sưu tập của Bản đồ. Các giá trị khóa khác biệt được phân biệt bằng thuật toán so sánh được chọn khi Bản đồ được tạo.

Một đối tượng Map có thể lặp lại các phần tử của nó theo thứ tự chèn. Đối tượng bản đồ phải được thực hiện bằng cách sử dụng bảng băm hoặc các cơ chế khác, trung bình, cung cấp thời gian truy cập là tuyến tính trên số lượng phần tử trong bộ sưu tập. Các cấu trúc dữ liệu được sử dụng trong đặc tả đối tượng Map này chỉ nhằm mô tả ngữ nghĩa quan sát được yêu cầu của các đối tượng Map. Nó không có ý định là một mô hình thực hiện khả thi.

Tôi vẫn nghe như một vật thể đối với tôi, vì vậy rõ ràng tôi đã bỏ lỡ điều gì đó.

Tại sao JavaScript đạt được một Mapđối tượng (được hỗ trợ tốt) ? Nó làm gì?


Câu trả lời:


286

Theo mozilla:

Một đối tượng Map có thể lặp lại các phần tử của nó theo thứ tự chèn - một vòng lặp for..of sẽ trả về một mảng [key, value] cho mỗi lần lặp.

Các đối tượng tương tự như Bản đồ ở chỗ cả hai đều cho phép bạn đặt khóa thành giá trị, truy xuất các giá trị đó, xóa khóa và phát hiện xem có thứ gì được lưu trữ tại khóa hay không. Do đó, Đối tượng đã được sử dụng làm Bản đồ trong lịch sử; tuy nhiên, có những khác biệt quan trọng giữa Đối tượng và Bản đồ giúp sử dụng Bản đồ tốt hơn.

Một đối tượng có một nguyên mẫu, vì vậy có các khóa mặc định trong bản đồ. Tuy nhiên, điều này có thể được bỏ qua bằng cách sử dụng map = Object.create (null). Các khóa của Đối tượng là Chuỗi, trong đó chúng có thể là bất kỳ giá trị nào cho Bản đồ. Bạn có thể dễ dàng lấy kích thước của Bản đồ trong khi bạn phải theo dõi kích thước thủ công cho Đối tượng.

Sử dụng bản đồ trên các đối tượng khi các khóa không xác định cho đến thời gian chạy và khi tất cả các khóa là cùng loại và tất cả các giá trị là cùng một loại.

Sử dụng các đối tượng khi có logic hoạt động trên các yếu tố riêng lẻ.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

Tính lặp lại theo thứ tự là một tính năng đã được các nhà phát triển mong muốn từ lâu, một phần vì nó đảm bảo hiệu suất tương tự trong tất cả các trình duyệt. Vì vậy, với tôi đó là một lớn.

Các myMap.has(key)phương pháp sẽ đặc biệt tiện dụng, và cũng là myMap.sizebất động sản.


13
Một nhược điểm, có lẽ là Bản đồ đòi hỏi nhiều bộ nhớ hơn (tuy nhiên trong cùng một thứ tự cường độ) để duy trì thứ tự chèn.
John Kurlak

4
Bản đồ có các tính năng khác ngoài tính trật tự đã được đề cập ở đây (sử dụng bất kỳ đối tượng nào làm khóa, tách khóa và đạo cụ, v.v.), nhưng FWIW trong một số trường hợp thứ tự lặp lại của các thuộc tính đối tượng đơn giản được xác định bởi ES2015. Xem stackoverflow.com/a/32149345 .
JMM

2
Tôi không hiểu ý nghĩa, khi bạn nói, Một đối tượng có nguyên mẫu, do đó, có các khóa mặc định trong bản đồ. Tuy nhiên, điều này có thể được bỏ qua bằng cách sử dụngmap = Object.create(null) . Khóa mặc định là gì? Làm thế nào các phím có liên quan đến Object.prototype?
overexchange

4
Các thử nghiệm của tôi trong Chrome cho thấy rằng các bản đồ không sử dụng bất kỳ lượng bộ nhớ đáng kể nào để duy trì trật tự. Tôi nghĩ rằng đã có thêm 0,1KB cho một triệu khóa và tôi không nghĩ đó là để duy trì trật tự. Tuy nhiên, ~ 0,1KB dường như là một chi phí không đổi. Thay vào đó, nếu bạn tạo một triệu bản đồ bằng một phím và so sánh nó lớn hơn nhiều so với đối tượng.
jgmjgm

2
@luxon bạn đang tạo một đối tượng ở đó. Thông số ES6 yêu cầu newtoán tử được sử dụng với Mapký hiệu tức là new Mapđể nó tạo đối tượng bản đồ. var a = {}là viết tắt của (có nghĩa tương đương với)var a = Object.create(Object.prototype)
dudewad

104

Sự khác biệt chính là các Đối tượng chỉ hỗ trợ các khóa chuỗi trong đó Bản đồ hỗ trợ nhiều hơn hoặc ít hơn bất kỳ loại khóa nào.

Nếu tôi làm obj[123] = truevà sau Object.keys(obj)đó tôi sẽ nhận được ["123"]hơn là [123]. Một bản đồ sẽ bảo tồn loại khóa và trả lại [123], đó là điều tuyệt vời. Bản đồ cũng cho phép bạn sử dụng Đối tượng làm khóa. Theo truyền thống để làm điều này, bạn sẽ phải cung cấp cho các đối tượng một số loại định danh duy nhất để băm chúng (Tôi không nghĩ rằng tôi đã từng thấy bất cứ điều gì như getObjectIdtrong JS là một phần của tiêu chuẩn). Bản đồ cũng đảm bảo giữ gìn trật tự vì vậy tất cả đều tốt hơn để bảo quản và đôi khi có thể giúp bạn tiết kiệm được một vài cách.

Giữa bản đồ và đối tượng trong thực tế có một số ưu và nhược điểm. Các đối tượng đạt được cả ưu điểm và nhược điểm khi được tích hợp rất chặt chẽ vào cốt lõi của JavaScript, điều này làm cho chúng khác biệt với Bản đồ đáng kể ngoài sự khác biệt về hỗ trợ chính.

Một lợi thế ngay lập tức là bạn có hỗ trợ cú pháp cho các Đối tượng giúp dễ dàng truy cập các phần tử. Bạn cũng có hỗ trợ trực tiếp cho nó với JSON. Khi được sử dụng như một hàm băm, thật khó chịu khi lấy một đối tượng mà không có thuộc tính nào cả. Theo mặc định, nếu bạn muốn sử dụng Đối tượng làm bảng băm, chúng sẽ bị ô nhiễm và bạn thường sẽ phải gọi hasOwnPropertychúng khi truy cập các thuộc tính. Bạn có thể xem ở đây làm thế nào theo mặc định Các đối tượng bị ô nhiễm và cách tạo các đối tượng không bị ô nhiễm để sử dụng làm băm:

({}).toString
    toString() { [native code] }
JSON.parse('{}').toString
    toString() { [native code] }
(Object.create(null)).toString
    undefined
JSON.parse('{}', (k,v) => (typeof v === 'object' && Object.setPrototypeOf(v, null) ,v)).toString
    undefined

Ô nhiễm trên các đối tượng không chỉ là một cái gì đó làm cho mã trở nên khó chịu hơn, chậm hơn, vv mà còn có thể có hậu quả tiềm tàng cho bảo mật.

Các đối tượng không phải là bảng băm thuần túy nhưng đang cố gắng làm nhiều hơn nữa. Bạn bị đau đầu như thế hasOwnProperty, không thể có được độ dài dễ dàng ( Object.keys(obj).length) và cứ thế. Các đối tượng không hoàn toàn được sử dụng làm bản đồ băm mà còn là Đối tượng có thể mở rộng động và do đó khi bạn sử dụng chúng làm các bảng băm thuần túy sẽ phát sinh vấn đề.

So sánh / Danh sách các hoạt động phổ biến khác nhau:

    Object:
       var o = {};
       var o = Object.create(null);
       o.key = 1;
       o.key += 10;
       for(let k in o) o[k]++;
       var sum = 0;
       for(let v of Object.values(m)) sum += v;
       if('key' in o);
       if(o.hasOwnProperty('key'));
       delete(o.key);
       Object.keys(o).length
    Map:
       var m = new Map();
       m.set('key', 1);
       m.set('key', m.get('key') + 10);
       m.foreach((k, v) => m.set(k, m.get(k) + 1));
       for(let k of m.keys()) m.set(k, m.get(k) + 1);
       var sum = 0;
       for(let v of m.values()) sum += v;
       if(m.has('key'));
       m.delete('key');
       m.size();

Có một vài lựa chọn, cách tiếp cận, phương pháp khác, vv với những thăng trầm khác nhau (hiệu suất, ngắn gọn, di động, có thể mở rộng, v.v.). Các đối tượng hơi lạ là cốt lõi của ngôn ngữ, do đó bạn có rất nhiều phương thức tĩnh để làm việc với chúng.

Bên cạnh lợi thế của Bản đồ bảo tồn các loại khóa cũng như có thể hỗ trợ những thứ như các đối tượng như các khóa mà chúng được cách ly khỏi các tác dụng phụ mà các đối tượng có nhiều. Bản đồ là một hàm băm thuần túy, không có sự nhầm lẫn về việc cố gắng trở thành một đối tượng cùng một lúc. Bản đồ cũng có thể dễ dàng mở rộng với các chức năng proxy. Các đối tượng hiện có một lớp Proxy tuy nhiên hiệu suất và việc sử dụng bộ nhớ rất tệ, trên thực tế, việc tạo proxy của riêng bạn trông giống như Map cho các đối tượng hiện hoạt động tốt hơn Proxy.

Một bất lợi đáng kể cho Maps là chúng không được hỗ trợ trực tiếp với JSON. Phân tích cú pháp là có thể nhưng có một số hangouts:

JSON.parse(str, (k,v) => {
    if(typeof v !== 'object') return v;
    let m = new Map();
    for(k in v) m.set(k, v[k]);
    return m;
});

Ở trên sẽ giới thiệu một hit hiệu suất nghiêm trọng và cũng sẽ không hỗ trợ bất kỳ phím chuỗi nào. Mã hóa JSON thậm chí còn khó khăn và có vấn đề hơn (đây là một trong nhiều cách tiếp cận):

// An alternative to this it to use a replacer in JSON.stringify.
Map.prototype.toJSON = function() {
    return JSON.stringify({
        keys: Array.from(this.keys()),
        values: Array.from(this.values())
    });
};

Điều này không tệ lắm nếu bạn hoàn toàn sử dụng Bản đồ nhưng sẽ gặp vấn đề khi bạn trộn các loại hoặc sử dụng các giá trị không vô hướng làm khóa (không phải JSON hoàn hảo với loại vấn đề như vậy, đó là tham chiếu đối tượng hình tròn IE). Tôi đã không kiểm tra nó nhưng rất có thể nó sẽ ảnh hưởng nghiêm trọng đến hiệu suất so với việc xâu chuỗi.

Các ngôn ngữ kịch bản lệnh khác thường không có vấn đề như vậy vì chúng có các kiểu không vô hướng rõ ràng cho Map, Object và Array. Phát triển web thường là một vấn đề khó khăn với các loại không vô hướng, trong đó bạn phải xử lý các vấn đề như PHP hợp nhất Array / Map với Object bằng A / M cho các thuộc tính và JS hợp nhất Map / Object với M / O mở rộng Array. Hợp nhất các loại phức tạp là nguyên nhân của các ngôn ngữ kịch bản cấp cao.

Cho đến nay đây phần lớn là các vấn đề xung quanh việc thực hiện nhưng hiệu suất cho các hoạt động cơ bản cũng quan trọng. Hiệu suất cũng phức tạp vì nó phụ thuộc vào động cơ và cách sử dụng. Làm các bài kiểm tra của tôi với một hạt muối vì tôi không thể loại trừ bất kỳ sai lầm nào (tôi phải vội vàng điều này). Bạn cũng nên chạy thử nghiệm của riêng mình để xác nhận vì tôi chỉ kiểm tra các kịch bản đơn giản rất cụ thể để chỉ ra một dấu hiệu sơ bộ. Theo các thử nghiệm trong Chrome đối với các đối tượng / bản đồ rất lớn, hiệu suất của các đối tượng kém hơn do xóa mà dường như bằng cách nào đó tỷ lệ với số lượng khóa thay vì O (1):

Object Set Took: 146
Object Update Took: 7
Object Get Took: 4
Object Delete Took: 8239
Map Set Took: 80
Map Update Took: 51
Map Get Took: 40
Map Delete Took: 2

Chrome rõ ràng có một lợi thế mạnh mẽ với việc nhận và cập nhật nhưng hiệu suất xóa là khủng khiếp. Bản đồ sử dụng một lượng bộ nhớ nhỏ hơn trong trường hợp này (trên cao) nhưng chỉ với một Đối tượng / Bản đồ được thử nghiệm với hàng triệu phím, tác động của chi phí đối với bản đồ không được thể hiện rõ. Với các đối tượng quản lý bộ nhớ dường như cũng giải phóng sớm hơn nếu tôi đọc chính xác hồ sơ có thể là một lợi ích có lợi cho các đối tượng.

Trong Firefox cho điểm chuẩn cụ thể này, nó là một câu chuyện khác:

Object Set Took: 435
Object Update Took: 126
Object Get Took: 50
Object Delete Took: 2
Map Set Took: 63
Map Update Took: 59
Map Get Took: 33
Map Delete Took: 1

Tôi phải ngay lập tức chỉ ra rằng trong việc xóa điểm chuẩn cụ thể này khỏi các đối tượng trong Firefox không gây ra bất kỳ vấn đề nào, tuy nhiên trong các điểm chuẩn khác, nó đã gây ra sự cố đặc biệt là khi có nhiều khóa giống như trong Chrome. Bản đồ rõ ràng là vượt trội trong Firefox cho các bộ sưu tập lớn.

Tuy nhiên đây không phải là kết thúc của câu chuyện, còn nhiều vật thể hay bản đồ nhỏ thì sao? Tôi đã thực hiện một điểm chuẩn nhanh về điều này nhưng không phải là một điểm tổng hợp (cài đặt / nhận) trong đó hoạt động tốt nhất với một số lượng nhỏ các phím trong các thao tác trên. Bài kiểm tra này là về bộ nhớ và khởi tạo.

Map Create: 69    // new Map
Object Create: 34 // {}

Một lần nữa những con số này khác nhau nhưng về cơ bản Object có một đầu mối tốt. Trong một số trường hợp, khả năng dẫn đối tượng trên bản đồ là cực kỳ (tốt hơn ~ 10 lần) nhưng trung bình nó tốt hơn khoảng 2-3 lần. Có vẻ như gai hiệu suất cực đoan có thể làm việc cả hai cách. Tôi chỉ thử nghiệm điều này trong Chrome và tạo để sử dụng bộ nhớ và chi phí bộ nhớ. Tôi khá ngạc nhiên khi thấy trong Chrome, có vẻ như Bản đồ với một phím sử dụng bộ nhớ nhiều hơn khoảng 30 lần so với Đối tượng có một phím.

Để kiểm tra nhiều đối tượng nhỏ với tất cả các hoạt động trên (4 phím):

Chrome Object Took: 61
Chrome Map Took: 67
Firefox Object Took: 54
Firefox Map Took: 139

Về mặt phân bổ bộ nhớ, chúng hoạt động giống nhau về giải phóng / GC nhưng Map sử dụng bộ nhớ gấp 5 lần. Thử nghiệm này đã sử dụng 4 phím trong đó như trong thử nghiệm cuối cùng, tôi chỉ đặt một phím để điều này giải thích việc giảm chi phí bộ nhớ. Tôi đã chạy thử nghiệm này một vài lần và Map / Object có thể nhiều hơn hoặc ít hơn cả cổ và cổ cho Chrome về tốc độ tổng thể. Trong Firefox cho các Đối tượng nhỏ, có một lợi thế về hiệu suất nhất định so với các bản đồ nói chung.

Điều này tất nhiên không bao gồm các tùy chọn cá nhân có thể thay đổi dữ dội. Tôi sẽ không tư vấn tối ưu hóa vi mô với những số liệu này. Những gì bạn có thể nhận ra là điều này, theo nguyên tắc thông thường, hãy xem xét Bản đồ mạnh mẽ hơn cho các cửa hàng và đối tượng giá trị khóa rất lớn cho các cửa hàng giá trị khóa nhỏ.

Ngoài ra, chiến lược tốt nhất với hai chiến lược này là thực hiện nó và chỉ làm cho nó hoạt động trước. Khi định hình, điều quan trọng là phải nhớ rằng đôi khi những điều bạn không nghĩ sẽ chậm khi nhìn vào chúng có thể rất chậm vì các quirks động cơ như đã thấy trong trường hợp xóa khóa đối tượng.


Thiếu tính tuần tự hóa đã là một nỗi đau thực sự đối với nhiều nhà phát triển. Nhìn vào phần nâng cao của Làm cách nào để duy trì Bản đồ ES6 trong kho lưu trữ cục bộ (hoặc ở nơi khác)? làm thế nào để bạn JSON.opesify một bản đồ ES6? .
Franklin Yu

Là số tính bằng milisecond, byte hoặc tổng số đối tượng?
StefansArya

Took so ms (một cái gì đó là viết tắt của một cái gì đó được sử dụng, vì vậy nó sử dụng hết thời gian trong trường hợp này). Mặc dù đây là một bài kiểm tra cũ và tôi không có mã điểm chuẩn nữa. Bây giờ có lẽ rất khác. Vấn đề xóa ví dụ tôi tin là đã được sửa.
jgmjgm

27

Tôi không nghĩ rằng những điểm sau đây đã được đề cập trong các câu trả lời cho đến nay và tôi nghĩ chúng đáng được đề cập.


Bản đồ có thể lớn hơn

Trong chrome, tôi có thể nhận được 16,7 triệu cặp khóa / giá trị Mapso với 11,1 triệu với một đối tượng thông thường. Gần như chính xác hơn 50% cặp với a Map. Cả hai đều chiếm khoảng 2GB bộ nhớ trước khi chúng gặp sự cố và vì vậy tôi nghĩ có thể phải làm gì với việc giới hạn bộ nhớ bằng chrome ( Chỉnh sửa : Yep, thử điền 2 Mapsvà bạn chỉ nhận được tới 8,3 triệu cặp mỗi lần trước khi nó gặp sự cố). Bạn có thể tự kiểm tra mã này bằng mã này (rõ ràng là chạy chúng riêng lẻ và không đồng thời):

var m = new Map();
var i = 0;
while(1) {
    m.set(((10**30)*Math.random()).toString(36), ((10**30)*Math.random()).toString(36));
    i++;
    if(i%1000 === 0) { console.log(i/1000,"thousand") }
}
// versus:
var m = {};
var i = 0;
while(1) {
    m[((10**30)*Math.random()).toString(36)] = ((10**30)*Math.random()).toString(36);
    i++;
    if(i%1000 === 0) { console.log(i/1000,"thousand") }
}

Các đối tượng đã có một số thuộc tính / khóa

Điều này đã làm tôi vấp ngã trước đây. Đối tượng thường xuyên có toString, constructor, valueOf, hasOwnProperty, isPrototypeOfvà một loạt các thuộc tính tồn tại trước đó khác. Điều này có thể không phải là một vấn đề lớn đối với hầu hết các trường hợp sử dụng, nhưng nó đã gây ra vấn đề cho tôi trước đây.

Bản đồ có thể chậm hơn:

Do .getchi phí gọi hàm và thiếu tối ưu hóa nội bộ, Map có thể chậm hơn đáng kể so với một đối tượng JavaScript cũ đơn giản cho một số tác vụ.


1
Theo bạn, ngữ nghĩa có hiệu suất vượt trội hơn ở đây? Bản đồ nghe có vẻ hoàn hảo nếu bạn cần một cuốn từ điển, nhưng thật khó chấp nhận việc tra cứu chậm hơn. Không phải là một tra cứu nhanh toàn bộ điểm của từ điển?
dùng2954463

3
Tôi chắc chắn muốn đi với các đối tượng cũ đồng bằng nếu bạn đang sử dụng tốt với 11 triệu cặp khóa / giá trị và không quan tâm đến các phím tồn tại trước đó như toString, constructor, vv (tức là phím của bạn rất khó có khả năng va chạm với họ). Chúng dễ dàng hơn để làm việc với - ví dụ như gia tăng là obj[i] = (obj[i] || 0) + 1, trong khi với Mapmap.set(i, (map.get(i) || 0) + 1)vẫn không quá tệ, nhưng nó chỉ cho thấy mọi thứ có thể trở nên lộn xộn như thế nào. Bản đồ chắc chắn có các trường hợp sử dụng của chúng, nhưng thường thì một đối tượng đơn giản sẽ làm.

Lưu ý rằng bạn có thể thoát khỏi sự mặc định toString, constructor, (vv) thuộc tính đối tượng bằng cách viết obj = Object.create(null)thay vì obj = {}.

17

Các đối tượng có thể hoạt động giống như từ điển vì Javascript được gõ động, cho phép bạn thêm hoặc xóa các thuộc tính trên một đối tượng bất cứ lúc nào.

Nhưng Map()chức năng mới tốt hơn nhiều vì:

  • Nó cung cấp get, set, has, và deletephương pháp.
  • Chấp nhận bất kỳ loại nào cho các phím thay vì chỉ các chuỗi.
  • Cung cấp một trình vòng lặp để for-ofsử dụng dễ dàng và duy trì thứ tự kết quả.
  • Không có trường hợp cạnh với nguyên mẫu và các thuộc tính khác hiển thị trong quá trình lặp hoặc sao chép.
  • Nó hỗ trợ hàng triệu mặt hàng.
  • Rất nhanh và tiếp tục nhanh hơn khi các công cụ javascript trở nên tốt hơn.

99% thời gian bạn chỉ nên sử dụng a Map(). Tuy nhiên, nếu bạn chỉ sử dụng các khóa dựa trên chuỗi và cần hiệu suất đọc tối đa, thì các đối tượng có thể là lựa chọn tốt hơn.

Chi tiết là (hầu hết tất cả) các công cụ javascript biên dịch các đối tượng xuống các lớp C ++ trong nền. Các loại này được lưu trữ và sử dụng lại theo "phác thảo" của chúng, vì vậy khi bạn tạo một đối tượng mới có cùng thuộc tính chính xác, công cụ sẽ sử dụng lại một lớp nền hiện có. Đường dẫn truy cập cho các thuộc tính trên các lớp này rất tối ưu và nhanh hơn nhiều so với tra cứu của a Map().

Việc thêm hoặc xóa một thuộc tính làm cho lớp sao lưu được lưu trữ được biên dịch lại, đó là lý do tại sao việc sử dụng một đối tượng làm từ điển với nhiều bổ sung và xóa khóa rất chậm, nhưng đọc và gán các khóa hiện có mà không thay đổi đối tượng là rất nhanh.

Vì vậy, nếu bạn có khối lượng công việc nặng một lần đọc với các phím chuỗi thì hãy sử dụng objectnhư một từ điển hiệu suất cao chuyên dụng, nhưng đối với mọi thứ khác, hãy sử dụng a Map().


Object cũng cung cấp get set has deletechức năng, nó cũng không quá thanh lịch (nhưng cũng không tệ). Cách nào Mapdễ sử dụng hơn cho việc lặp lại? Không chắc tôi có thể đồng ý.
Andrew

@Andrew Tôi đang sử dụng các phương thức và chức năng cũng khác nhau tùy thuộc vào những gì bạn đang sử dụng và kết quả. Lặp lại dễ dàng hơn vì các thuộc tính nguyên mẫu và nguyên gốc không hiển thị trong vòng lặp và sử dụng trình lặp JS bình thường duy trì cùng một thứ tự.
Mani Gandham

11

Ngoài các câu trả lời khác, tôi thấy rằng Bản đồ khó sử dụng và dài dòng hơn so với các đối tượng.

obj[key] += x
// vs.
map.set(map.get(key) + x)

Điều này rất quan trọng, bởi vì mã ngắn hơn để đọc nhanh hơn, biểu cảm trực tiếp hơn và được lưu giữ tốt hơn trong đầu lập trình viên .

Một khía cạnh khác: vì set () trả về bản đồ, không phải giá trị, nên không thể xâu chuỗi các bài tập.

foo = obj[key] = x;  // Does what you expect
foo = map.set(key, x)  // foo !== x; foo === map

Bản đồ gỡ lỗi cũng đau đớn hơn. Dưới đây, bạn thực sự không thể thấy những phím nào trong bản đồ. Bạn sẽ phải viết mã để làm điều đó.

Chúc may mắn khi đánh giá một Iterator Map

Các đối tượng có thể được đánh giá bởi bất kỳ IDE nào:

WebStorm đánh giá một đối tượng


4
Với tất cả điều này, có vẻ như bản đồ là một tối ưu hóa sớm.
PRman

10

Tóm lược:

  • Object: Cấu trúc dữ liệu trong đó dữ liệu được lưu trữ dưới dạng cặp giá trị chính. Trong một đối tượng, khóa phải là một số, chuỗi hoặc ký hiệu. Giá trị có thể là bất cứ thứ gì, vì vậy các đối tượng, hàm khác, v.v ... Một đối tượng là một cấu trúc dữ liệu không theo thứ tự , tức là trình tự chèn các cặp giá trị khóa không được ghi nhớ
  • ES6 Map: Cấu trúc dữ liệu trong đó dữ liệu được lưu trữ dưới dạng cặp giá trị chính. Trong đó một khóa duy nhất ánh xạ tới một giá trị . Cả khóa và giá trị có thể ở bất kỳ loại dữ liệu nào . Bản đồ là một cấu trúc dữ liệu có thể lặp lại, điều này có nghĩa là trình tự chèn được ghi nhớ và chúng ta có thể truy cập các phần tử trong ví dụ như một for..ofvòng lặp

Sự khác biệt chính:

  • A Mapđược sắp xếp và lặp lại, trong khi một đối tượng không được sắp xếp và không thể lặp lại

  • Chúng ta có thể đặt bất kỳ loại dữ liệu nào làm Mapkhóa, trong khi các đối tượng chỉ có thể có một số, chuỗi hoặc ký hiệu làm khóa.

  • Một Mapkế thừa từ Map.prototype. Điều này cung cấp tất cả các loại chức năng và thuộc tính tiện ích giúp làm việc với Mapcác đối tượng dễ dàng hơn nhiều.

Thí dụ:

vật:

let obj = {};

// adding properties to a object
obj.prop1 = 1;
obj[2]    =  2;

// getting nr of properties of the object
console.log(Object.keys(obj).length)

// deleting a property
delete obj[2]

console.log(obj)

Bản đồ:

const myMap = new Map();

const keyString = 'a string',
    keyObj = {},
    keyFunc = function() {};

// setting the values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, 'value associated with keyObj');
myMap.set(keyFunc, 'value associated with keyFunc');

console.log(myMap.size); // 3

// getting the values
console.log(myMap.get(keyString));    // "value associated with 'a string'"
console.log(myMap.get(keyObj));       // "value associated with keyObj"
console.log(myMap.get(keyFunc));      // "value associated with keyFunc"

console.log(myMap.get('a string'));   // "value associated with 'a string'"
                         // because keyString === 'a string'
console.log(myMap.get({}));           // undefined, because keyObj !== {}
console.log(myMap.get(function() {})) // undefined, because keyFunc !== function () {}

nguồn: MDN


4

Ngoài ra để có thể lặp lại theo thứ tự được xác định rõ và khả năng sử dụng các giá trị tùy ý làm khóa (ngoại trừ -0), bản đồ có thể hữu ích vì các lý do sau:

  • Thông số kỹ thuật thực thi các hoạt động bản đồ là trung bình tuyến tính.

    Bất kỳ triển khai đối tượng không ngu ngốc nào cũng sẽ sử dụng bảng băm hoặc tương tự, vì vậy việc tra cứu thuộc tính có thể sẽ không đổi trên trung bình. Sau đó, các đối tượng có thể thậm chí nhanh hơn bản đồ. Nhưng điều đó không được yêu cầu bởi thông số kỹ thuật.

  • Đối tượng có thể có những hành vi bất ngờ khó chịu.

    Ví dụ: giả sử bạn không đặt bất kỳ thuộc footính nào cho một đối tượng mới được tạo obj, do đó bạn sẽ obj.footrả về không xác định. Nhưng foocó thể được xây dựng tài sản thừa kế từ Object.prototype. Hoặc bạn cố gắng tạo obj.foobằng cách sử dụng một bài tập, nhưng một số setter đang Object.prototypechạy thay vì lưu trữ giá trị của bạn.

    Bản đồ ngăn chặn những điều này. Vâng, trừ khi một số kịch bản gây rối với Map.prototype. Và Object.create(null)cũng sẽ hoạt động, nhưng sau đó bạn mất cú pháp khởi tạo đối tượng đơn giản.


4

Khi nào nên sử dụng Bản đồ thay vì Đối tượng JavaScript đơn giản?

Đối tượng JavaScript đơn giản {key: 'value'} chứa dữ liệu có cấu trúc. Nhưng đối tượng JS đơn giản có những hạn chế của nó:

  1. Chỉ các chuỗi và ký hiệu có thể được sử dụng làm khóa của Đối tượng. Nếu chúng ta sử dụng bất kỳ thứ gì khác nói, các số làm khóa của một đối tượng thì trong khi truy cập các khóa đó, chúng ta sẽ thấy các khóa đó sẽ được chuyển đổi thành các chuỗi ngầm khiến chúng ta mất tính nhất quán của các loại. tên const = {1: 'một', 2: 'hai'}; Object.keys (tên); // ['1', '2']

  2. Có khả năng vô tình ghi đè các thuộc tính được kế thừa từ các nguyên mẫu bằng cách viết mã định danh JS làm tên chính của một đối tượng (ví dụ: toString, constructor, v.v.)

  3. Một đối tượng khác không thể được sử dụng làm khóa của một đối tượng, vì vậy không có thông tin bổ sung nào có thể được viết cho một đối tượng bằng cách viết đối tượng đó là khóa của đối tượng khác và giá trị của đối tượng khác sẽ chứa thông tin bổ sung

  4. Đối tượng không phải là trình vòng lặp

  5. Kích thước của một đối tượng không thể được xác định trực tiếp

Những hạn chế này của Đối tượng được giải quyết bằng Bản đồ nhưng chúng ta phải coi Bản đồ là phần bổ sung cho Đối tượng thay vì thay thế. Về cơ bản Map chỉ là mảng các mảng nhưng chúng ta phải truyền mảng đó cho đối tượng Map làm đối số với từ khóa mới, nếu không chỉ với mảng mảng thì các thuộc tính và phương thức hữu ích của Map không có sẵn. Và hãy nhớ các cặp khóa-giá trị bên trong mảng các mảng hoặc Bản đồ phải được phân tách bằng dấu phẩy, không có dấu hai chấm như trong các đối tượng đơn giản.

3 mẹo để quyết định nên sử dụng Bản đồ hay Đối tượng:

  1. Sử dụng bản đồ trên các đối tượng khi các khóa không xác định cho đến khi hết thời gian vì các khóa được tạo bởi đầu vào của người dùng hoặc vô tình có thể phá vỡ mã sử dụng đối tượng nếu các khóa đó ghi đè lên các thuộc tính được kế thừa của đối tượng, vì vậy bản đồ sẽ an toàn hơn trong các trường hợp đó. Cũng sử dụng bản đồ khi tất cả các khóa là cùng loại và tất cả các bản đồ là cùng loại.

  2. Sử dụng bản đồ nếu có nhu cầu lưu trữ các giá trị nguyên thủy làm khóa.

  3. Sử dụng các đối tượng nếu chúng ta cần hoạt động trên các yếu tố cá nhân.

Lợi ích của việc sử dụng Bản đồ là:

1. Bản đồ chấp nhận bất kỳ loại khóa nào và giữ nguyên loại khóa:

Chúng tôi biết rằng nếu khóa của đối tượng không phải là một chuỗi hoặc ký hiệu thì JS sẽ ngầm biến nó thành một chuỗi. Ngược lại, Map chấp nhận bất kỳ loại khóa nào: chuỗi, số, boolean, ký hiệu, v.v. và Map giữ nguyên loại khóa gốc. Ở đây chúng tôi sẽ sử dụng số làm khóa trong Bản đồ và nó sẽ vẫn là một số:

const numbersMap= new Map();

numbersMap.set(1, 'one');

numbersMap.set(2, 'two');

const keysOfMap= [...numbersMap.keys()];

console.log(keysOfMap);                        // [1, 2]

Trong Bản đồ, chúng ta thậm chí có thể sử dụng toàn bộ một đối tượng làm khóa. Có thể đôi khi chúng ta muốn lưu trữ một số dữ liệu liên quan đến đối tượng, mà không đính kèm dữ liệu này vào bên trong đối tượng để chúng ta có thể làm việc với các đối tượng nạc nhưng muốn lưu trữ một số thông tin về đối tượng. Trong những trường hợp đó, chúng ta cần sử dụng Bản đồ để có thể đặt Đối tượng làm khóa và dữ liệu liên quan của đối tượng làm giá trị.

const foo= {name: foo};

const bar= {name: bar};

const kindOfMap= [[foo, 'Foo related data'], [bar, 'Bar related data']];

Nhưng nhược điểm của phương pháp này là sự phức tạp của việc truy cập giá trị theo khóa, vì chúng ta phải lặp qua toàn bộ mảng để có được giá trị mong muốn.

function getBy Key(kindOfMap, key) {
    for (const [k, v]  of kindOfMap) {
        if(key === k) {
            return v;
        }
    }
    return undefined;
}

getByKey(kindOfMap, foo);            // 'Foo related data'

Chúng tôi có thể giải quyết vấn đề này khi không truy cập trực tiếp vào giá trị bằng cách sử dụng Bản đồ phù hợp.

const foo= {name: 'foo'};

const bar= {name: 'bar'};

const myMap= new Map();

myMap.set(foo, 'Foo related data');
myMap.set(bar, 'Bar related data');

console.log(myMap.get(foo));            // 'Foo related data'

Chúng ta có thể thực hiện điều này bằng WeakMap, chỉ cần viết, const myMap = new WeakMap (). Sự khác biệt giữa Map và WeakMap là WeakMap cho phép thu gom rác các khóa (ở đây là các đối tượng) để ngăn chặn rò rỉ bộ nhớ, WeakMap chỉ chấp nhận các đối tượng làm khóa và WeakMap đã giảm tập hợp các phương thức.

2. Bản đồ không hạn chế tên chính:

Đối với các đối tượng JS đơn giản, chúng ta có thể vô tình ghi đè lên thuộc tính được kế thừa từ nguyên mẫu và nó có thể nguy hiểm. Ở đây chúng ta sẽ ghi đè lên thuộc tính toString () của đối tượng tác nhân:

const actor= {
    name: 'Harrison Ford',
    toString: 'Actor: Harrison Ford'
};

Bây giờ, hãy xác định một fn isPlainObject () để xác định xem đối số được cung cấp có phải là một đối tượng đơn giản hay không và fn này sử dụng phương thức toString () để kiểm tra nó:

function isPlainObject(value) {
    return value.toString() === '[object Object]';
}

isPlainObject(actor);        // TypeError : value.toString is not a function

// this is because inside actor object toString property is a string instead of inherited method from prototype

Bản đồ không có bất kỳ hạn chế nào đối với tên khóa, chúng ta có thể sử dụng các tên khóa như toString, constructor, v.v.

const actorMap= new Map();

actorMap.set('name', 'Harrison Ford');

actorMap.set('toString', 'Actor: Harrison Ford');

function isMap(value) {
  return value.toString() === '[object Map]';
}

console.log(isMap(actorMap));     // true

Nếu chúng ta gặp tình huống đầu vào của người dùng tạo các khóa thì chúng ta phải lấy các khóa đó bên trong Bản đồ thay vì một đối tượng đơn giản. Điều này là do người dùng có thể chọn một tên trường tùy chỉnh như, toString, constructor, v.v. sau đó các tên khóa như vậy trong một đối tượng đơn giản có khả năng phá vỡ mã mà sau này sử dụng đối tượng này. Vì vậy, giải pháp phù hợp là liên kết trạng thái giao diện người dùng với bản đồ, không có cách nào để phá vỡ Bản đồ:

const userCustomFieldsMap= new Map([['color', 'blue'], ['size', 'medium'], ['toString', 'A blue box']]);

3. Bản đồ có thể lặp lại:

Để lặp lại các thuộc tính của một đối tượng đơn giản, chúng ta cần Object.entries () hoặc Object.keys (). Object.entries (plainObject) trả về một mảng các cặp giá trị khóa được trích xuất từ ​​đối tượng, sau đó chúng ta có thể phá hủy các khóa và giá trị đó và có thể nhận được các khóa và giá trị đầu ra bình thường.

const colorHex= {
  'white': '#FFFFFF',
  'black': '#000000'
}

for(const [color, hex] of Object.entries(colorHex)) {
  console.log(color, hex);
}
//
'white' '#FFFFFF'   
'black' '#000000'

Vì Bản đồ có thể lặp lại, đó là lý do tại sao chúng ta không cần các phương thức entry () để lặp lại trên Bản đồ và phá hủy khóa, mảng giá trị có thể được thực hiện trực tiếp trên Bản đồ vì bên trong Bản đồ, mỗi phần tử tồn tại dưới dạng một mảng các cặp giá trị khóa được phân tách bằng dấu phẩy .

const colorHexMap= new Map();
colorHexMap.set('white', '#FFFFFF');
colorHexMap.set('black', '#000000');


for(const [color, hex] of colorHexMap) {
  console.log(color, hex);
}
//'white' '#FFFFFF'   'black' '#000000'

Ngoài ra map.keys () trả về một iterator trên các khóa và map.values ​​() trả về một iterator trên các giá trị.

4. Chúng ta có thể dễ dàng biết kích thước của Bản đồ

Chúng ta không thể xác định trực tiếp số lượng thuộc tính trong một đối tượng đơn giản. Chúng ta cần một trình trợ giúp fn like, Object.keys () trả về một mảng với các khóa của đối tượng sau đó sử dụng thuộc tính length chúng ta có thể lấy số lượng khóa hoặc kích thước của đối tượng đơn giản.

const exams= {'John Rambo': '80%', 'James Bond': '60%'};

const sizeOfObj= Object.keys(exams).length;

console.log(sizeOfObj);       // 2

Nhưng trong trường hợp Bản đồ, chúng ta có thể có quyền truy cập trực tiếp vào kích thước của Bản đồ bằng cách sử dụng thuộc tính map.size.

const examsMap= new Map([['John Rambo', '80%'], ['James Bond', '60%']]);

console.log(examsMap.size);

1

Hai mẹo này có thể giúp bạn quyết định nên sử dụng Bản đồ hay Đối tượng:

  • Sử dụng bản đồ trên các đối tượng khi các khóa không xác định cho đến thời gian chạy và khi tất cả các khóa là cùng loại và tất cả các giá trị là cùng một loại.

  • Sử dụng bản đồ trong trường hợp nếu có nhu cầu lưu trữ các giá trị nguyên thủy làm khóa vì đối tượng coi mỗi khóa là một chuỗi hoặc là giá trị số, giá trị boolean hoặc bất kỳ giá trị nguyên thủy nào khác.

  • Sử dụng các đối tượng khi có logic hoạt động trên các yếu tố riêng lẻ.

Nguồn: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Keyed_Collections#Object_and_Map_compared


2
Những mẹo này có vẻ không đặc biệt hữu ích vì nó có xu hướng không dễ dàng phân chia mọi thứ theo các tiêu chí đó. Tôi không hiểu với lý do đầu tiên tại sao bản đồ là một lợi ích khi các khóa / giá trị cùng loại. Nghe có vẻ giống như nó đang cố gắng sử dụng các đối tượng như các lớp / cấu trúc, bản đồ như các bộ sưu tập. Điều thứ hai được viết kém không đi đến điểm. Nó thực sự có nghĩa là sử dụng bản đồ khi bạn có các loại tương đương chuỗi hỗn hợp ("1" và 1) hoặc khi bạn cần / muốn bảo tồn các loại khóa. Lần cuối cùng tôi nghĩ nó giống như lần đầu tiên, nó cho rằng bạn không biết đối tượng là gì nên nó mơ hồ.
jgmjgm

0

Đây là một cách ngắn để tôi nhớ nó: KOI

  1. Chìa khóa. Khóa đối tượng là chuỗi hoặc ký hiệu. Các khóa bản đồ cũng có thể là số (1 và "1" là khác nhau), các đối tượng NaN, v.v ... Nó sử dụng ===để phân biệt giữa các khóa, với một ngoại lệ NaN !== NaNnhưng bạn có thể sử dụng NaNlàm khóa.
  2. Đặt hàng. Thứ tự chèn được ghi nhớ. Vì vậy, [...map]hoặc [...map.keys()]có một thứ tự cụ thể.
  3. Giao diện. Đối tượng: obj[key]hoặc obj.a(trong một số ngôn ngữ [][]=thực sự là một phần của giao diện). Bản đồ có get(), set(), has(), delete(), vv Lưu ý rằng bạn có thể sử dụng map[123]nhưng đó là sử dụng nó như là một đối tượng JS đồng bằng.

0

Theo Mozilla

Object vs Map trong JavaScript theo cách ngắn gọn với các ví dụ.

Đối tượng tuân theo khái niệm tương tự như của bản đồ, tức là sử dụng cặp khóa-giá trị để lưu trữ dữ liệu. Nhưng có một số khác biệt nhỏ làm cho bản đồ trở thành một biểu diễn tốt hơn trong các tình huống nhất định.

Map- là một cấu trúc dữ liệu giúp lưu trữ dữ liệu dưới dạng các cặp. Cặp này bao gồm một khóa duy nhất và một giá trị được ánh xạ tới khóa. Nó giúp ngăn ngừa sự trùng lặp.

Sự khác biệt chính

  • Bản đồ là một thể hiện của một đối tượng nhưng ngược lại là không đúng sự thật.

var map = new Map();
var obj = new Object(); 
console.log(obj instanceof Map);   // false
console.log(map instanceof Object);  // true

  • Trong Object, kiểu dữ liệu của trường khóa được giới hạn ở số nguyên, chuỗi và ký hiệu. Trong khi ở Map, trường khóa có thể thuộc bất kỳ kiểu dữ liệu nào (số nguyên, mảng, đối tượng)

var map = new Map();//Empty 
map.set(1,'1');
map.set('one', 1);
map.set('{}', {name:'Hello world'});
map.set(12.3, 12.3)
map.set([12],[12345])

for(let [key,value] of map.entries())
  console.log(key+'---'+value)

  • Trong Bản đồ, thứ tự ban đầu của các yếu tố được bảo tồn. Điều này không đúng trong trường hợp đối tượng.

let obj ={
  1:'1',
  'one':1,
  '{}': {name:'Hello world'},
  12.3:12.3,
  [12]:[100]
}
console.log(obj)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.