前言
人生總是在意外之中. 情況大概是這樣的. 前兩天版本上線以后, 無意中發現了一個bug
, 雖然不是很大, 為了不讓用戶使用時感覺到問題. 還是對著一個小小的bug
進行了修復, 并重新在上線一次, 雖然問題不大, 但帶來的時間成本還是存在的. 以及上線后用戶體驗并不是很好.
問題產生的原因就在于JavaScript
數組遍歷方法中對于空數組返回值的問題. 空數組遍歷的知識點, 在學習的過程中, 相信你肯定也接觸過, 學習過. 但在使用時往往會忽略這一點.
我們以every
為例
1. every 基本使用
對于every
遍歷方法, 這里就不過多闡述了. 主要就是遍歷數組中每個元素, 執行回調函數, 當所有的元素都返回true
時, 結果是true
, 只要有一次遍歷時, 回調函數返回false
結果就是false
示例:
let arr = [40,50,60,10,20,30]// 判斷數組中所有的元素是否都大于5
let bol = arr.every((item) => item > 5)
console.log('bol', bol)
// 輸出結果: bol true// 判斷數組中所有的元素是否都大于
let bol2 = arr.every((item) => item > 10)
console.log('bol2', bol2)
// 輸出結果: bol2 false
在這個示例中, 第一次調用every
時, 會遍歷所有的數組元素, 因為每一個元素都大于5, 所以回調函數會執行6次, 每次都返回true
, every
遍歷方法最終返回true
, 表示數組中每個元素都符合要求
第二次遍歷時, 只會遍歷4次, 因為在遍歷到10 時, 回調函數返回false
, 此時數組后面的元素就不需要再遍歷了. 該false
已經確定了最終的結果, false
表示數組中包含不符號要求的元素.
every
遍歷方法并不需要關心具體是第幾項元素不符合要求. 該方法的作用就是判斷數組中是否是每一項都符合要求
2. every 空數組的問題
我們先說一下最終的結果, 空數組使用every
時, 每次結果都返回true
示例:
let arr = []// 固定返回true
let bol = arr.every((item) => true)
console.log('bol', bol)
// 輸出結果: bol true// 回調函數固定返回false
let bol2 = arr.every((item) => {console.log('every')return false
})
console.log('bol2', bol2);
// bol2 true
示例中, 我們對于空數組使用every
遍歷方法, 無論回調函數返回的是true
,還是false
最終的結果都是true
我們在回調函數中添加console.log("every")
記錄回調函數是否執行. 從運行結果來看, 會調函數并沒有執行. 空數組中沒有元素, 并不會執行回調函數, 也就意味這回調函數中返回的是什么值都毫無意義.
every
遍歷方法最終的結果true
顯然是一個默認值. 可以理解為調用every
時, 默認返回值就是true
, 然后遍歷元素,執行回調函數, 只要有一次回調函數返回的false
, 則作為最終結果返回false
并結束遍歷.
3. 規范描述
根據ECMA-262 官方描述, every
方法的邏輯如下
這里對于最終返回值, 我將其框選出來. 通過官方描述, 最終的結果有兩種情況, 其一就是默認返回true
, 其二是根據回調函數執行的結果返回false
,
這里我們根據表述, 自定義一個函數模擬every
方法
示例
// 參數接受一個回調函數Array.prototype.myEvery = (callbackfn, thisArg) => {// 獲取this, 通過數組調用該方法, this 即為數組const O = this;// 獲取數組長度const len = O.length;// 確認參數callback 是一個函數. 否則報錯if(typeof callbackfn !== "function"){throw new TypeError(typeof callbackfn + "is not a function")}// 遍歷數組let k = 0; while(k < len){// 轉為字符串const Pk = String(key)// 判斷下標是否為數組本身的屬性const kPresent = O.hasOwnProperty(Pk);if(kPresent){// 獲取數組元素const kValue = O[PK]// 調用回調函數, 獲取回調函數的結果const testResult = Boolean( callbackfn.call(this.Arg, kValue, k, O))// 如果回調函數返回false, 則停止循環, 整體返回falseif(testResult === false){return false}}k++}// 數組元素循環完畢, 回調函數都沒有返回false, 則表示數組每一項否符合要求// 最終返回truereturn true}
從代碼中可以看出,every ()
默認返回為 true,并且只有在回調函數執行返回 false
時才返回 false
。如果數組中沒有元素,那么就沒有機會執行回調函數,因此方法就沒有辦法返回 false
,只會返回默認值true
。
4. 場景描述
在工作中發生問題場景是這樣的, 在使用vue
開發, 父組件給子組件傳參. 期望傳入的 參數在子組件的本身數據中都包含, 則不執行后續邏輯, 如果傳入的數據, 在子組件數據中有不存的項, 則更新子組件數據.
這里我將復雜業務邏輯簡化為JavaScript
方法的調用.
示例:
let cacheArray = [10,20,30];
function update (arr) {// 判斷傳入的數組每一項存在于cacheArray 中const result = arr.every((item) => cacheArray.includes(item))// 條件為true, 則表示傳入數組中的數據都存在, 則不執行后續邏輯,// false, 更新cacheArray 數據, 并執行后續邏輯if(!result){cacheArray = [...arr]console.log('cacheArray', cacheArray)}
}
這個示例代碼在表面上看沒有任何問題, 但如果你想清空cacheArray
數組. 你會發現調用update
方法做不到,
當你調用update
方法,并傳入一個[]
時, 空數組調用every
的結果始終是true
, 所以后面取反的結果始終為false
, 根本執行更新cacheArray
數組的代碼.
發現問題點, 解決方案就很簡單了, 修改一下判斷條件, 當參數為空數組時. length
必然是0
, 通過判斷是空數組, 根據邏輯運算符的||
的短路算法規則, 不需再去判斷!result
修改判斷條件
if(!arr.length || !result){cacheArray = [...arr]console.log('cacheArray', cacheArray)
}
5. 總結
這就是空數組給功能帶來的問題. 所以有的時候, 真的不是我們學習了所有知識, 我們就能做到萬無一失. 在工作中存在很多復雜的邏輯, 一個疏忽, 或沒有考慮細致都會帶來問題. 不是不會, 而是所有業務邏輯交織在一起. 難免帶來一些邏輯上的遺漏
對于some
方法也會有相同的問題, 對于空數組會始終返回false
, 這個留給你自己探討