梳理 JavaScript 中空数组调用 every方法返回true 带来惊讶的问题

前言

人生总是在意外之中. 情况大概是这样的. 前两天版本上线以后, 无意中发现了一个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, 则停止循环, 整体返回false
          if(testResult === false){
            return false
          }
        }

        k++
      }

      // 数组元素循环完毕, 回调函数都没有返回false, 则表示数组每一项否符合要求
      // 最终返回true
      return 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, 这个留给你自己探讨

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容