「讀懂源碼系列2」我從 lodash 源碼中學到的幾個知識點

前言

上一篇文章 「前端面試題系列8」數組去重(10 種濃縮版) 的最后,簡單介紹了 lodash 中的數組去重方法 _.uniq,它可以實現我們日常工作中的去重需求,能夠去重 NaN,并保留 {...}

今天要講的,是我從 _.uniq 的源碼實現文件 baseUniq.js 中學到的幾個很基礎,卻又容易被忽略的知識點。

三個 API

讓我們先從三個功能相近的 API 講起,他們分別是:_.uniq_.uniqBy_.uniqWith。它們三個背后的實現文件,都指向了 .internal 下的 baseUniq.js

區別在于 _.uniq 只需傳入一個源數組 array, _.uniqBy 相較于 _.uniq 要多傳一個迭代器 iteratee,而 _.uniqWith 要多傳一個比較器 comparator。iterateecomparator 的用法,會在后面說到。

以 _.uniqWith 為例,它是這樣調用 _.baseUniq 的:

function uniqWith(array, comparator) {comparator = typeof comparator == 'function' ? comparator : undefinedreturn (array != null && array.length)? baseUniq(array, undefined, comparator): []
}
復制代碼

baseUniq 的實現原理

baseUniq 的源碼并不多,但比較繞。先貼一下的源碼。

const LARGE_ARRAY_SIZE = 200function baseUniq(array, iteratee, comparator) {let index = -1let includes = arrayIncludeslet isCommon = trueconst { length } = arrayconst result = []let seen = resultif (comparator) {isCommon = falseincludes = arrayIncludesWith}else if (length >= LARGE_ARRAY_SIZE) {const set = iteratee ? null : createSet(array)if (set) {return setToArray(set)}isCommon = falseincludes = cacheHasseen = new SetCache}else {seen = iteratee ? [] : result}outer:while (++index < length) {let value = array[index]const computed = iteratee ? iteratee(value) : valuevalue = (comparator || value !== 0) ? value : 0if (isCommon && computed === computed) {let seenIndex = seen.lengthwhile (seenIndex--) {if (seen[seenIndex] === computed) {continue outer}}if (iteratee) {seen.push(computed)}result.push(value)}else if (!includes(seen, computed, comparator)) {if (seen !== result) {seen.push(computed)}result.push(value)}}return result
}
復制代碼

為了兼容剛才說的三個 API,就產生了不少的干擾項。如果先從 _.uniq 入手,去掉 iteratee 和 comparator 的干擾,就會清晰不少。

function baseUniq(array) {let index = -1const { length } = arrayconst result = []if (length >= 200) {const set = createSet(array)return setToArray(set)}outer:while (++index < length) {const value = array[index]if (value === value) {let resultIndex = result.lengthwhile (resultIndex--) {if (result[resultIndex] === value) {continue outer}}result.push(value)} else if (!includes(seen, value)) {result.push(value)}}return result
}
復制代碼

這里有 2 個知識點。

知識點一、NaN === NaN 嗎?

在源碼中有一個判斷 value === value,乍一看,會覺得這是句廢話!?!但其實,這是為了過濾 NaN 的情況。

MDN 中對 NaN 的解釋是:它是一個全局對象的屬性,初始值就是 NaN。它通常都是在計算失敗時,作為 Math 的某個方法的返回值出現的。

判斷一個值是否是 NaN,必須使用 Number.isNaN()isNaN(),在執行自比較之中:NaN,也只有 NaN,比較之中不等于它自己。

NaN === NaN;        // false
Number.NaN === NaN; // false
isNaN(NaN);         // true
isNaN(Number.NaN);  // true
復制代碼

所以,在源碼中,當遇到 NaN 的情況時,baseUniq 會轉而去執行 !includes(seen, value) 的判斷,去處理 NaN 。

知識點二、冒號的特殊作用

在源碼的主體部分,while 語句之前,有一行 outer:,它是干什么用的呢? while 中還有一個 while 的內部,有一行 continue outer,從語義上理解,好像是繼續執行 outer,這又是種什么寫法呢?

outer:
while (++index < length) {...while (resultIndex--) {if (result[resultIndex] === value) {continue outer}}
}
復制代碼

我們都知道 Javascript 中,常用到冒號的地方有三處,分別是:A ? B : C 三元操作符、switch case 語句中、對象的鍵值對組成

但其實還有一種并不常見的特殊作用:標簽語句。在 Javascript 中,任何語句都可以通過在它前面加上標志符和冒號來標記(identifier: statement),這樣就可以在任何地方使用該標記,最常用于循環語句中。

所以,在源碼中,outer 只是看著有點不習慣,多看兩遍就好了,語義上還是很好理解的。

_.uniqBy 的 iteratee

_.uniqBy 可根據指定的 key 給一個對象數組去重,一個官網的例子如下:

// The `_.property` iteratee shorthand.
_.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
// => [{ 'x': 1 }, { 'x': 2 }]
復制代碼

這里的 'x'_.property('x') 的縮寫,它指的就是 iteratee

從給出的例子和語義上看,還挺好理解的。但是為什么 _.property 就能實現對象數組的去重了呢?它又是如何實現的呢?

@param {Array|string} path The path of the property to get.
@returns {Function} Returns the new accessor function.function property(path) {return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path)
}
復制代碼

從注釋看,property 方法會返回一個 Function,再看 baseProperty 的實現:

@param {string} key The key of the property to get.
@returns {Function} Returns the new accessor function.function baseProperty(key) {return (object) => object == null ? undefined : object[key]
}
復制代碼

咦?怎么返回的還是個 Function ?感覺它什么也沒干呀,那個參數 object 又是哪里來的?

知識點三、純函數的概念

純函數,是函數式編程中的概念,它代表這樣一類函數:對于指定輸出,返回指定的結果。不存在副作用

// 這是一個簡單的純函數
const addByOne = x => x + 1;
復制代碼

也就是說,純函數的返回值只依賴其參數,函數體內不能存在任何副作用。如果是同樣的參數,則一定能得到一致的返回結果。

function baseProperty(key) {return (object) => object == null ? undefined : object[key]
}
復制代碼

baseProperty 返回的就是一個純函數,在符合條件的情況下,輸出 object[key]。在函數式編程中,函數是“一等公民”,它可以只是根據參數,做簡單的組合操作,再作為別的函數的返回值。

所以,在源碼中,object 是調用 baseProperty 時傳入的對象。 baseProperty 的作用,是返回期望結果為 object[key] 的函數。

_.uniqWith 的 comparator

還是先從官網的小例子說起,它會完全地給對象中所有的鍵值對,進行比較。

var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];_.uniqWith(objects, _.isEqual);
// => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
復制代碼

而在 baseUniq 的源碼中,可以看到最終的實現,需要依賴 arrayIncludesWith 方法,以下是它的源碼:

function arrayIncludesWith(array, target, comparator) {if (array == null) {return false}for (const value of array) {if (comparator(target, value)) {return true}}return false
}
復制代碼

arrayIncludesWith 沒什么復雜的。comparator 作為一個參數傳入,將 targetarray 的每個 value 進行處理。從官網的例子看,_.isEqual 就是 comparator,就是要比較它們是否相等。

接著就追溯到了 _.isEqual 的源碼,它的實現文件是 baseIsEqualDeep.js。在里面看到一個讓我犯迷糊的寫法,這是一個判斷。

/** Used to check objects for own properties. */
const hasOwnProperty = Object.prototype.hasOwnProperty
...const objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__')
復制代碼

hasOwnProperty ?call, 'wrapped' ?

知識點四、對象的 hasOwnProperty

再次查找到了 MDN 的解釋:所有繼承了 Object 的對象都會繼承到 hasOwnProperty 方法。它可以用來檢測一個對象是否含有特定的自身屬性;會忽略掉那些從原型鏈上繼承到的屬性。

o = new Object();
o.prop = 'exists';
o.hasOwnProperty('prop');             // 返回 true
o.hasOwnProperty('toString');         // 返回 false
o.hasOwnProperty('hasOwnProperty');   // 返回 false
復制代碼

call 的用法可以參考這篇 細說 call、apply 以及 bind 的區別和用法。

那么 hasOwnProperty.call(object, '__wrapped__') 的意思就是,判斷 object 這個對象上是否存在 'wrapped' 這個自身屬性。

wrapped 是什么屬性?這就要說到 lodash 的延遲計算方法 _.chain,它是一種函數式風格,從名字就可以看出,它實現的是一種鏈式的寫法。比如下面這個例子:

var names = _.chain(users).map(function(user){return user.user;}).join(" , ").value();
復制代碼

如果你沒有顯樣的調用value方法,使其立即執行的話,將會得到如下的LodashWrapper延遲表達式:

LodashWrapper {__wrapped__: LazyWrapper, __actions__: Array[1], __chain__: true, constructor: function, after: function…}
復制代碼

因為延遲表達式的存在,因此我們可以多次增加方法鏈,但這并不會被執行,所以不會存在性能的問題,最后直到我們需要使用的時候,使用 value() 顯式立即執行即可。

所以,在 baseIsEqualDeep 源碼中,才需要做 hasOwnProperty 的判斷,然后在需要的情況下,執行 object.value()

總結

閱讀源碼,在一開始會比較困難,因為會遇到一些看不明白的寫法。就像一開始我卡在了 value === value 的寫法,不明白它的用意。一旦知道了是為了過濾 NaN 用的,那后面就會通暢很多了。

所以,閱讀源碼,是一種很棒的重溫基礎知識的方式。遇到看不明白的點,不要放過,多查多問多看,才能不斷地夯實基礎,讀懂更多的源碼思想,體會更多的原生精髓。如果我在一開始看到 value === value 時就放棄了,那或許就不會有今天的這篇文章了。

PS:歡迎關注我的公眾號 “超哥前端小棧”,交流更多的想法與技術。

轉載于:https://juejin.im/post/5c8c6c26f265da2db3059c93

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/279884.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/279884.shtml
英文地址,請注明出處:http://en.pswp.cn/news/279884.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

有小伙伴問:上位機用QT還是winform/wpf好?

楔子有小伙伴問&#xff1a;上位機用QT還是winform/wpf好&#xff1f;Qt是C寫的&#xff0c;跨平臺的UI框架&#xff0c;Winform/wpf是C#寫的不跨平臺的Windows上運行的UI框架。這兩個說到底是語言本質的爭論或者區別。優點Qt的優點是可以跨平臺運行UI界面&#xff0c;在Linux&…

使用jenkins進行項目的自動構建部署

jenkins 簡介 Jenkins是基于Java開發的一種持續集成工具&#xff0c;用于監控持續重復的工作&#xff0c;功能包括&#xff1a;持續的軟件版本發布/測試項目和監控外部調用執行的工作。 官網地址地址&#xff1a; https://jenkins.io 下載安裝啟動 CentOS 下用yum進行安裝啟動 …

如何刪除Apple Music中的連接功能

Love Apple Music, but tired of the intrusive Connect feature taking up space on your favorite artist’s page? Well, don’t worry, because getting “dis-Connected” is just a matter of changing a few simple settings in your iPhone or iPad running iOS 8.0 o…

python設計模式(十四):模板方法模式

定義一個算法或者流程&#xff0c;部分環節設計為外部可變&#xff0c;用類似于模板的思想來實例化一個實體&#xff0c;可以往模板中填充不同的內容&#xff1b;在模板思想下&#xff0c;實體的整體框架是確定的&#xff0c;他是一個模板&#xff0c;但是模板下內容可變&#…

FirstBird--項目流程

創建項目(英文路徑)—–img圖片文件創建窗體–設置大小(Basic—size–>320*480)—最大化功能禁用(Expert–>setResizable(false))添加面板–設置布局方式(set Layout—>AbsoluteLayout)自己創建面板 GameMain中將Jpanel1改為WinJpanel–創建對應類–>extends JPane…

PeeringDB初探

做網絡相關工作的&#xff0c;可能需要了解PeeringDB這個網站&#xff08;https://www.peeringdb.com)&#xff0c; 這里有大部分公開注冊的 ASN&#xff08;Autonomous System Number) 以及他們相互直接做Peering的信息&#xff0c;這也是這個網站名字的由來。據統計&#xff…

網站排障分析命令

系統連接狀態篇&#xff1a;1.查看TCP連接狀態netstat-nat|awk{print$6}|sort|uniq-c|sort-rnnetstat-n|awk/^tcp/{print$NF}|sort|uniq-c|sort-rnnetstat-ant|awk{print$NF}|grep-v[a-z]|sort|uniq-c2.查找請求數請20個IP&#xff08;常用于查找攻來源&#xff09;&#xff1a…

修復windows臉部識別_如何在Windows 10中改善面部識別

修復windows臉部識別If you have the right hardware, Windows 10 lets you unlock your computer with nothing but a smile. However, Microsoft’s facial recognition isn’t always spot-on. Here’s how to help Windows recognize you better. 如果您擁有合適的硬件&…

使用組策略推送exchange自簽名證書

一、導出證書打開證書頒發機構&#xff0c;在證書服務器上面選屬性&#xff0c;然后按照下圖進行導出操作。 在選擇格式時按照上圖標識選擇。 二、導入證書新建一個組策略&#xff0c;在計算機配置-策略-windows設置-安全設置-公鑰策略中選中“受信任的根證書頒發機構”并新建導…

基于.NetCore開發,前端支持Layui、React、Vue且前后端分離的快速開發框架

今天給大家推薦一個基于.Net Core開發的&#xff0c;前端框架支持Layui、React、Vue&#xff0c;并且前端和后端都支持代碼一鍵生成&#xff0c;用于項目開發&#xff0c;可極大的提升開發效率。項目簡介這是基于.net core的快速開發框架&#xff0c;前端框架可以根據自己需求選…

PHP常用工具方法集...

PHP常用工具方法集&#xff0c;更新時間 2018-7-14 <?php /*** 常用工具方法集* Author: zj*//** 工具總述 1.加密解密 2.生成隨機字符串 3.獲取文件擴展名&#xff08;后綴&#xff09; 4.文件大小格式化 5.替換標簽字符 6.列出目錄下的文件名 7.獲取當前頁面URL 8.讓瀏覽…

一題多解 面試題

最近在其他論壇上看到幾個網友的面試題&#xff0c;這些天&#xff0c;QQ群內的人都在討論怎么解答才最簡單&#xff0c;下面列出題目&#xff1a; 文件a&#xff1a; 文件b: a b c a b c b c a b c a c b a …

什么是Google On.Here,以及如何設置?

Google Wi-Fi is similar to other mesh Wi-Fi systems, but one big feature separates it from the pack: Google On.Here. Google Wi-Fi與其他網狀Wi-Fi系統相似&#xff0c;但其中一個重要功能將其與眾不同&#xff1a;Google On.Here。 發生什么了&#xff1f; (What Is O…

一張圖看懂 SQL 的各種 join 用法

原文鏈接https://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins 轉載于:https://www.cnblogs.com/xuchao0506/p/10559951.html

1Python全棧之路系列Web框架介紹

Python全棧之路系列之Web框架介紹 所有的語言Web框架本質其實就是起一個socket服務端,監聽一個端口,然后運行起來 Web框架包含兩部分,一部分是socket,另外一部分是業務的邏輯處理,根據請求的不同做不同的處理 Python的Web框架分成了兩類, 即包含socket也包含業務邏輯處理的(tor…

『 再看.NET7』數值類型

在C#中&#xff0c;有int16&#xff0c;用short來定義&#xff1b;有int32&#xff0c;用int定義&#xff1b;用int64&#xff0c;用long來定義。在.NET7中&#xff0c;添加了int128&#xff0c;和unint128&#xff0c;位數更大的整型。var i16 short.MaxValue; Console.Write…

獲取幫助命令

whatis 基于數據庫的查找,查找內容比較慢 優點&#xff1a;查找速度快 缺點&#xff1a;沒有實時性 [rootlocalhost ~]# whatis ls ls (1) - list directory contents ls (1p) - list directory contents 數據庫文件 Centos6:/…

筆記本電腦升級固態硬盤好嗎_如何升級筆記本電腦硬盤

筆記本電腦升級固態硬盤好嗎Upgrading your laptop’s hard drive is a great way to get some extra life out of an old machine (or resurrect a dead one). Read on as we walk you through the prep work, the installation, and the followup. 升級筆記本電腦的硬盤驅動器…

購物單

小明剛剛找到工作&#xff0c;老板人很好&#xff0c;只是老板夫人很愛購物。老板忙的時候經常讓小明幫忙到商場代為購物。小明很厭煩&#xff0c;但又不好推辭。 這不&#xff0c;XX大促銷又來了&#xff01;老板夫人開出了長長的購物單&#xff0c;都是有打折優惠的。 …

Seay源代碼審計系統

這是一款基于C#語言開發的一款針對PHP代碼安全性審計的系統&#xff0c;主要運行于Windows系統上。這款軟件能夠發現SQL注入、代碼執行、命令執行、文件包含、文件上傳、繞過轉義防護、拒絕服務、XSS跨站、信息泄露、任意URL跳轉等漏洞。 下載鏈接 https://pan.baidu.com/s/1V…