【若川源码共读】把一个值转换成数组--arrify

131 阅读4分钟

arrify源码学习

源码

arrify作为一个可以将任意类型转为数组类型的库,初听觉得很厉害,想必这个库很复杂吧,会兼容各种场景,然而当我看到源码的时候,确实有点震惊,这个代码库?就这么几行? 先贴一波源码

// 导出默认函数 arrify,拥有形参 value
export default function arrify(value) {
  // 如果没有传人value,或者传入的value为null或者undefined,则直接返回空数组
  if (value === null || value === undefined) {
    return [];
  }
  // 如果当前传入的数值为数组类型,则返回当前传入的数组
  if (Array.isArray(value)) {
    return value;
  }
  // 如果当前传入的数值为字符串类型,则返回当前字符串包裹的一维数组
  // 因为string拥有迭代器属性,返回结果是所有的字符串,因此要在迭代器之前判断字符串类型
  if (typeof value === "string") {
    return [value];
  }
  // 检查当前传入value是否拥有迭代器属性,如若拥有迭代器属性,则使其for...of出其值
  if (typeof value[Symbol.iterator] === "function") {
    return [...value];
  }
​
  return [value];
}

然而阅读arrify源码发现上面判断 null和undefined,string,arry都是基本操作,唯一让人困惑的是这个Symbol.iterator

这个Symbol.iterator到底是个什么玩意呢?

Symbol.iterator

Symbol.iterator是作为一个迭代器属性存在,具体可以查看MDN对于Symbol.iterator说明

以下是我个人理解: Symbol.iterator作为一个迭代器属性存在于数组的属性中,也正因为有这个属性,数组也才能被for...of,看到有位大佬在讨论for...offor...in的关系,其实这个问题也作为我们公司的面试题被我问过好多次,for...of是否可以遍历对象?大多数面试者回答都是不能,但是如果我非要让他能被for...of遍历呢?这个时候如果你了解Symbol.iterator,这个问题就很好回答了,如果不了解,那就一起来看看代码吧

注:解构作为for...of语法糖,此处就使用解构代替for..of

当我们尝试修改一个数组的迭代器属性时:

// 使用迭代去属性修改数组遍历值
let arr = [1, 2, 3, 4, 5, 6];
arr[Symbol.iterator] = function () {
  let index = -1;
  return {
    next: function () {
      console.log(index, arr.length);
      index++;
      return {
        done: index == arr.length,
        value: arr[index],
      };
    },
  };
};
console.log([...arr]);
/**
-1 6
0 6
1 6
2 6
3 6
4 6
5 6
[ 1, 2, 3, 4, 5, 6 ]
 */

下面为控制面板输出,Symbol.iterator要求必须返回一个符合迭代器协议的参数

例如:

{
  value:xx,
  done:true/false
}

只有返回了这样的协议,才证明当前迭代器是为正确的返回结果,否则就会报错,其中value为非必要返回,done为必要返回,只能返回true或者false,当返回true,证明当前迭代器已经进行完毕不需要下次迭代,当返回false时,需要再次迭代

注:此处返回的迭代器属性,与Generator返回的迭代器属性相同,因此可以用*yield*语法糖来代替

Generator在mdn上面的说明

例如:

function* () {
  yield* [1,2,3];
};
​
// 或者function* () {
  yield 1;
  yield 2;
  yield 3;
};

可能你看了上面的代码会说,arr本来就不应该返回这样的属性吗?对,没错,数组本身就存在迭代器属性,那么换成对象呢?

下面操作一下对象的迭代器

// 使用迭代器属性修改对象遍历值
let info = {
  name: 123,
  test: 456,
  dadd: "ceshi",
};
​
// info[Symbol.iterator] = function* () {
//  yield* Object.values(info);
// };
​
 info[Symbol.iterator] = function () {
  let index = 0;
  return {
    next: () => {
      console.log(this);
      let keys = Object.keys(this);
      console.log(keys.length);
      return {
        value: this[keys[index++]],
        done: index > keys.length,
      };
    },
  };
 };
console.log([...info]);
// 最终返回结果 [ 123, 456, 'ceshi' ]

这样就实现了对象的遍历,怎样,是不是很屌

你以为在这里就结束了?不

下面试试把Symbol.iterator加入对象的原型链,这样所有的对象也就拥有了迭代器属性

// 给所有的对象属性增加迭代器
Object.prototype[Symbol.iterator] = function () {
  let index = 0;
  console.log("进入了迭代器属性");
  return {
    // 此处使用箭头函数,确保this能指向当前对象
    next: () => {
      let keys = Object.keys(this);
      console.log(keys);
      return {
        value: this[keys[index++]],
        done: index > keys.length,
      };
    },
  };
};

测试一些对象属性返回结果

// 测试所有迭代器属性的对象// 普通对象属性
let a = {
  b: 111,
  c: 222,
  d: function () {},
  f: new Date(),
};
console.log([...a]); // [ 111, 222, [Function: d], 2022-11-11T06:42:17.428Z ]// date属性
let date = new Date();
​
console.log([...date]); // []
​
​
// 数组
let arr2 = [4,5,6,[8,9]]
console.log([...arr2]); // [ 4, 5, 6, [ 8, 9 ] ]
​
​
// 函数
let fn = function() {}
console.log([...fn]);  //[]

但是这样也失去了迭代器属性的作用,此处仅为测试迭代器

总结

作者很巧妙的使用了迭代器属性实现了将可迭代对象转为数组,大道至简是吧?哈哈哈

学习了 Symbol.iterator,Generator,for...of,for...in