<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>

          JS 項(xiàng)目中究竟應(yīng)該使用 Object 還是 Map?| 項(xiàng)目復(fù)盤

          共 8090字,需瀏覽 17分鐘

           ·

          2021-03-22 12:26

          • 本文已獲得原作者的獨(dú)家授權(quán),有想轉(zhuǎn)載的朋友們可以在后臺(tái)聯(lián)系我申請(qǐng)開白哦!


          前言

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

          本文將會(huì)探討一下 ObjectMap 的不同,從多個(gè)角度對(duì)比一下 ObjectMap

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

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

          用法對(duì)比

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

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

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

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

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

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

            對(duì)于 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)) // {}

          句法對(duì)比

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

          Obejct

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

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

          Map

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

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

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

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

          Obejct

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

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

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

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

          Map

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

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

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

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

          性能對(duì)比

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

          測試方法

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

          對(duì)于速度測試,因?yàn)閱我坏牟僮魉俣忍炝耍芏鄷r(shí)候 performance.now() 會(huì)返回 0。所以我進(jìn)行了 10000 次的循環(huán)然后判斷時(shí)間差。因?yàn)檠h(huán)本身也會(huì)占據(jù)一部分時(shí)間,所以以下的測試只能作為一個(gè)大致的參考。

          創(chuàng)建時(shí)的性能

          測試用的代碼如下

          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;

          首先進(jìn)行對(duì)比的是創(chuàng)建 ObjectMap 時(shí)的表現(xiàn)。對(duì)于創(chuàng)建的速度表現(xiàn)如下:

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

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

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

          新增元素時(shí)的性能

          測試用的代碼如下

          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;

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

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

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

          讀取元素時(shí)的性能

          測試用的代碼如下

          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`

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

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

          刪除元素時(shí)的性能

          不知道大家是否聽說過 delete 操作符性能低下,甚至有很多時(shí)候?yàn)榱诵阅埽瑫?huì)寧可將值設(shè)置為 undefined 而不使用 delete 操作符的說法。但其實(shí)在 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`

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

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

          特殊情況

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

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

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

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

          總結(jié)

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

          使用 Map

          • 儲(chǔ)存的鍵不是字符串/數(shù)字/或者 Symbol 時(shí),選擇 Map,因?yàn)?Object 并不支持
          • 儲(chǔ)存大量的數(shù)據(jù)時(shí),選擇 Map,因?yàn)樗加玫膬?nèi)存更小
          • 需要進(jìn)行許多新增/刪除元素的操作時(shí),選擇 Map,因?yàn)樗俣雀?/section>
          • 需要保持插入時(shí)的順序的話,選擇 Map,因?yàn)?Object 會(huì)改變排序
          • 需要迭代/遍歷的話,選擇 Map,因?yàn)樗J(rèn)是可迭代對(duì)象,迭代更為便捷

          使用 Object

          • 只是簡單的數(shù)據(jù)結(jié)構(gòu)時(shí),選擇 Object,因?yàn)樗跀?shù)據(jù)少的時(shí)候占用內(nèi)存更少,且新建時(shí)更為高效
          • 需要用到 JSON 進(jìn)行文件傳輸時(shí),選擇 Object,因?yàn)?JSON 不默認(rèn)支持 Map
          • 需要對(duì)多個(gè)鍵值進(jìn)行運(yùn)算時(shí),選擇 Object,因?yàn)榫浞ǜ鼮楹啙?/section>
          • 需要覆蓋原型上的鍵時(shí),選擇 Object

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

          參考

          探究JS V8引擎下的“數(shù)組”底層實(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高級(jí)程序設(shè)計(jì)(第4版)

          JavaScript: The Definitive Guide (7th Edition)

          最后





          如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)

          2. 歡迎加我微信「huab119」拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

          3. 關(guān)注公眾號(hào)「前端勸退師」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。



          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了


          瀏覽 80
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  无码一区二区三区四区五区六区七区 | 麻豆五月婷婷 | 三级片91麻豆网站 | 天天日天天日天天 | 尾随极品少妇 |