arrify源码学习
- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与
- 这是源码共读的第33期, 点击了解详情
源码
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...of和for...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*语法糖来代替
例如:
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