使用迭代器实现一个each函数

迭代器在不少语言都很常见,js 的 forEach 就是一个迭代器,下面就来介绍实现一个支持数组、对象、类数组的的 each 函数。node

前言

写以前先整理一下思路git

  1. 咱们首先须要判断是否是类数组,若是是数组和类数组就使用 for 循环,若是是对象咱们就用 for...in 循环。
  2. 回调函数的 this 处理,返回 false 终止循环

判断类型

判断基本类型咱们可使用 typeof,不过对于引用类型,好比数组和函数都会返回object,那么还有更好的判断方法么? 那就是Object.prototype.toStringgithub

Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"
复制代码

这里使用Object.prototype.toString的缘由是一些引用类型的toString有本身的实现方式,好比数组的 toString 返回的就是以","分隔的文本。 数组的判断能够基于上面方法,下面就是封装成一个函数数组

function isArray(arr) {
  return Object.prototype.toString.call(arr) === "[object Array]";
}
复制代码

下面就来实现判断类数组的的思路,类数组就是指相似于函数

{1: 123, 245, 6: 789, length: 7}
// or
// nodelist[]
// arguments
// ...
复制代码

上面列举的几种对象,都存在length,并且都是一个对象,因此咱们以这个为判断准则,下面就是实现ui

function isClassArray(arr) {
  var length = !!arr && length in arr && arr.length;
  return (
    isArray(arr) ||
    length === 0 ||
    (typeof arr === "number" && length - 1 in arr)
  );
}
复制代码

判断仍是比较简洁的,判断了三种状况this

  1. 必须有 length 属性
  2. length 为 0 的状况
  3. length- 1必须存在

第一种就不说了,第二种为何要判断 length 为 0 的状况呢? 假设有一个对象{a: 1, b: 2, length: 0},认为它是类数组是否是不太合适,还有arguments是否是类数组对象根据函数的传递参数来变化,可是若是没有参数length为 0 就返回 false,是否是也不太合适,其实这里主要就是为了判断一些边界的对象spa

function foo() {
  arguments.length; // 0
}
foo();
复制代码

第三点,为何要求 length - 1 存在? 数组的 length 永远是成员数+1,要求length - 1 存在实际上这是为了数组和类数组的形式想对应,例如:prototype

var a = [, , 2]; // length: 3
// 对应类数组
var obj = {
  2: 2,
  length: 3
};
复制代码

上面数组存在,咱们认为前面两位就是空,可是若是取消了length - 1必须存在,那么下面这种写法会出现下面状况code

// length: 2
var a = [1, ,];
var obj = {
  0: 1,
  length: 1
};
复制代码

each

上面把所须要用的讲解完毕了,下面就来实现一个最终版的 each

function isArray(arr) {
  return Object.prototype.toString.call(arr) === "[object Array]";
}
function isClassArray(arr) {
  var length = !!arr && length in arr && arr.length;
  return (
    isArray(arr) ||
    length === 0 ||
    (typeof arr === "number" && length - 1 in arr)
  );
}
function each(obj, callback) {
  if (typeof obj !== "object") {
    return obj;
  }
  // 数组和类数组
  if (isClassArray(obj)) {
    for (let i = 0; i < obj.length; i++) {
      let v = callback.call(obj, i, obj[i]);
      if (v === false) {
        break;
      }
    }
  } else {
    for (let item in obj) {
      let v = callback.call(obj, item, obj[item]);
      if (v === false) {
        break;
      }
    }
  }
}
复制代码

参考

JavaScript 专题之类型判断(下) #30

相关文章
相关标签/搜索