Chúng ta có thể sử dụng đối tượng làm khóa trong javascript hashmap không?
Trong JavaScript, các đối tượng rất tiện dụng. Chúng cho phép chúng tôi dễ dàng nhóm nhiều phần dữ liệu lại với nhau. Sau ES6, chúng tôi đã có một bổ sung mới cho ngôn ngữ - 0. Ở nhiều khía cạnh, có vẻ như một 1 có khả năng hơn với giao diện hơi vụng về. Tuy nhiên, hầu hết mọi người vẫn tiếp cận các đối tượng khi họ cần bản đồ băm và chỉ chuyển sang sử dụng 0 khi họ nhận ra rằng các khóa không thể chỉ là các chuỗi cho các trường hợp sử dụng của họ. Kết quả là, 0 vẫn chưa được sử dụng nhiều trong cộng đồng JavaScript ngày nay Show
Trong bài đăng này, tôi sẽ chia nhỏ tất cả các lý do khi bạn nên cân nhắc sử dụng thêm 0 và các đặc điểm hiệu suất của nó với điểm chuẩn
TL; DR
Tại sao const hashMap = {} 1 không đáp ứng được trường hợp sử dụng bản đồ bămCó lẽ nhược điểm rõ ràng nhất của việc sử dụng các đối tượng cho bản đồ băm là các đối tượng chỉ cho phép các khóa là chuỗi và ký hiệu. Bất kỳ loại nào khác sẽ được chuyển thành chuỗi hoàn toàn thông qua phương thức 44 5Quan trọng hơn, việc sử dụng các đối tượng cho bản đồ băm có thể gây nhầm lẫn và nguy cơ bảo mật thừa kế không mong muốnTrước ES6, cách duy nhất để lấy bản đồ băm là tạo một đối tượng trống
Tuy nhiên, khi tạo, đối tượng này không còn trống nữa. Mặc dù 45 được tạo bằng một đối tượng trống theo nghĩa đen, nhưng nó sẽ tự động kế thừa từ 46. Đó là lý do tại sao chúng ta có thể gọi các phương thức như 47, 44, 49 trên 45 mặc dù chúng ta chưa bao giờ định nghĩa rõ ràng các phương thức đó trên đối tượngDo kế thừa nguyên mẫu, giờ đây chúng tôi có hai loại thuộc tính được kết hợp. thuộc tính sống trong chính đối tượng, tôi. e. các thuộc tính riêng của nó và các thuộc tính tồn tại trong chuỗi nguyên mẫu, tôi. e. tài sản kế thừa. Do đó, chúng tôi cần kiểm tra bổ sung (e. g. 47) để đảm bảo rằng một thuộc tính nhất định thực sự do người dùng cung cấp, trái ngược với thuộc tính kế thừa từ nguyên mẫuNgoài ra, do cơ chế phân giải thuộc tính hoạt động như thế nào trong JavaScript, bất kỳ thay đổi nào đối với 46 trong thời gian chạy sẽ gây ra hiệu ứng gợn sóng trong tất cả các đối tượng. Điều này mở ra cơ hội cho các cuộc tấn công ô nhiễm nguyên mẫu, đây có thể là một vấn đề bảo mật nghiêm trọng đối với các ứng dụng JavaScript lớnMay mắn thay, chúng ta có thể giải quyết vấn đề này bằng cách sử dụng 53, điều này tạo ra một đối tượng không kế thừa gì từ 46tên va chạmKhi các thuộc tính riêng của một đối tượng có xung đột tên với các thuộc tính trên nguyên mẫu của nó, nó sẽ phá vỡ các kỳ vọng và do đó làm hỏng chương trình của bạn Ví dụ: chúng ta có hàm 55 chấp nhận một đối tượng 4Có một mối nguy hiểm về độ tin cậy trong 56. đưa ra cách thức hoạt động của cơ chế phân giải thuộc tính trong JavaScript, nếu 57 chứa thuộc tính do người dùng cung cấp có cùng tên 47, thì điều đó sẽ che phủ 59. Do đó, chúng tôi không biết phương thức nào sẽ được gọi chính xác trong thời gian chạyMột số chương trình phòng thủ có thể được thực hiện để ngăn chặn điều này. Ví dụ: chúng ta có thể "mượn" 47 "thực" từ 46 để thay thế 5Một cách ngắn hơn có thể gọi phương thức trên một đối tượng theo nghĩa đen như trong 62 tuy nhiên nó vẫn còn khá cồng kềnh. Đó là lý do tại sao có một phương thức tĩnh mới được thêm vào 63Công thái học dưới mức tối ưu 1 không cung cấp công thái học đầy đủ để được sử dụng làm bản đồ băm. Nhiều tác vụ phổ biến không thể được thực hiện bằng trực giáckích thước 1 không đi kèm với API tiện dụng để lấy kích thước, tôi. e. số lượng tài sản. Và có những sắc thái đối với những gì tạo nên kích thước của một đối tượng
Tất cả các tùy chọn trên có độ phức tạp thời gian chạy là 41 vì trước tiên chúng tôi phải xây dựng một mảng khóa trước khi chúng tôi có thể nhận được độ dài của nólặp đi lặp lạiVòng lặp qua các đối tượng chịu sự phức tạp tương tự Chúng ta có thể sử dụng vòng lặp 42 cũ tốt. Nhưng nó tiết lộ vô số thuộc tính kế thừa 6Chúng tôi không thể sử dụng 43 với một đối tượng vì theo mặc định, nó không thể lặp lại, trừ khi chúng tôi xác định rõ ràng phương thức 44 trên đóChúng ta có thể sử dụng 45, 46 và 47 để lấy danh sách các khóa chuỗi (hoặc/và giá trị) có thể đếm được và lặp lại qua đó thay vào đó, điều này giới thiệu một bước bổ sung với chi phí hoạt độngCuối cùng, thứ tự chèn không được tôn trọng đầy đủ. Trong hầu hết các trình duyệt, các khóa số nguyên được sắp xếp theo thứ tự tăng dần và được ưu tiên hơn các khóa chuỗi ngay cả khi các khóa chuỗi được chèn trước các khóa số nguyên 4xa lạKhông có cách nào dễ dàng để xóa tất cả các thuộc tính khỏi một đối tượng, bạn phải xóa từng thuộc tính một bằng toán tử 48, vốn được biết đến là chậm. Tuy nhiên, điểm chuẩn của tôi cho thấy rằng hiệu suất của nó thực sự không chậm hơn một bậc so với 49. Thêm về điều đó saukiểm tra sự tồn tại của tài sảnCuối cùng, chúng ta không thể dựa vào ký hiệu dấu chấm/dấu ngoặc để kiểm tra sự tồn tại của một thuộc tính vì bản thân giá trị đó có thể được đặt là 6. Thay vào đó chúng ta phải sử dụng 59 hoặc 63 9Bản đồ cho Bản đồ bămES6 mang lại cho chúng tôi 0. Nó phù hợp hơn nhiều cho trường hợp sử dụng bản đồ bămTrước hết, không giống như ________ 01 chỉ cho phép các khóa là chuỗi và ký hiệu, ________ 00 hỗ trợ các khóa thuộc mọi loại dữ liệu
Nhưng quan trọng hơn, 0 cung cấp sự tách biệt rõ ràng giữa dữ liệu do người dùng xác định và dữ liệu chương trình tích hợp sẵn, với chi phí bổ sung là 99 để truy xuất các mục nhập 0 cũng cung cấp công thái học tốt hơn. Một 0 là một iterable theo mặc định. Điều đó có nghĩa là bạn có thể lặp lại bản đồ một cách dễ dàng với 43 và thực hiện những việc như sử dụng tính năng phá hủy lồng nhau để lấy mục nhập đầu tiên khỏi bản đồ 0Trái ngược với 1, 0 cung cấp các API chuyên dụng cho các tác vụ phổ biến khác nhau
Hiệu suất hoành trángDường như có một niềm tin chung trong cộng đồng JavaScript rằng hầu hết các phần của 0 nhanh hơn 1. Có những người tuyên bố đã thấy hiệu suất tăng đáng kể khi chuyển từ 1 sang 0Kinh nghiệm mài LeetCode của tôi dường như xác nhận niềm tin này. LeetCode cung cấp một lượng dữ liệu khổng lồ dưới dạng các trường hợp thử nghiệm cho giải pháp của bạn và sẽ hết thời gian nếu giải pháp của bạn mất quá nhiều thời gian. Những câu hỏi như thế này chỉ hết thời gian nếu bạn sử dụng 1, nhưng không phải trên 0Tuy nhiên, tôi tin rằng chỉ cần nói " 0 nhanh hơn 1" là đơn giản. Phải có một số sắc thái mà tôi muốn tìm hiểu bản thân mình. Vì vậy. Tôi đã tạo một ứng dụng nhỏ để chạy một số điểm chuẩnTuyên bố miễn trừ trách nhiệm quan trọngTôi không khẳng định mình hoàn toàn hiểu cách V8 hoạt động bí mật để tối ưu hóa 0 mặc dù tôi đã nhiều lần cố gắng đọc các bài đăng trên blog và xem qua mã nguồn C++. Đo điểm chuẩn mạnh mẽ hoàn hảo là khó và hầu hết chúng ta chưa bao giờ trải qua bất kỳ hình thức đào tạo nào về đo điểm chuẩn hoặc diễn giải kết quả. Tôi càng làm nhiều điểm chuẩn, tôi càng cảm thấy nó giống như một câu chuyện về những người mù và một con voi. Vì vậy, hãy coi thường mọi điều tôi đang nói ở đây về hiệu suất. Bạn sẽ cần kiểm tra những thay đổi như vậy với ứng dụng của mình trong môi trường sản xuất để biết chắc chắn liệu có hiệu suất thực tế đạt được từ việc sử dụng 0 so với 1 hay khôngChi tiết triển khai điểm chuẩnỨng dụng này có một bảng hiển thị tốc độ chèn, lặp lại và xóa được đo trên 1 và 0Hiệu suất của thao tác chèn và lặp được đo bằng thao tác trên giây. Tôi đã viết một hàm tiện ích 57 chạy lặp lại hàm đích cho đến ngưỡng thời gian tối thiểu đã chỉ định (i. e. trường đầu vào 58 trên giao diện người dùng) đã đạt được. Nó trả về số lần trung bình một chức năng như vậy được thực thi mỗi giây 9Đối với việc xóa, tôi chỉ đơn giản là đo thời gian sử dụng toán tử 48 để xóa tất cả các thuộc tính khỏi một đối tượng và so sánh nó với thời gian của 49 cho một 0 có cùng kích thước. Tôi có thể sử dụng 91 nhưng nó đánh bại mục đích của điểm chuẩn vì tôi biết chắc chắn rằng nó sẽ nhanh hơn rất nhiềuTrong ba thao tác này, tôi chú ý nhiều hơn đến thao tác chèn vì nó có xu hướng là thao tác phổ biến nhất mà tôi thực hiện trong công việc hàng ngày của mình. Đối với hiệu suất lặp, thật khó để đưa ra một điểm chuẩn bao gồm tất cả vì có nhiều biến thể lặp khác nhau mà chúng ta có thể thực hiện trên một đối tượng nhất định. Ở đây tôi chỉ đo vòng lặp 42Tôi đã sử dụng ba loại chìa khóa ở đây
Tất cả các khóa được tạo ngẫu nhiên để chúng tôi không nhấn vào bộ đệm nội tuyến do V8 triển khai. Tôi cũng chuyển đổi rõ ràng các khóa số nguyên và số thành chuỗi bằng cách sử dụng 44 trước khi thêm chúng vào các đối tượng để tránh chi phí truyền ngầmCuối cùng, trước khi điểm chuẩn bắt đầu, cũng có một giai đoạn khởi động trong ít nhất 100 mili giây trong đó chúng tôi liên tục tạo các đối tượng và bản đồ mới sẽ bị loại bỏ ngay lập tức Tôi đặt mã trên CodeSandbox nếu bạn muốn chơi với nó Tôi đã bắt đầu với 1 và 0 với kích thước 100 thuộc tính/mục nhập, cho đến hết 5000000 và để mỗi loại hoạt động tiếp tục chạy trong 10000 mili giây để xem chúng hoạt động như thế nào với nhau. Đây là những phát hiện của tôiTại sao chúng tôi dừng lại khi số lượng mục nhập đạt 5000000?Bởi vì nó lớn bằng một đối tượng có thể nhận được trong JavaScript. Theo , một kỹ sư V8 đang hoạt động trên StackOverflow, "nếu các phím là chuỗi, một đối tượng thông thường sẽ trở nên chậm chạp một cách bất thường sau ~8. Các yếu tố 3M (vì lý do kỹ thuật. một trường bit nhất định rộng 23 bit và sử dụng đường dẫn dự phòng rất chậm khi vượt quá). " phím chuỗiNói chung, khi các khóa là chuỗi (không phải số), 0 hoạt động tốt hơn 1 trên tất cả các thao tácNhưng điều khó hiểu là khi số lượng mục nhập không thực sự lớn (dưới 100000), thì tốc độ chèn của 0 nhanh gấp đôi so với 1, nhưng khi kích thước tăng lên trên 100000, khoảng cách về hiệu suất bắt đầu thu hẹp lạiTôi đã tạo một số biểu đồ để minh họa rõ hơn những phát hiện của mình Biểu đồ trên cho thấy tỷ lệ chèn giảm như thế nào (trục y) khi số lượng mục nhập tăng lên (trục x). Tuy nhiên do trục x mở rộng quá rộng (từ 100 đến 1000000) nên khó nhận biết được khoảng cách giữa 2 đường này Sau đó, tôi đã sử dụng thang logarit để xử lý dữ liệu và tạo biểu đồ bên dưới Bạn có thể nói rõ ràng hai dòng đang hội tụ Tôi đã tạo một biểu đồ khác biểu thị tốc độ chèn của 0 nhanh hơn bao nhiêu so với 1. Bạn có thể thấy 0 bắt đầu nhanh hơn khoảng 2 lần so với 1. Sau đó, theo thời gian, khoảng cách hiệu suất bắt đầu thu hẹp lại. Cuối cùng, 0 chỉ nhanh hơn 30% khi kích thước tăng lên 5000000Hầu hết chúng ta sẽ không bao giờ có hơn 1 triệu mục trong một đối tượng hoặc bản đồ. Với kích thước vài trăm hoặc hàng nghìn mục nhập, 0 có hiệu suất ít nhất gấp đôi so với 1. Do đó, chúng ta có nên để nó ở đó và bắt đầu tái cấu trúc cơ sở mã của mình bằng cách dốc toàn lực vào 0 không?Tuyệt đối không. hoặc ít nhất là không kỳ vọng rằng ứng dụng của chúng tôi sẽ nhanh hơn gấp 2 lần. Hãy nhớ rằng chúng tôi chưa khám phá các loại khóa khác. Chúng ta hãy xem các khóa số nguyên phím số nguyênLý do tôi đặc biệt muốn chạy điểm chuẩn trên các đối tượng có khóa số nguyên là V8 tối ưu hóa bên trong và lưu trữ chúng trong một mảng riêng biệt có thể được truy cập tuyến tính và liên tục. Tuy nhiên, tôi không thể tìm thấy bất kỳ tài nguyên nào xác nhận rằng nó sử dụng cùng một loại tối ưu hóa cho những năm 0Trước tiên hãy thử các khóa số nguyên trong phạm vi [0, 1000] Đúng như tôi mong đợi, lần này 1 vượt trội hơn 0. Chúng nhanh hơn 65% so với bản đồ về tốc độ chèn và nhanh hơn 16% để lặp lạiHãy mở rộng phạm vi để số nguyên tối đa trong các khóa là 1200 Có vẻ như bây giờ 0 s đang bắt đầu nhanh hơn một chút so với các đối tượng để chèn và nhanh hơn 5 lần để lặp lạiBây giờ chúng tôi chỉ tăng phạm vi của các phím số nguyên chứ không phải kích thước thực của 1 và 0. Hãy tăng kích thước để xem điều đó ảnh hưởng đến hiệu suất như thế nàoKhi kích thước của 1000 thuộc tính, 1 sẽ nhanh hơn 70% so với 0 khi chèn và chậm hơn 2 lần khi lặp lạiTôi đã chơi với một loạt các kết hợp khác nhau của kích thước 1/ 0 và phạm vi khóa số nguyên và không thể tìm ra một mẫu rõ ràng. Nhưng xu hướng chung mà tôi đang thấy là, khi kích thước tăng lên, với một số nguyên nhỏ tương đối là khóa, các đối tượng có thể hoạt động hiệu quả hơn 0 về mặt chèn, luôn gần giống như xóa và chậm hơn 4 hoặc 5 lần . Ngưỡng của các khóa số nguyên tối đa mà tại đó các đối tượng bắt đầu chậm hơn để chèn tăng theo kích thước của các đối tượng. Ví dụ: khi đối tượng chỉ có 100 mục nhập, ngưỡng là 1200; phím sốCuối cùng, chúng ta hãy xem loại phím cuối cùng - phím số Về mặt kỹ thuật, các khóa số nguyên trước đây cũng là số. Ở đây, các phím số đề cập cụ thể đến các chuỗi số được tạo bởi 06Các kết quả tương tự với các trường hợp khóa chuỗi đó. Các 0 bắt đầu nhanh hơn nhiều so với các đối tượng (chèn và xóa nhanh hơn 2 lần, lặp lại nhanh hơn 4-5 lần), nhưng delta ngày càng nhỏ hơn khi chúng tôi tăng kích thướcCòn đối tượng/bản đồ lồng nhau thì sao?Bạn có thể nhận thấy rằng tôi chỉ nói về các vật thể phẳng và bản đồ chỉ có một chiều sâu. Tôi đã thêm một số chi tiết nhưng tôi thấy rằng các đặc điểm hiệu suất phần lớn vẫn giữ nguyên miễn là tổng số mục nhập là như nhau, bất kể chúng tôi có bao nhiêu cấp độ lồng nhau Ví dụ: với 36 là 100 và 37 là 3, chúng tôi có tổng số một triệu mục (100 * 100 * 100). Kết quả gần như giống nhau so với việc chỉ có 38 cho chiều rộng và 1 cho 37Sử dụng bộ nhớMột khía cạnh quan trọng khác của điểm chuẩn là sử dụng bộ nhớ Vì tôi không có quyền kiểm soát trình thu gom rác trong môi trường trình duyệt, nên tôi quyết định chạy điểm chuẩn trong Node Tôi đã tạo một tập lệnh nhỏ để đo mức sử dụng bộ nhớ tương ứng của chúng với bộ sưu tập rác đầy đủ được kích hoạt thủ công trong mỗi phép đo. Chạy nó với 40 và tôi nhận được kết quả như sau 5Rõ ràng là 0 tiêu thụ ít bộ nhớ hơn 1 ở bất kỳ đâu từ 20% đến 50%, điều này không có gì ngạc nhiên vì 0 không lưu trữ các bộ mô tả thuộc tính như 44/ 45/ 46 giống như 1.Phần kết luậnVì vậy, những gì chúng ta lấy đi từ tất cả điều này?
Lưu ý về khả năng tương thích của trình duyệt 0 là một tính năng của ES6. Hiện tại, hầu hết chúng ta không nên lo lắng về khả năng tương thích của nó trừ khi bạn đang nhắm mục tiêu cơ sở người dùng với một số trình duyệt cũ, thích hợp. Ý tôi là "cũ" hơn IE 11 vì ngay cả IE 11 cũng hỗ trợ 0 và tại thời điểm này IE 11 đã chết. Theo mặc định, chúng ta không nên dịch mã và thêm polyfill vào mục tiêu ES5 một cách thiếu suy nghĩ, bởi vì nó không chỉ làm tăng kích thước gói của bạn mà còn chạy chậm hơn so với JavaScript hiện đại. Quan trọng nhất, nó phạt 99. 999% người dùng của bạn sử dụng trình duyệt hiện đạiNgoài ra, chúng tôi không phải bỏ hỗ trợ cho các trình duyệt cũ - cung cấp mã cũ qua 55 bằng cách cung cấp các gói dự phòng để chúng tôi có thể tránh làm giảm trải nghiệm của khách truy cập với các trình duyệt hiện đại. Tham khảo Chuyển sang JavaScript hiện đại nếu bạn cần thuyết phục hơnNgôn ngữ JavaScript đang phát triển và nền tảng ngày càng tốt hơn trong việc tối ưu hóa JavaScript hiện đại. Chúng ta không nên sử dụng khả năng tương thích của trình duyệt như một cái cớ để bỏ qua tất cả những cải tiến đã được thực hiện Tôi có thể sử dụng đối tượng làm khóa trong HashMap không?Có, chúng ta có thể sử dụng bất kỳ đối tượng nào làm khóa trong Bản đồ trong java nhưng chúng ta cần ghi đè các phương thức equals() và hashCode() của lớp đối tượng đó.
Bản đồ JavaScript có thể có đối tượng làm khóa không?Với bản đồ, tất cả các kiểu gốc (chuỗi, số, boolean, không xác định, null, đối tượng) đều có thể là khóa
Một đối tượng có thể là một khóa trong từ điển JavaScript không?Khóa từ điển JavaScript
. Object keys can only be strings, and even though a developer can use other data types to set keys, JavaScript will convert them to a string value.
Khóa đối tượng có thể là mảng không?Đối tượng. keys() trả về một mảng gồm vô số tên thuộc tính khóa chuỗi của một đối tượng nhất định . |