從一道前端面試題引發的原理性探究

来源:https://www.cnblogs.com/w3cfed/archive/2020/04/05/vue-react-key.html
-Advertisement-
Play Games

作者 | Function | 前端時空 Vue 和 React 中的 key 到底有什麼用? key 是給每一個 vnode 的唯一 id,依靠 key,我們的 diff 操作可以更準確、更快速。 對於簡單列表頁渲染來說 diff 節點也更快,但會產生一些隱藏的副作用,比如可能不會產生過渡效果,或 ...


作者 | Function | 前端時空

Vue 和 React 中的 key 到底有什麼用?

key 是給每一個 vnode 的唯一 id,依靠 key,我們的 diff 操作可以更準確、更快速。 對於簡單列表頁渲染來說 diff 節點也更快,但會產生一些隱藏的副作用,比如可能不會產生過渡效果,或者在某些節點有綁定數據(表單)狀態,會出現狀態錯位。)

diff 演算法的過程中,先會進行新舊節點的首尾交叉對比,當無法匹配的時候會用新節點的 key 與舊節點進行比對,從而找到相應舊節點。

你以為這樣回答,面試官就能放過你。Too young,To simple。下麵是面試官的反問三連擊:

為什麼更準確??

因為帶 key 就不是就地復用了,在 sameNode 函數 a.key === b.key 對比中可以避免就地復用的情況。所以會更加準確,如果不加 key,會導致之前節點的狀態被保留下來,會產生一系列的 bug。

為什麼更快速?

key 的唯一性可以被 Map 數據結構充分利用,相比於遍歷查找的時間複雜度 O(n),Map 的時間複雜度僅僅為 O(1)。

為什麼 Map 數據結構會更快?

Map / Set / WeakSet / WeakMap 就是使用隱藏的 Hash code 優化哈希表

ECMAScript 2015 引入了幾個新的數據結構,例如 Map,Set,WeakSet和 WeakMap,所有這些結構都在後臺使用哈希表。下麵詳細介紹了V8 v6.3+ 如何將 key 存儲在哈希表中的最新進展。

哈希碼 Hash code

散列函數用於將給定的 key 映射到哈希表中的特定位置。一個哈希碼是給定的 key 運行此散列函數的運算結果。
hashCode = hashFunc(key)

在 V8 中,哈希碼只是一個隨機數,與對象值無關。因此,我們無法重新計算它,這意味著我們必須存儲它。

以前,對於那些把 JavaScript 對象作為 key 的情況,V8 將哈希碼作為私有符號(private symbol)存儲在對象上。V8 中的私有符號類似於Symbol,只是它不可枚舉,也不會不會泄漏到用戶空間 JavaScript 中。也就是說這個 symbol 只在 V8 引擎內部使用,用戶的 JavaScript 代碼訪問不到。

function GetObjectHash(key) {
  let hash = key[hashCodeSymbol];
  if (IS_UNDEFINED(hash)) {
    hash = (MathRandom() * 0x40000000) | 0;
    if (hash === 0) hash = 1;
    key[hashCodeSymbol] = hash;
  }
  return hash;
}

之所以行之有效,是因為在將對象添加到哈希表之前,我們不必為哈希碼欄位保留記憶體。當對象被添加到哈希表時,才把新的私有符號存儲在對象上。

與使用內聯緩存(IC)系統進行的任何其他屬性查找一樣,V8 還可以優化哈希碼符號查找,從而為哈希碼提供非常快速的查找。當鍵具有相同的隱藏類時,這對於單態內聯緩存查找非常有效。但是,大多數現實世界的代碼都不遵循這種模式,並且鍵通常具有不同的隱藏類,導致散列碼的復態內聯緩存查找變慢。

私有符號方法的另一個問題是,它在存儲哈希碼時觸發了密鑰的隱藏類轉換。這導致不僅對哈希代碼查找也為上鍵和其他財產查找差的多形態代碼去優化從優化代碼。

JavaScript 對象支持存儲

V8 的 JavaScript 對象(JSObject)使用 2 個 word(除了它的頭部):一個 word 用於存儲指向元素存儲的指針,另一個 word 用於存儲指向屬性存儲的指針。
  • word (computer architecture)

元素存儲用於像數組索引的屬性,而屬性存儲用於其鍵為字元串或符號的屬性。有關這些的更多信息,請參見 Camillo Bruni 的 V8 博客文章。

const x = {};
x[1] = 'bar';      // ← stored in elements
x['foo'] = 'bar';  // ← stored in properties

隱藏哈希碼 Hiding the hash code

存儲哈希碼最簡單的方法是將 JavaScript 對象的大小擴展一個字,並將散列碼直接存儲在對象上。但是,對於那些沒有添加到哈希表中的對象,這會浪費記憶體。相反,我們可以嘗試將散列碼存儲在元素存儲或屬性存儲中。

元素存儲是一個包含其長度和所有元素的數組。在這裡沒有太多的工作要做,因為可以把哈希碼存儲在一個保留的槽中(比如第 0 個索引),不過,當我們不使用這個對象作為哈希表中的關鍵字時,仍然會浪費記憶體。

讓我們看看屬性存儲。有兩種數據結構用作屬性存儲:數組字典

與元素存儲中使用的數組不同,元素存儲不具有上限,而屬性存儲中使用的數組的上限為 1022 個值。由於性能原因,V8 在超過此限制時則轉換為使用字典模式。(我略微簡化了這一點 - V8 也可以在其他情況下使用字典,但是可以存儲在數組中的值的數量有一個固定的上限。)

因此,屬性存儲有三種可能的狀態:

  • 空(沒有屬性)
  • 數組(最多可以存儲 1022 個值)
  • 字典

1、屬性存儲是空的

對於空的情況,我們可以直接在 JSObject 的偏移量上存儲哈希碼。

2、屬性存儲是一個數組

V8 表示小於 231 的整數(在 32 位系統上)更加高效,如 Smi。在一個 Smi 中,最低有效位是用來區別指針的 tag,而其餘的 31 位保存實際的整數值。

通常,數組將它們的長度存儲為 Smi。既然我們知道這個數組的最大容量只有 1022 個,我們只需要 10 個比特就可以存儲這個長度。我們可以使用剩下的 21 位來存儲哈希碼!

3、屬性支持存儲是一個字典

對於字典的情況,我們將字典大小增加1個字,以便將哈希碼存儲在字典起始位置的專用槽中。在這種情況下,我們可能會浪費掉一個字的存儲空間,因為這個比例增長的大小並不像數組那麼大。

通過這些更改,哈希碼查找不再需要經過複雜的 JavaScript 屬性查找機制。

性能改進

SixSpeed 對 Map 和 Set 的基準測試,這些變化導致了 5〜50% 的性能提升。

這一變化也導致 ARES6 中的基準測試提高了 5%。

這也導致 Emberperf 基準測試套件中測試的 Ember.js 提高了 18%。

探究總結

掌握一門技術併合理使用它的最好辦法就是深入理解這項技術背後的工作原理

參考資料

https://v8.dev/blog/hash-code

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 先安裝zsh 安裝oh my zsh 手動安裝 自動安裝 ` 不管使用手動 自動安裝,完成後都需要重啟。oh my zsh才能生效。 修改主題 挑選你喜歡的主題:https://github.com/robbyrussell/oh my zsh/wiki/Themes ...
  • Redis是一個開源的、基於記憶體的數據結構存儲器,可以用作資料庫、緩存和消息中間件 Redis最常用的功能 緩存 分散式鎖 ...
  • 創建3台虛擬機 主機為桌面版 其他為迷你版本 ******************************常用命令、進程名稱****************************啟動集群命令: start-all.sh啟動zookeeper: zkServer.sh start 啟動journal ...
  • 統計一個表的數據量是經常遇到的需求,但是不同的表設計及不同的寫法,統計性能差別會有較大的差異,下麵就簡單通過實驗進行測試(大家測試的時候註意緩存的情況,否則影響測試結果)。 1、 準備工作 為了後續測試工作的進行,先準備幾張用於測試的表及數據,為了使測試數據具有參考意義,建議測試表的數據量大一點,以 ...
  • 簡介 Flutter Fly是什麼?Flutter Fly是一款開源的Flutter 項目,非常適合初學者進行學習。App內集成了160+Flutter基礎控制項的詳細介紹及用法,內容來源於: "http://laomengit.com/" 。 歡迎頁: 首頁、控制項頁面、詳情頁及搜索頁面: 我: Ap ...
  • 質數是指在大於1的自然數中,除了1和它自身外沒有其他因數的自然數。 一、標記法,flag初始值為true,當n%i 0時(1<i<n),說明n不是質數,此時flag值為false且迴圈終止;當n%i != 0 時,flag的值始終為true,此時會輸出n是質數。 <!DOCTYPE html> <h ...
  • 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <tit ...
  • Vue.js作為目前最熱門最具前景的前端框架之一,其提供了一種幫助我們快速構建並開發前端項目的新的思維模式。本文旨在幫助大家認識Vue.js,並詳細介紹使用vue-cli腳手架工具快速的創建Vue項目,以及對項目目錄結構的解釋說明,使大家清晰的瞭解Vue項目的開發流程。 ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...