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

          一行 Object.keys() 引發(fā)的思考

          共 16024字,需瀏覽 33分鐘

           ·

          2022-06-28 04:21


          大廠技術(shù)  高級前端  Node進階

          點擊上方 程序員成長指北,關(guān)注公眾號

          回復(fù)1,加入高級Node交流群

          事背景

          有一天上線后大佬反饋了一個問題,他剛發(fā)的動態(tài)在生成分享卡片的時候,卡片底部的小程序碼丟失了,然而其他小伙伴都表示在自己手機上運行正常。事實上大佬也說除了這條動態(tài)以外,其它都是正常的。

          說明這個 BUG 需要特定的動態(tài)卡片 + 特定的設(shè)備才能復(fù)現(xiàn),所幸坐我對面的小姐姐手機與大佬是同款,也能復(fù)現(xiàn) BUG,避免了作為社恐的我要去找大佬借手機測試的尷尬。

          先交代一下項目背景,這是一個微信小程序項目,其中生成分享卡片功能用到的是一個叫 wxml2canvas[1] 的庫,然而該庫目前看上去已經(jīng)「年久失修」,上面所說的 BUG 就是因為這個庫,

          本文分享一下排查該 BUG 的過程、以及如何從 ECMAScript 規(guī)范中找到關(guān)于 Object.keys() 返回順序的規(guī)范定義,最后介紹一下在 V8 引擎中是如何處理對象屬性的。

          希望大家在閱讀本文后,不會再因為搞不懂 Object.keys() 輸出的順序而犯錯導(dǎo)致產(chǎn)生莫名其妙的 BUG。

          TL;DR

          本文很長,如果你不想閱讀整篇文章,可以閱讀這段摘要;如果你打算閱讀整篇文章,那么你完全可以跳過本段。

          如果閱讀摘要時未能幫助你理解,可以跳轉(zhuǎn)到對應(yīng)章節(jié)進行詳細閱讀。

          摘要:

          1. 這個 BUG 是如何產(chǎn)生的?
          • wxml2canvas 在繪制的時候,會根據(jù)一個叫做 sorted 的對象對它的 keys 進行遍歷,該對象的 key 為節(jié)點的 top 值,value 為節(jié)點元素;問題就是出在這里,該庫作者誤以為 Object.keys() 總是會按照實際創(chuàng)建屬性的順序返回,然而當 key 為正整數(shù)的時候,返回順序就不符合原本的預(yù)期了,會出現(xiàn)了繪制順序錯亂,從而導(dǎo)致這個 BUG 的產(chǎn)生。
          • 源碼:src/index.js#L1146[2] 和 src/index.js#L829[3]
          1. 如何解決這個 BUG
          • 由于對象的 key 是一個數(shù)字,那么 key 有可能會是整數(shù),也有可能是浮點數(shù)。但是預(yù)期行為是希望 Object.keys() 按照屬性實際創(chuàng)建的順序返回,那只要將所有 key 都強制轉(zhuǎn)換為浮點數(shù)就好了。
          1. Object.keys() 是按照什么順序返回值的?
          • Object.keys() 返回順序與遍歷對象屬性時的順序一樣,調(diào)用的 [[OwnPropertyKeys]]() 內(nèi)部方法。
          • 根據(jù) ECMAScript 規(guī)范[4],在輸出 keys 時會先將所有 key 為數(shù)組索引類型(正整數(shù))從小到大的順序排序,然后將所有字符串類型(包括負數(shù)、浮點數(shù))的 key 按照實際創(chuàng)建的順序來排序
          1. V8 內(nèi)部是如何處理對象屬性的?
          • V8 在存儲對象屬性時,為了提高訪問效率,會分為常規(guī)屬性(properties) 和 排序?qū)傩?elements)
            • 排序?qū)傩?elements) ,就是數(shù)組索引類型的屬性(也就是正整數(shù)類型)。
            • 常規(guī)屬性(properties) ,就是字符串類型的屬性(也包括負數(shù)、浮點數(shù))。
            • 以上兩種屬性都會存放在線性結(jié)構(gòu)中,稱為快屬性
            • 然而這樣每次查詢都有一個間接層,會影響效率,所以 V8 引入對象內(nèi)屬性(in-object-properties) 。
          • V8 會為每一個對象關(guān)聯(lián)一個隱藏類,用于記錄該對象的形狀,相同形狀的對象會共用同一個隱藏類。
            • 當對象添加、刪除屬性的時候,會創(chuàng)建一個新的對應(yīng)的隱藏類,并重新關(guān)聯(lián)。
          • 對象內(nèi)屬性會將部分常規(guī)屬性直接放在對象第一層,所以它訪問效率是最高的。
            • 常規(guī)屬性的數(shù)量少于對象初始化時的屬性數(shù)量時,常規(guī)屬性會直接作為對象內(nèi)屬性存放。
          • 雖然快屬性訪問速度快,但是從線性結(jié)構(gòu)中添加或刪除時執(zhí)行效率會非常低,因此如果屬性特別多、或出現(xiàn)添加和刪除屬性時,就會將常規(guī)屬性從線性存儲改為字典存儲,這就是慢屬性

          可以看一下這兩張圖幫助理解:

          V8 常規(guī)屬性和排序?qū)傩?/p>

          V8 對象內(nèi)屬性、快屬性和慢屬性

          圖片出處:《圖解 Google V8》[5]

          如何解決該 BUG

          由于是特定的動態(tài) + 特定的設(shè)備才能復(fù)現(xiàn)問題,可以很輕易地排除掉網(wǎng)絡(luò)原因,通過在 wxml2canvas 輸出繪制的節(jié)點列表,也能看到小程序碼相關(guān)的節(jié)點。

          既然 wxml2canvas 已經(jīng)接受到小程序碼的節(jié)點,卻沒有繪制出來,那么問題自然就出在 wxml2canvas 內(nèi)部,不過已經(jīng)見怪不怪了,在我加入項目以后就已經(jīng)多次因為這操蛋的 wxml2canvas 出現(xiàn)各種問題而搞得頭皮發(fā)麻,有機會一定要替換掉這個庫,但由于已經(jīng)有很多頁面在依賴這個庫,現(xiàn)在也只能硬著頭皮上。

          首先懷疑是小程序碼節(jié)點的坐標位置不太對,通過對比,發(fā)現(xiàn)位置相差不大,排除該原因。

          然后對比所有節(jié)點的繪制順序,發(fā)現(xiàn)了一個不太尋常的點,在復(fù)現(xiàn) BUG 的手機上,繪制小程序碼節(jié)點的時機是比較靠前的,但由于它在卡片底部,所以在正常情況下,應(yīng)該是比較靠后才對。

          于是通過查看相關(guān)代碼,果然發(fā)現(xiàn)了其中的玄機:

          在繪制的時候,通過遍歷 sorted 對象,從上往下、從左到右依次繪制,但是通過對比兩臺手機的 Object.keys(),發(fā)現(xiàn)了它們的輸出是不一樣的,這時候我就明白怎么回事了。

          先來說說這個 sorted 對象,它是一個 key 為節(jié)點 top 值,value 為所有相同 top 值(同一行)的元素數(shù)組。

          下面是生成它的代碼:

          問題就發(fā)生在前面所說的 Object.keys() 這里,我們先來看個 ??:

          const sorted = {}

          sorted[300] = {}
          sorted[200] = {}
          sorted[100] = {}

          console.log(Object.keys(sorted)) // 輸出什么呢?

          相信大部分同學(xué)都知道答案是:[‘100', '200', '300’]。

          如果在有浮點數(shù)的情況呢?

          const sorted = {}

          sorted[300] = {}
          sorted[100] = {}
          sorted[200] = {}
          sorted[50.5] = {}

          console.log(Object.keys(sorted)) // 這次又輸出什么呢?

          會不會有同學(xué)以為答案是:['50.5', ‘100', '200', '300’] 呢?

          但正確的答案應(yīng)該是:[‘100', '200', '300’,’50.5’]。

          所以我合理地猜測 wxml2canvas 的作者就是犯了這樣的錯誤,他可能以為 Object.keys 會根據(jù) key 從小到大的順序返回,因此滿足從上往下繪制的邏輯。

          但是他卻沒有考慮浮點數(shù)的情況,所以當某個節(jié)點 top 值為整數(shù)的時候,會比其他 top 值為浮點數(shù)的節(jié)點更早地繪制,導(dǎo)致繪制后面的節(jié)點時覆蓋了前面的節(jié)點。

          于是,當我把代碼改成這樣后,分享卡片的小程序碼就正常繪制出來了:

            Object
            .keys(sorted)
          + .sort((a, b)=> a - b)
            .forEach((top, topIndex) => {
              //  do something
            }

          OK,搞定收工。

          測試小姐姐:慢著!影響到其它地方了。

          我一看,果然。于是再次經(jīng)過對比,發(fā)現(xiàn)原來大部分情況下,top 值都會是浮點數(shù),而本次出 BUG 的卡片小程序碼只是非常湊巧地為整數(shù),導(dǎo)致繪制順序不對。

          我才發(fā)現(xiàn) wxml2canvas 原本的邏輯是想根據(jù) sorted 創(chuàng)建的順序來繪制,但是沒有考慮 key 為整數(shù)的情況。

          所以,最后通過這樣修改解決問題:

          _sortListByTop (list = []) {
              let sorted = {};

              // 粗略地認為2px相差的元素在同一行
              list.forEach((item, index) => {
          -       let top = item.top;
          +       let top = item.top.toFixed(6); // 強制添加小數(shù)點,將整數(shù)轉(zhuǎn)為浮點數(shù)
                  if (!sorted[top]) {
                      if (sorted[top - 2]) {
                          top = top - 2;
                      }else if (sorted[top - 1]) {
                          top = top - 1;
                      } else if (sorted[top + 1]) {
                          top = top + 1;
                      } else if (sorted[top + 2]) {
                          top = top + 2;
                      } else {
                          sorted[top] = [];
                      }
                  }
                  sorted[top].push(item);
              });

              return sorted;
          }

          很顯然,是因為 wxml2canvas 作者對 Object.keys() 返回順序的機制不了解,才導(dǎo)致出現(xiàn)這樣的 BUG。

          不知道是否也有同學(xué)犯過同樣的錯誤,為避免再次出現(xiàn)這樣的情況,非常有必要深入、全面地介紹一下 Object.keys() 的執(zhí)行機制。

          所以接下來就跟隨我一探究竟吧。

          深入理解 Object.keys()

          可能會有同學(xué)說:Object.keys() 又不是什么新出的 API, Google 一下不就行了,何必大費周章寫一篇文章來介紹呢?

          的確通過搜索引擎可以很快就能知道 Object.keys() 的返回順序是怎樣的,但是很多都只流于表面,甚至我還見過這樣片面的回答:數(shù)字排前面,字符串排后面。

          所以這次我想試著追本溯源,通過第一手資料來獲取信息,輕易相信口口相傳得來的信息,都極有可能是片面的、甚至是錯誤的。

          PS:其實不光技術(shù),我們在對待其它不了解的事物都應(yīng)保持同樣的態(tài)度。

          我們先來看看在 MDN[6] 上關(guān)于 Object.keys() 的描述:

          Object.keys() 方法會返回一個由一個給定對象的自身可枚舉屬性組成的數(shù)組,數(shù)組中屬性名的排列順序和正常循環(huán)遍歷該對象時返回的順序一致 。

          emmm... 并沒有直接告訴我們輸出順序是什么,不過我們可以看看上面的 Polyfill[7] 是怎么寫的:

          if (!Object.keys) {
            Object.keys = (function ({
              var hasOwnProperty = Object.prototype.hasOwnProperty,
                  hasDontEnumBug = !({toStringnull}).propertyIsEnumerable('toString'),
                  dontEnums = [
                    'toString',
                    'toLocaleString',
                    'valueOf',
                    'hasOwnProperty',
                    'isPrototypeOf',
                    'propertyIsEnumerable',
                    'constructor'
                  ],
                  dontEnumsLength = dontEnums.length;

              return function (obj{
                if (typeof obj !== 'object' && typeof obj !== 'function' || obj === nullthrow new TypeError('Object.keys called on non-object');

                var result = [];

                for (var prop in obj) {
                  if (hasOwnProperty.call(obj, prop)) result.push(prop);
                }

                if (hasDontEnumBug) {
                  for (var i=0; i < dontEnumsLength; i++) {
                    if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
                  }
                }
                return result;
              }
            })()
          };

          其實就是利用 for...in 來進行遍歷,接下來我們可以再看看關(guān)于 for...in[8] 的文檔,然而里面也沒有告訴我們順序是怎樣的。

          既然 MDN 上沒有,那我們可以直接看 ECMAScript 規(guī)范,通常 MDN 上都會附上關(guān)于這個 API 的規(guī)范鏈接,我們直接點開最新(Living Standard)的那個,下面是關(guān)于 Object.keys 的規(guī)范定義[9]

          When the keys function is called with argument O, the following steps are taken:

          1. Let obj be ? ToObject[10](O).
          2. Let nameList be ? EnumerableOwnPropertyNames[11](obj, key).
          3. Return CreateArrayFromList[12](nameList).

          對象屬性列表是通過 EnumerableOwnPropertyNames 獲取的,這是它的規(guī)范定義[9]

          The abstract operation EnumerableOwnPropertyNames takes arguments O (an Object) and kind (key, value, or key+value). It performs the following steps when called:

          1. Let ownKeys be ? O.[OwnPropertyKeys].

          2. Let properties be a new empty List.

          3. For each element key of ownKeys, do a. If Type(key) is String, then

            b. Else, 1. Let value be ? Get(O, key). 2. If kind is value, append value to properties. 3. Else i. Assert: kind is key+value. ii. Let entry be ! CreateArrayFromList(? key, value ?). iii. Append entry to properties.

            1. Let desc be ? O.GetOwnProperty.
            2. If desc is not undefined and desc.[[Enumerable]] is true, then a. If kind is key, append key to properties.
          4. Return properties.

          敲黑板!這里有個細節(jié),請同學(xué)們多留意,后面會考。

          我們接著探索,OwnPropertyKeys 最終返回的 OrdinaryOwnPropertyKeys

          The [[OwnPropertyKeys]] internal method of an ordinary object O takes no arguments. It performs the following steps when called:

          1. Return ! OrdinaryOwnPropertyKeys(O)[13].

          重頭戲來了,關(guān)于 keys 如何排序就在 OrdinaryOwnPropertyKeys 的定義中:

          The abstract operation OrdinaryOwnPropertyKeys takes argument O (an Object). It performs the following steps when called:

          1. Let keys be a new empty List.
          2. For each own property key P of O such that P is an array index, in ascending numeric index order, do a. Add P as the last element of keys.
          3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do a. Add P as the last element of keys.
          4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do a. Add P as the last element of keys.
          5. Return keys.

          到這里,我們已經(jīng)知道我們想要的答案,這里總結(jié)一下:

          1. 創(chuàng)建一個空的列表用于存放 keys
          2. 將所有合法的數(shù)組索引按升序的順序存入
          3. 將所有字符串類型索引按屬性創(chuàng)建時間以升序的順序存入
          4. 將所有 Symbol 類型索引按屬性創(chuàng)建時間以升序的順序存入
          5. 返回 keys

          這里順便也糾正一個普遍的誤區(qū):有些回答說將所有屬性為數(shù)字類型的 key 從小到大排序,其實不然,還必須要符合 「合法的數(shù)組索引」 ,也即只有正整數(shù)才行,負數(shù)或者浮點數(shù),一律當做字符串處理。

          PS:嚴格來說對象屬性沒有數(shù)字類型的,無論是數(shù)字還是字符串,都會被當做字符串來處理。

          我們結(jié)合上面的規(guī)范,來思考一下下面這段代碼會輸出什么:

          const testObj = {}

          testObj[-1] = ''
          testObj[1] = ''
          testObj[1.1] = ''
          testObj['2'] = ''
          testObj['c'] = ''
          testObj['b'] = ''
          testObj['a'] = ''
          testObj[Symbol(1)] = ''
          testObj[Symbol('a')] = ''
          testObj[Symbol('b')] = ''
          testObj['d'] = ''

          console.log(Object.keys(testObj))

          請認真思考后,在這里核對你的答案是否正確:

          查看結(jié)果 ??

          ['1', '2', '-1', '1.1', 'c', 'b', 'a', 'd']

          是否與你想象的一致?你可能會奇怪為什么沒有 Symbol 類型。

          還記得前面敲黑板讓同學(xué)們留意的地方嗎,因為在 EnumerableOwnPropertyNames 的規(guī)范中規(guī)定了返回值只應(yīng)包含字符串屬性(上面說了數(shù)字其實也是字符串)。

          所以 Symbol 屬性是不會被返回的,可以看 MDN[14] 上關(guān)于 Object.getOwnPropertyNames() 的描述。

          如果要返回 Symbol 屬性可以用 Object.getOwnPropertySymbols()[15]

          看完 ECMAScript 的規(guī)范定義,相信你不會再搞錯 Object.keys() 的輸出順序了。但是你好奇 V8 是如何處理對象屬性的嗎,下一節(jié)我們就來講講。

          V8 是如何處理對象屬性的

          在 V8 的官方博客上有一篇文章《Fast properties in V8》[16]中譯版[17]),非常詳細地向我們解釋了 V8 內(nèi)部如何處理 JavaScript 的對象屬性,強烈推薦閱讀。

          本節(jié)內(nèi)容主要參考這兩個地方,下面我們來總結(jié)一下。

          首先,V8 為了提高對象屬性的訪問效率,將屬性分為兩種類型:

          • 排序?qū)傩?elements) ,就是符合數(shù)組索引類型的屬性(也就是正整數(shù))。

          • 常規(guī)屬性(properties) ,就是字符串類型的屬性(也包括負數(shù)、浮點數(shù))。

          所有的排序?qū)傩?/strong>都會存放在一個線性結(jié)構(gòu)中,線性結(jié)構(gòu)的特點就是支持通過索引隨機訪問,所以能加快訪問速度,對于存放在線性結(jié)構(gòu)的屬性都稱為快屬性

          常規(guī)屬性也會存放在另一個線性結(jié)構(gòu)中,可以看下面這張圖幫助理解:

          V8 排序?qū)傩院统R?guī)屬性

          但是常規(guī)屬性還需要做一些額外的處理,這里我們要先介紹一下什么是隱藏類

          由于 JavaScript 在運行時是可以修改對象屬性的,所以在查詢的時候會比較慢,可以看回上面那張圖,每次訪問一個屬性的時候都需要經(jīng)過多一層的訪問,而像 C++ 這類靜態(tài)語言在聲明對象之前需要定義這個對象的結(jié)構(gòu)(形狀),經(jīng)過編譯后每個對象的形狀都是固定的,所以在訪問的時候由于知道了屬性的偏移量,自然就會比較快。

          V8 采用的思路就是將這種機制應(yīng)用在 JavaScript 對象中,所以引入了隱藏類的機制,你可以簡單的理解隱藏類就是描述這個對象的形狀、包括每個屬性對應(yīng)的位置,這樣查詢的時候就會快很多。

          關(guān)于隱藏類還有幾點要補充:

          1. 對象的第一個字段指向它的隱藏類
          2. 如果兩個對象的形狀是完全相同的,會共用同一個隱藏類
          3. 當對象添加、刪除屬性的時候,會創(chuàng)建一個新的對應(yīng)的隱藏類,并重新指向它。
          4. V8 有一個轉(zhuǎn)換樹的機制來創(chuàng)建隱藏類,不過本文不贅述,有興趣可以看這里[18]

          解釋完隱藏類,我們再回頭來講講常規(guī)屬性,通過上面那張圖我們很容易發(fā)現(xiàn)一個問題,那就是每次訪問一個屬性的時候,都需要經(jīng)過一個間接層才能訪問,這無疑降低了訪問效率,為了解決這個問題,V8 又引入了一個叫做對象內(nèi)屬性,顧名思義,它會將某些屬性直接存放在對象的第一層里,它的訪問是最快的,如下圖所示:

          V8 對象內(nèi)屬性

          但要注意,對象內(nèi)屬性只存放常規(guī)屬性,排序?qū)傩砸琅f不變。而且需要常規(guī)屬性的數(shù)量小于某個數(shù)量的時候才會直接存放對象內(nèi)屬性,那這個數(shù)量是多少呢?

          答案是取決于對象初始化時的大小

          PS:有些文章說是少于 10 個屬性時才會存放對象內(nèi)屬性,別被誤導(dǎo)了

          除了對象內(nèi)屬性快屬性以外,還有一個慢屬性

          為什么會有慢屬性呢?快屬性雖然訪問很快,但是如果要從對象中添加或刪除大量屬性,則可能會產(chǎn)生大量時間和內(nèi)存開銷來維護隱藏類,所以在屬性過多或者反復(fù)添加、刪除屬性時會將常規(guī)屬性的存儲方式從線性結(jié)構(gòu)變成字典,也就是降低到慢屬性,而由于慢屬性的信息不會再存放在隱藏類中,所以它的訪問會比快屬性要慢,但是可以高效地添加和刪除屬性。可以通過下圖幫助理解:

          V8 慢屬性

          寫到這里,我覺得自己對 V8 的快屬性、慢屬性這些知識已經(jīng)非常了解,簡直要牛逼到上天了。

          但當我看到這段代碼的時候:

          function toFastProperties(obj{
              /*jshint -W027*/
              function f({}
              f.prototype = obj;
              ASSERT("%HasFastProperties"true, obj);
              return f;
              eval(obj);
          }

          我的心情是這樣的:

          關(guān)于這段代碼是如何能讓 V8 使用對象快屬性的可以看這篇文章:開啟 V8 對象屬性的“fast”模式[19]

          另外也可以看一下這段代碼:to-fast-properties/index.js[20]

          寫在最后

          當在開發(fā)時遇到一個簡單的錯誤,通常可以很快地利用搜索引擎解決問題,但如果只是面向 Google 編程,可能在技術(shù)上很難會有進步,所以我們不光要能解決問題,還要理解這個產(chǎn)生問題的背后的原因到底是什么,也就是知其然更知其所以然。

          真的非常建議每個 JavaScript 開發(fā)者都應(yīng)該去了解一些關(guān)于 V8 或其它 JavaScript 引擎的知識,無論你是通過什么途徑(真的沒有打廣告),這樣能保證我們在編寫 JavaScript 代碼時出現(xiàn)問題可以更加地得心應(yīng)手。

          最后,本文篇幅有限,部分細節(jié)難免會有遺漏,非常建議有興趣深入了解的同學(xué)可以延伸閱讀下面的列表。

          延伸閱讀

          • Fast properties in V8[16]
            • 中譯版[17]
          • How is data stored in V8 JS engine memory?[21]
          • V8 中的快慢屬性與快慢數(shù)組[22]
          • 開啟 V8 對象屬性的“fast”模式[23]
          • ECMAScript? 2015 Language Specification[24]
          • Does JavaScript guarantee object property order? —— stackoverflow[25]

          參考資料

          [1]

          https://github.com/wg-front/wxml2canvas

          [2]

          https://github.com/wg-front/wxml2canvas/blob/master/src/index.js#L1146

          [3]

          https://github.com/wg-front/wxml2canvas/blob/master/src/index.js#L829

          [4]

          https://262.ecma-international.org/6.0/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys

          [5]

          https://time.geekbang.org/column/intro/100048001

          [6]

          https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

          [7]

          https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/keys#polyfill

          [8]

          https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for...in

          [9]

          https://tc39.es/ecma262/#sec-object.keys

          [10]

          https://tc39.es/ecma262/#sec-toobject

          [11]

          https://tc39.es/ecma262/#sec-enumerableownpropertynames

          [12]

          https://tc39.es/ecma262/#sec-createarrayfromlist

          [13]

          https://tc39.es/ecma262/#sec-ordinaryownpropertykeys

          [14]

          https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames

          [15]

          https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols

          [16]

          https://v8.dev/blog/fast-properties

          [17]

          https://blog.crimx.com/2018/11/25/v8-fast-properties/

          [18]

          https://v8.dev/blog/fast-properties#hiddenclasses-and-descriptorarrays

          [19]

          https://zhuanlan.zhihu.com/p/25069272

          [20]

          https://github.com/sindresorhus/to-fast-properties/blob/main/index.js

          [21]

          https://blog.dashlane.com/how-is-data-stored-in-v8-js-engine-memory/

          [22]

          https://z3rog.tech/blog/2020/fast-properties.html

          [23]

          https://zhuanlan.zhihu.com/p/25069272

          [24]

          https://262.ecma-international.org/6.0/

          [25]

          https://stackoverflow.com/questions/5525795/does-javascript-guarantee-object-property-order

          作者:4Ark

          https://juejin.cn/post/7041049741458669576

          Node 社群



          我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學(xué)習、共建。下方加 考拉 好友回復(fù)「Node」即可。



          如果你覺得這篇內(nèi)容對你有幫助,我想請你幫我2個小忙:

          1. 點個「在看」,讓更多人也能看到這篇文章
          2. 訂閱官方博客 www.inode.club 讓我們一起成長

          點贊和在看就是最大的支持

          瀏覽 40
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  AAA片播放 | 成人一级黄片 | 伊人视频在线观看 | 欧美女三级片网站 | 久久国产高清视频免费看 |