<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          我應(yīng)該使用 Object 還是 Map?

          共 8882字,需瀏覽 18分鐘

           ·

          2021-04-03 21:22

          ??  點擊上方卡片關(guān)注


          前言

          在日常的 JavaScript 項目中,我們最常用到的數(shù)據(jù)結(jié)構(gòu)就是各種形式的鍵值對格式了(key-value pair)。在 JavaScript 中,除了最基礎(chǔ)的 Object 是該格式外,ES6 新增的 Map 也同樣是鍵值對格式。它們的用法在很多時候都十分接近。不知道有沒有人和我一樣糾結(jié)過該選擇哪個去使用呢?在本菜最近的項目中,我又遇到了這樣的煩惱,索性一不做二不休,去對比一下究竟該使用哪一個。

          本文將會探討一下 Object 和 Map 的不同,從多個角度對比一下 Object 和 Map

          • 用法的區(qū)別:在某些情況下的用法會截然不同
          • 句法的區(qū)別:創(chuàng)建以及增刪查改的句法區(qū)別
          • 性能的區(qū)別:速度和內(nèi)存占用情況

          希望讀完本文的你可以在日后的項目中做出更為合適的選擇。

          用法對比

          1. 對于 Object 而言,它鍵(key)的類型只能是字符串,數(shù)字或者 Symbol;而對于 Map 而言,它可以是任何類型。(包括 Date,Map,或者自定義對象)

          2. Map 中的元素會保持其插入時的順序;而 Object 則不會完全保持插入時的順序,而是根據(jù)如下規(guī)則進行排序:

            • 非負整數(shù)會最先被列出,排序是從小到大的數(shù)字順序
            • 然后所有字符串,負整數(shù),浮點數(shù)會被列出,順序是根據(jù)插入的順序
            • 最后才會列出 SymbolSymbol 也是根據(jù)插入的順序進行排序的
          3. 讀取 Map 的長度很簡單,只需要調(diào)用其 .size() 方法即可;而讀取 Object 的長度則需要額外的計算:Object.keys(obj).length

          4. Map 是可迭代對象,所以其中的鍵值對是可以通過 for of 循環(huán)或 .foreach() 方法來迭代的;而普通的對象鍵值對則默認是不可迭代的,只能通過 for in 循環(huán)來訪問(或者使用 Object.keys(o)、Object.values(o)、Object.entries(o) 來取得表示鍵或值的數(shù)字)迭代時的順序就是上面提到的順序。

            const o = {};
            const m = new Map();
            o[Symbol.iterator] !== undefined// false
            m[Symbol.iterator] !== undefined// true
          5. 在 Map 中新增鍵時,不會覆蓋其原型上的鍵;而在 Object 中新增鍵時,則有可能覆蓋其原型上的鍵:

            Object.prototype.x = 1;
            const o = {x:2};
            const m = new Map([[x,2]]);
            o.x; // 2,x = 1 被覆蓋了
            m.x; // 1,x = 1 不會被覆蓋
          6. JSON 默認支持 Object 而不支持 Map。若想要通過 JSON 傳輸 Map 則需要使用到 .toJSON() 方法,然后在 JSON.parse() 中傳入復(fù)原函數(shù)來將其復(fù)原。

            對于 JSON 這里就不具體展開了,有興趣的朋友可以看一下這:JSON 的序列化和解析

            const o = {x:1};
            const m = new Map([['x'1]]);
            const o2 = JSON.parse(JSON.stringify(o)); // {x:1}
            const m2 = JSON.parse(JSON.stringify(m)) // {}

          句法對比

          創(chuàng)建時的區(qū)別

          Obejct

          const o = {}; // 對象字面量
          const o = new Object(); // 調(diào)用構(gòu)造函數(shù)
          const o = Object.create(null); // 調(diào)用靜態(tài)方法 Object.create 

          對于 Object 來說,我們在 95%+ 的情況下都會選擇對象字面量,它不僅寫起來最簡單,而且相較于下面的函數(shù)調(diào)用,在性能方面會更為高效。對于構(gòu)建函數(shù),可能唯一使用到的情況就是顯式的封裝一個基本類型;而 Object.create 可以為對象設(shè)定原型。

          Map

          const m = new Map(); // 調(diào)用構(gòu)造函數(shù)

          和 Object 不同,Map 沒有那么多花里胡哨的創(chuàng)建方法,通常只會使用其構(gòu)造函數(shù)來創(chuàng)建。

          除了上述方法之外,我們也可以通過 Function.prototype.apply()、Function.prototype.call()、reflect.apply()、Reflect.construct() 方法來調(diào)用 Object 和 Map 的構(gòu)造函數(shù)或者  Object.create() 方法,這里就不展開了。

          新增/讀取/刪除元素時的區(qū)別

          Obejct

          const o = {};
          //新增/修改
          o.x = 1;
          o['y'] = 2;
          //讀取
          o.x; // 1
          o['y']; // 2
          //或者使用 ES2020 新增的條件屬性訪問表達式來讀取
          o?.x; // 1
          o?.['y']; // 2
          //刪除
          delete o.b;

          對于新增元素,看似使用第一種方法更為簡單,不過它也有些許限制:

          • 屬性名不能包含空格和標(biāo)點符號
          • 屬性名不能以數(shù)字開頭

          對于條件屬性訪問表達式的更多內(nèi)容可以看一下這:條件屬性訪問表達式

          Map

          const m = new Map();
          //新增/修改
          m.set('x'1);
          //讀取
          map.get('x');
          //刪除
          map.delete('b');

          對于簡單的增刪查改來說,Map 上的方法使用起來也是十分便捷的;不過在進行聯(lián)動操作時,Map 中的用法則會略顯臃腫:

          const m = new Map([['x',1]]);
          // 若想要將 x 的值在原有基礎(chǔ)上加一,我們需要這么做:
          m.set('x', m.get('x') + 1);
          m.get('x'); // 2

          const o = {x1};
          // 在對象上修改則會簡單許多:
          o.x++;
          o.x // 2

          性能對比

          接下來我們來討論一下 Object 和 Map 的性能。不知道各位有沒有聽說過 Map 的性能優(yōu)于 Object 的說法,我反正是見過不少次,甚至在 JS 高程四中也提到了 Map 對比 Object 時性能的優(yōu)勢;不過對于性能的概括都十分的籠統(tǒng),所以我打算做一些測試來對比一下它們的區(qū)別。

          測試方法

          在這里我進行的對于性能測試的都是基于 v8 引擎的。速度會通過 JS 標(biāo)準(zhǔn)庫自帶的 performance.now() 函數(shù)來判斷,內(nèi)存使用情況會通過 Chrome devtool 中的 memory 來查看。

          對于速度測試,因為單一的操作速度太快了,很多時候 performance.now() 會返回 0。所以我進行了 10000 次的循環(huán)然后判斷時間差。因為循環(huán)本身也會占據(jù)一部分時間,所以以下的測試只能作為一個大致的參考。

          創(chuàng)建時的性能

          測試用的代碼如下

          let n,  n2 = 5;
          // 速度
          while (n2--) {
            let p1 = performance.now();
            n = 10000;
            while (n--) { let o = {}; }
            let p2 = performance.now();
            n = 10000;
            while (n--) { let m = new Map(); }
            let p3 = performance.now();
            console.log(`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`);
          }
          // 內(nèi)存
          class Test {}
          let test = new Test();
          test.o = o;
          test.m = m;

          首先進行對比的是創(chuàng)建 Object 和 Map 時的表現(xiàn)。對于創(chuàng)建的速度表現(xiàn)如下:

          我們可以發(fā)現(xiàn)創(chuàng)建 Object 的速度會快于 Map。對于內(nèi)存使用情況則如下:

          我們主要關(guān)注其 Retained Size,它表示了為其分配的空間。(即刪除時釋放的內(nèi)存大小)

          通過對比我們可以發(fā)現(xiàn),空的 Object 會比空的 Map 占用更少的內(nèi)。所以這一輪 Object 贏得一籌。

          新增元素時的性能

          測試用的代碼如下

          console.clear();
          let n,  n2 = 5;
          let o = {}, m = new Map();
          // 速度
          while (n2--) {
            let p1 = performance.now();
            n = 10000;
            while (n--) { o[Math.random()] = Math.random(); }
            let p2 = performance.now();
            n = 10000;
            while (n--) { m.set(Math.random(), Math.random()); }
            let p3 = performance.now();
            console.log(`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`);
          }
          // 內(nèi)存
          class Test {}
          let test = new Test();
          test.o = o;
          test.m = m;

          對于新建元素時的速度表現(xiàn)如下:

          我們可以發(fā)現(xiàn)新建元素時,Map 的速度會快于 Object。對于內(nèi)存使用情況則如下:

          通過對比我們可以發(fā)現(xiàn),在擁有一定數(shù)量的元素時, Object 會比 Map 占用多了約 78% 的內(nèi)存。我也進行了多次的測試,發(fā)現(xiàn)在擁有足夠的元素時,這個百分比是十分穩(wěn)定的。所以說,在需要進行很多新增操作,且需要儲存許多數(shù)據(jù)的時候,使用 Map 會更高效。

          讀取元素時的性能

          測試用的代碼如下

          let n;
          let o = {}, m = new Map();

          n = 10000;
          while (n--) { o[Math.random()] = Math.random(); }
          n = 10000;
          while (n--) { m.set(Math.random(), Math.random()); }

          let p1 = performance.now();
          for (key in o) { let k = o[key]; }
          let p2 = performance.now();
          for ([key] of m) { let k = m.get(key); }
          let p3 = performance.now();
          `Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`

          對于讀取元素時的速度表現(xiàn)如下:

          通過對比,我們可以發(fā)現(xiàn) Object 略占優(yōu)勢,但總體差別不大。

          刪除元素時的性能

          不知道大家是否聽說過 delete 操作符性能低下,甚至有很多時候為了性能,會寧可將值設(shè)置為 undefined 而不使用 delete 操作符的說法。但其實在 v8 近來的優(yōu)化下,它的效率已經(jīng)提升許多了。

          測試用的代碼如下

          let n;
          let o = {}, m = new Map();

          n = 10000;
          while (n--) { o[Math.random()] = Math.random(); }
          n = 10000;
          while (n--) { m.set(Math.random(), Math.random()); }

          let p1 = performance.now();
          for (key in o) { delete o[key]; }
          let p2 = performance.now();
          for ([key] of m) { m.delete(key); }
          let p3 = performance.now();
          `Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`

          對于刪除元素時的速度表現(xiàn)如下:

          我們可以發(fā)現(xiàn)在進行刪除操作時,Map 的速度會略占優(yōu),但整體差別其實并不大。

          特殊情況

          其實除了最基本的情況之外,還有一種特殊的情況。還記得我們在前面提到的 Object 中鍵的排序嗎?我們提到了其中的非負整數(shù)會被最先列出。其實對于非負整數(shù)作為鍵的值和其余類型作為鍵的值來說,v8 是會對它們進行區(qū)別對待的。負整數(shù)作為鍵的部分會被當(dāng)成數(shù)組對待,即非負整數(shù)具有一定的連續(xù)性時,會被當(dāng)成快數(shù)組,而過于稀疏時會被當(dāng)成慢數(shù)組。

          對于快數(shù)組,它擁有連續(xù)的內(nèi)存,所以在進行讀寫時會更快,且占用更少的內(nèi)存。更多的內(nèi)容可以看一下這: 探究JS V8引擎下的“數(shù)組”底層實現(xiàn)

          在鍵為連續(xù)非負整數(shù)時,性能如下:

          我們可以看到 Object 不僅平均速度更快了,其占用的內(nèi)存也大大減少了。

          總結(jié)

          通過對比我們可以發(fā)現(xiàn),Map 和 Object 各有千秋,對于不同的情況下,我們應(yīng)當(dāng)作出不同的選擇。所以我總結(jié)了一下我認為使用 Map 和 Object 更為合適的時機。

          使用 Map

          • 儲存的鍵不是字符串/數(shù)字/或者 Symbol 時,選擇 Map,因為 Object 并不支持
          • 儲存大量的數(shù)據(jù)時,選擇 Map,因為它占用的內(nèi)存更小
          • 需要進行許多新增/刪除元素的操作時,選擇 Map,因為速度更快
          • 需要保持插入時的順序的話,選擇 Map,因為 Object 會改變排序
          • 需要迭代/遍歷的話,選擇 Map,因為它默認是可迭代對象,迭代更為便捷

          使用 Object

          • 只是簡單的數(shù)據(jù)結(jié)構(gòu)時,選擇 Object,因為它在數(shù)據(jù)少的時候占用內(nèi)存更少,且新建時更為高效
          • 需要用到 JSON 進行文件傳輸時,選擇 Object,因為 JSON 不默認支持 Map
          • 需要對多個鍵值進行運算時,選擇 Object,因為句法更為簡潔
          • 需要覆蓋原型上的鍵時,選擇 Object

          雖然 Map 在很多情況下會比 Object 更為高效,不過 Object 永遠是 JS 中最基本的引用類型,它的作用也不僅僅是為了儲存鍵值對。

          參考

          探究JS V8引擎下的“數(shù)組”底層實現(xiàn)

          Fast properties in V8

          Shallow, Retained, and Deep Size

          Slow delete of object properties in JS in V8

          ES6 — Map vs Object — What and when?

          JavaScript高級程序設(shè)計(第4版)

          JavaScript: The Definitive Guide (7th Edition)

          ??愛心三連擊

          1.看到這里了就點個在看支持下吧,你的點贊在看是我創(chuàng)作的動力。

          2.關(guān)注公眾號程序員成長指北,回復(fù)「1」加入高級前端交流群!「在這里有好多 前端 開發(fā)者,會討論 前端 Node 知識,互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長。

          “在看轉(zhuǎn)發(fā)”是最大的支持

          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩熟女一区二区 | 一区二区三区无码流出 | 国产激情精品在线观看 | 阴阴婷婷小视频 | 亚洲爆乳一区二区三区 |