Lodash 源碼中的那些迷人的細節
巧妙的函數實現吸引著你想去看看他的實現方法,裡面會有更多奇思妙想讓你欣喜若狂...
去年的時候,決心去研究Lodash 中方法實現。執行的路數就是對照API 文檔,先用自己的想法去實現,然後再對照源碼找差別... 當看到與作者一致的實現的時候,我會不要臉的跟自己說一句:你好聰明,哈哈...如此這般,花了幾個月零散的時間覆蓋了部分API,沉澱了一些實現過程和思路,感興趣的走傳送門>>> ...今天又去翻看了一下,看到裡面有一些精彩的地方,遂挑出來與君分享。
1. length = start > end ? 0 : ((end - start) >>> 0)
在Lodash 的代碼中,會經常看到這樣的代碼片段,需要根據傳入的Array 的初始和結束索引求得要操作的數據元素的長度。
那>>> 0
有什麼用處?
在JS中,Array.length是一個32位無符號整型數字,而通過無符號位移運算>>> 0
,就是為了確保我們得到的正確的length值,它總是能得到一個32-bit unsigned ints ... 比如:
- 1 >>> 0 // 4294967295 0 >>> 0 // 0 '1' >>> 0 // 1 '1x' >>> 0 // 0 null >>> 0 // 0
而且,對於異常情況的包容也可以讓我們減少一些類型判斷和顯示的強制類型轉化的操作。
因此,在有用到Array.length 的場景,可用>>> 0 做參數防護。
2. arr instanceof Array
我們會用instanceof 去判斷實例與類之間的關係,但是這種關係可靠嗎?
arr instanceof Array 為true 肯定arr 就是一個數組,但是為false 是不是就表示arr 肯定不是數組呢?
不能!在有多個frame 的時候就不能...
let xArray = window . frames [ 0 ]. Array ; // origin from iframe's window let arr = new xArray ( 1 , 2 , 3 ); arr instanceof Array ; // false Array . isArray ( arr ); // true
也就是,你以為的並不都是你以為的... 雖然我們平時不常會有多個frame 間這種級別的類的混用,但是以防萬一...
通常,在需要判斷是否是數組時,可以使用Array.isArray
或者它的Polyfill:
if(!Array.isArray){Array.isArray=function(arg){returnObject.prototype.toString.call(arg)==='[object Array]';};}
3. _.eq(value, other)
判斷value和other是否(強)相等,在ECMA規範中對於相等其實是有明確的定義的,其中NaN === NaN
,但是在JS中,NaN是不與任何值相等的,包括自身,而Lodash彌補了這部分的不足,那怎麼判斷NaN === NaN 呢?
functioneq(value,other){returnvalue===other||(value!==value&&other!==other)}
我們知道NaN是唯一個不與自身相等的值,因此可以通過這個特性分別得到value和other是否是NaN,如果是,則兩者相等,返回true.
4. _.isNaN
如何判斷是否是NaN ?
通過isNaN 全局方法?
isNaN ( 'x' ) // true isNaN ( NaN ) // true isNaN ( 0 ) // false
全局方法isNaN是有坑的,它的NaN定義估計真的是not a number,而不是值得NaN這個s數值,也就是除了NaN,對於不能轉化為數字的情況都會返回true ...糟心!其實這個問題已經在ES6中被改善啦, Number.isNaN
分分鐘教isNaN
做人,Number.isNaN只會對NaN返回true,其他情況都是false...
5. (-0).toString() === '0'
?苦惱!
-0的toString竟然沒有保留-
,坑爹嘛不是...
但是, _.toString(-0) === '-0'
,如何做的?
// 一系列的前置检查和转化后... const INFINITY = 1 / 0 ; // 手动INFINITY const res = ` ${ value } ` ; if ( res === '0' && 1 / value === - INFINITY ) { return '-0' ; } else { return res ; }
6. JS的世界裡,誰不能轉化為數字?
數字、數字字符串肯定是可以的...
對象?或者非數字的字符串?它們也能得到NaN
Symbol !
Number ( 'xx' ) // NaN Number ( new Object ()) // NaN Number ( Symbol ()) // --> Uncaught TypeError: Cannot convert a Symbol value to a number
另外,Symbol 可以顯示的轉化為String 和Boolean, 但是,不能進行任何隱式轉換,也就是不能對Sybmol 進行運算,比如:
const symbol = Symbol (); 1 + symbol // TypeError '1' + symbol // TypeError // 可显示转化为布尔和字符串Boolean ( symbol ) // true String ( symbol ) // "Symbol()"
7. _.repeat
的優化手段
_.repeat([str=''],[times=1])
指定string 重複n 次輸出,可以很簡單的通過循環實現:
constrepeat=(str='',times=1)=>{letres=str;while(--times){res+=str;}returnres;}repeat('6',3);// 666
Easy ?好像不是特別完美吧,設想repeat('6', 4)
按照上述實現需要循環4次,但是其實是可以通過兩次操作就把最終結果拼接出來的,就相當於repeat(repeat('6', 2), 2)
,所以這塊是有優化空間的,來看優化後的算法:
constrepeat=(str='',times=1)=>{letresult='';if(!str||times<1){returnresult;}do{if(times%2){result+=str;}times=Math.floor(times/2);if(times){str+=str;}}while(times)returnresult;}
性能對比: repeat('hello world', 100000)
測試的benchmark:

遠遠超出...
結束語
在Lodash 中,函數的實現非常嚴謹、高效、兼容性強,以及具有一定的前瞻性,本文只拎出來一丁點兒細微的點,強力建議去擼源碼...
順道
阿里巴巴南京研發中心,迫切期待你的加入,詳見:坐標大南京,阿里巴巴Java、前端內推。