JavaScript 实现 对象、数组、类数组通用的遍历方法 each

jQuery 的 each 方法

jQuery 有个 each 方法,是对于对象、数组通用的遍历方法。

具体的用法参考官方文档:.each()

今天我们也来实现这样一个 each 方法。

思路

参数

参考 jQuery ,each 方法接收两个参数:

  • 要遍历的对象;
  • 要在其上执行的回调函数。

逻辑:

  • 自然是要先判断其类型,是数组(类数组)就执行 for 循环(forEach 等不合适,后面再说),是对象就执行 for in 循环;
  • 循环中执行回调函数。

关于数组、类数组的判断:

  • 类数组是拥有一个 length 属性和 若干索引属性 的对象,常见的 arguments 对象就是类数组;
  • 类数组不是数组, Array.prototype 上的方法无法在类数组对象上调用,会报错;
  • 类数组的条件更“宽”,即符合数组特征的一定符合类数组特征,反之不一定,而它们的遍历方法一致。所以在数组、类数组通用的遍历方法 each 的判断逻辑中,可以只划一条“及格线”,只需要检测是否是类数组即可。

参考 JavaScript 权威指南中给出的方法:

function isArrayLike(o) {
    if (o && // o is not null, undefined, etc
        // o is an object
        typeof o === "object" &&
        // o.length is a finite number
        isFinite(o.length) &&
        // o.length is non-negative
        o.length >= 0 &&
        // o.length is an integer
        o.length === Math.floor(o.length) &&
        // o.length < 2^32
        o.length < 4294967296) //数组的上限值
        return true;
    else 
        return false;
}

代码

基本款

由此,得到以下代码:

// 基本款
function each(obj, cb) {
  var length, i = 0;
  if ( isArrayLike(obj) ) {
      length = obj.length;
      for ( ; i < length; i++ ) {
          // 此处如果用 forEach 遍历,注意参数的顺序
          cb(i, obj[i])
      }
  } else {
      for ( i in obj ) {
          cb(i, obj[i])
      }
  }
  return obj;
}

注: 此处直接用 forEach 遍历是可以的,但要注意 forEach 的回调函数的参数的顺序。

修复 this

遍历过程中,为了让回调函数中的 this 指向元素,我们使用 call() 方法:

// call() 方法第一个参数即为 this 指向的 context
callbackFunction.call(obj[i]i, obj[i])

代码:

// 修复 this
function each(obj, cb) {
  var length, i = 0;
  if ( isArrayLike(obj) ) {
      length = obj.length;
      for ( ; i < length; i++ ) {
          // 使用 call()
          cb.call(obj[i],i, obj[i])
      }
  } else {
      for ( i in obj ) {
          // 使用 call()
          cb.call(obj[i],i, obj[i])
      }
  }
  return obj;
}

终止遍历的功能

jQuery 的 each 方法有终止遍历的功能:

  • 当回调函数返回值为 false,则终止遍历。

思路:
这个实现起来很简单,由于我们使用了 for 循环,可以在检测到 false 时,通过 break 轻松实现终止遍历。

if (callbackFunction.call(obj[i], i, obj[i]) === false) {
    break;
}

用上面这段代码替换

callbackFunction.call(obj[i], i, obj[i])

最终代码:

// 最终代码
function each(obj, cb) {
  var length, i = 0;
  if ( isArrayLike(obj) ) {
      length = obj.length;
      for ( ; i < length; i++ ) {
          // 终止遍历的功能
          if (cb.call(obj[i], i, obj[i]) === false) {
               break;
          }
      }
  } else {
      for ( i in obj ) {
          // 终止遍历的功能
          if (cb.call(obj[i], i, obj[i]) === false) {
                break;
          }
      }
  }
  return obj;
}

测试

let obj = {
  ak:"av",
  bk:"bv"
}

let array = [1,2,3]

let arr = [{jb:'jb1'},{jw: 'jw1'}]

function logger (index, item) {
  console.log(`~ ${index}: ${item} ~`);
}

each(obj, logger)
each(array, logger)
each(arr, logger)

输出:

~ ak: av ~
~ bk: bv ~
~ 0: 1 ~
~ 1: 2 ~
~ 2: 3 ~
~ 0: [object Object] ~
~ 1: [object Object] ~

成功~~

参考:
https://github.com/mqyqingfeng/Blog/issues/40
https://blog.csdn.net/beijiyang999/article/details/79760562
https://blog.csdn.net/beijiyang999/article/details/80179097

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章