摘要:在這里直接給最終的源碼第四版如果該插入的位置的值正好等于元素的值,說明是第一個符合要求的值判斷是否是值得注意的是在的實現中,只有是支持有序數組使用二分查找,并不支持。
前言JavaScript專題系列第十篇,講解如何從數組中查找指定元素,并且跟著 undersocre 實現 findIndex 和 findLastIndex、sortedIndex、indexOf 和 lastIndexOf
在開發中,我們經常會遇到在數組中查找指定元素的需求,可能大家覺得這個需求過于簡單,然而如何優雅的去實現一個 findIndex 和 findLastIndex、indexOf 和 lastIndexOf 方法卻是很少人去思考的。本文就帶著大家一起參考著 underscore 去實現這些方法。
在實現前,先看看 ES6 的 findIndex 方法,讓大家了解 findIndex 的使用方法。
findIndexES6 對數組新增了 findIndex 方法,它會返回數組中滿足提供的函數的第一個元素的索引,否則返回 -1。
舉個例子:
function isBigEnough(element) { return element >= 15; } [12, 5, 8, 130, 44].findIndex(isBigEnough); // 3
findIndex 會找出第一個大于 15 的元素的下標,所以最后返回 3。
是不是很簡單,其實,我們自己去實現一個 findIndex 也很簡單。
實現findIndex思路自然很明了,遍歷一遍,返回符合要求的值的下標即可。
function findIndex(array, predicate, context) { for (var i = 0; i < array.length; i++) { if (predicate.call(context, array[i], i, array)) return i; } return -1; } console.log(findIndex([1, 2, 3, 4], function(item, i, array){ if (item == 3) return true; })) // 2findLastIndex
findIndex 是正序查找,但正如 indexOf 還有一個對應的 lastIndexOf 方法,我們也想寫一個倒序查找的 findLastIndex 函數。實現自然也很簡單,只要修改下循環即可。
function findLastIndex(array, predicate, context) { var length = array.length; for (var i = length; i >= 0; i--) { if (predicate.call(context, array[i], i, array)) return i; } return -1; } console.log(findLastIndex([1, 2, 3, 4], function(item, index, array){ if (item == 1) return true; })) // 0createIndexFinder
然而問題在于,findIndex 和 findLastIndex 其實有很多重復的部分,如何精簡冗余的內容呢?這便是我們要學習的地方,日后面試問到此類問題,也是加分的選項。
underscore 的思路就是利用傳參的不同,返回不同的函數。這個自然是簡單,但是如何根據參數的不同,在同一個循環中,實現正序和倒序遍歷呢?
讓我們直接模仿 underscore 的實現:
function createIndexFinder(dir) { return function(array, predicate, context) { var length = array.length; var index = dir > 0 ? 0 : length - 1; for (; index >= 0 && index < length; index += dir) { if (predicate.call(context, array[index], index, array)) return index; } return -1; } } var findIndex = createIndexFinder(1); var findLastIndex = createIndexFinder(-1);sortedIndex
findIndex 和 findLastIndex 的需求算是結束了,但是又來了一個新需求:在一個排好序的數組中找到 value 對應的位置,保證插入數組后,依然保持有序的狀態。
假設該函數命名為 sortedIndex,效果為:
sortedIndex([10, 20, 30], 25); // 2
也就是說如果,注意是如果,25 按照此下標插入數組后,數組變成 [10, 20, 25, 30],數組依然是有序的狀態。
那么這個又該如何實現呢?
既然是有序的數組,那我們就不需要遍歷,大可以使用二分查找法,確定值的位置。讓我們嘗試著去寫一版:
// 第一版 function sortedIndex(array, obj) { var low = 0, high = array.length; while (low < high) { var mid = Math.floor((low + high) / 2); if (array[mid] < obj) low = mid + 1; else high = mid; } return high; }; console.log(sortedIndex([10, 20, 30, 40, 50], 35)) // 3
現在的方法雖然能用,但通用性不夠,比如我們希望能處理這樣的情況:
// stooges 配角 比如 三個臭皮匠 The Three Stooges var stooges = [{name: "stooge1", age: 10}, {name: "stooge2", age: 30}]; var result = sortedIndex(stooges, {name: "stooge3", age: 20}, function(stooge){ return stooge.age }); console.log(result) // 1
所以我們還需要再加上一個參數 iteratee 函數對數組的每一個元素進行處理,一般這個時候,還會涉及到 this 指向的問題,所以我們再傳一個 context 來讓我們可以指定 this,那么這樣一個函數又該如何寫呢?
// 第二版 function cb(fn, context) { return function(obj) { return fn ? fn.call(context, obj) : obj; } } function sortedIndex(array, obj, iteratee, context) { iteratee = cb(iteratee, context) var low = 0, high = array.length; while (low < high) { var mid = Math.floor((low + high) / 2); if (iteratee(array[mid]) < iteratee(obj)) low = mid + 1; else high = mid; } return high; };indexOf
sortedIndex 也完成了,現在我們嘗試著去寫一個 indexOf 和 lastIndexOf 函數,學習 findIndex 和 FindLastIndex 的方式,我們寫一版:
// 第一版 function createIndexOfFinder(dir) { return function(array, item){ var length = array.length; var index = dir > 0 ? 0 : length - 1; for (; index >= 0 && index < length; index += dir) { if (array[index] === item) return index; } return -1; } } var indexOf = createIndexOfFinder(1); var lastIndexOf = createIndexOfFinder(-1); var result = indexOf([1, 2, 3, 4, 5], 2); console.log(result) // 1fromIndex
但是即使是數組的 indexOf 方法也可以多傳遞一個參數 fromIndex,從 MDN 中看到 fromIndex 的講究可有點多:
設定開始查找的位置。如果該索引值大于或等于數組長度,意味著不會在數組里查找,返回 -1。如果參數中提供的索引值是一個負值,則將其作為數組末尾的一個抵消,即 -1 表示從最后一個元素開始查找,-2 表示從倒數第二個元素開始查找 ,以此類推。 注意:如果參數中提供的索引值是一個負值,仍然從前向后查詢數組。如果抵消后的索引值仍小于 0,則整個數組都將會被查詢。其默認值為 0。
再看看 lastIndexOf 的 fromIndex:
從此位置開始逆向查找。默認為數組的長度減 1,即整個數組都被查找。如果該值大于或等于數組的長度,則整個數組會被查找。如果為負值,將其視為從數組末尾向前的偏移。即使該值為負,數組仍然會被從后向前查找。如果該值為負時,其絕對值大于數組長度,則方法返回 -1,即數組不會被查找。
按照這么多的規則,我們嘗試著去寫第二版:
// 第二版 function createIndexOfFinder(dir) { return function(array, item, idx){ var length = array.length; var i = 0; if (typeof idx == "number") { if (dir > 0) { i = idx >= 0 ? idx : Math.max(length + idx, 0); } else { length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; } } for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { if (array[idx] === item) return idx; } return -1; } } var indexOf = createIndexOfFinder(1); var lastIndexOf = createIndexOfFinder(-1);優化
到此為止,已經很接近原生的 indexOf 函數了,但是 underscore 在此基礎上還做了兩點優化。
第一個優化是支持查找 NaN。
因為 NaN 不全等于 NaN,所以原生的 indexOf 并不能找出 NaN 的下標。
[1, NaN].indexOf(NaN) // -1
那么我們該如何實現這個功能呢?
就是從數組中找到符合條件的值的下標嘛,不就是我們最一開始寫的 findIndex 嗎?
我們來寫一下:
// 第三版 function createIndexOfFinder(dir, predicate) { return function(array, item, idx){ if () { ... } // 判斷元素是否是 NaN if (item !== item) { // 在截取好的數組中查找第一個滿足isNaN函數的元素的下標 idx = predicate(array.slice(i, length), isNaN) return idx >= 0 ? idx + i: -1; } for () { ... } } } var indexOf = createIndexOfFinder(1, findIndex); var lastIndexOf = createIndexOfFinder(-1, findLastIndex);
第二個優化是支持對有序的數組進行更快的二分查找。
如果 indexOf 第三個參數不傳開始搜索的下標值,而是一個布爾值 true,就認為數組是一個排好序的數組,這時候,就會采用更快的二分法進行查找,這個時候,可以利用我們寫的 sortedIndex 函數。
在這里直接給最終的源碼:
// 第四版 function createIndexOfFinder(dir, predicate, sortedIndex) { return function(array, item, idx){ var length = array.length; var i = 0; if (typeof idx == "number") { if (dir > 0) { i = idx >= 0 ? idx : Math.max(length + idx, 0); } else { length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; } } else if (sortedIndex && idx && length) { idx = sortedIndex(array, item); // 如果該插入的位置的值正好等于元素的值,說明是第一個符合要求的值 return array[idx] === item ? idx : -1; } // 判斷是否是 NaN if (item !== item) { idx = predicate(array.slice(i, length), isNaN) return idx >= 0 ? idx + i: -1; } for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { if (array[idx] === item) return idx; } return -1; } } var indexOf = createIndexOfFinder(1, findIndex, sortedIndex); var lastIndexOf = createIndexOfFinder(-1, findLastIndex);
值得注意的是:在 underscore 的實現中,只有 indexOf 是支持有序數組使用二分查找,lastIndexOf 并不支持。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/84343.html
摘要:專題系列共計篇,主要研究日常開發中一些功能點的實現,比如防抖節流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點是研究專題之函數組合專題系列第十六篇,講解函數組合,并且使用柯里化和函數組合實現模式需求我們需要寫一個函數,輸入,返回。 JavaScript 專題之從零實現 jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實現一個 jQuery 的 ext...
摘要:寫在前面專題系列是我寫的第二個系列,第一個系列是深入系列。專題系列自月日發布第一篇文章,到月日發布最后一篇,感謝各位朋友的收藏點贊,鼓勵指正。 寫在前面 JavaScript 專題系列是我寫的第二個系列,第一個系列是 JavaScript 深入系列。 JavaScript 專題系列共計 20 篇,主要研究日常開發中一些功能點的實現,比如防抖、節流、去重、類型判斷、拷貝、最值、扁平、柯里...
摘要:前端日報精選使用重寫大型應用后,我們想分享一下這八個經驗與交互之技術有贊前端團隊專題之學在數組中查找指定元素基于角色的權限驗證從源碼學習如何寫模板中文譯掘金譯回顧的成功掘金一些有趣的事情布局教程入門指南定位元素古寺比的寺更 2017-07-27 前端日報 精選 使用 React Native 重寫大型 Ionic 應用后,我們想分享一下這八個經驗H5與Native交互之JSBridge...
摘要:專題系列第九篇,講解如何實現數組的扁平化,并解析的源碼扁平化數組的扁平化,就是將一個嵌套多層的數組嵌套可以是任何層數轉換為只有一層的數組。 JavaScript 專題系列第九篇,講解如何實現數組的扁平化,并解析 underscore 的 _.flatten 源碼 扁平化 數組的扁平化,就是將一個嵌套多層的數組 array (嵌套可以是任何層數)轉換為只有一層的數組。 舉個例子,假設有個...
摘要:專題系列第三篇,講解各種數組去重方法,并且跟著寫一個前言數組去重方法老生常談,既然是常談,我也來談談。它類似于數組,但是成員的值都是唯一的,沒有重復的值。 JavaScript 專題系列第三篇,講解各種數組去重方法,并且跟著 underscore 寫一個 unique API 前言 數組去重方法老生常談,既然是常談,我也來談談。 雙層循環 也許我們首先想到的是使用 indexOf 來循...
閱讀 2429·2021-09-01 10:41
閱讀 1450·2019-08-30 14:12
閱讀 517·2019-08-29 12:32
閱讀 2865·2019-08-29 12:25
閱讀 2939·2019-08-28 18:30
閱讀 1711·2019-08-26 11:47
閱讀 986·2019-08-26 10:35
閱讀 2595·2019-08-23 18:06