likes
comments
collection
share

arrify 源码阅读 | 19行核心代码分析以及代码之外的收获

作者站长头像
站长
· 阅读数 70

前言

新年伊始,万象更新,宜登高望远,宜阅读源码。

新的一年,第一个 Flag 就是源码阅读。

之所以执着于源码,一方面是因为去年借着日更,读完了好几本大部头的JavaScript相关书籍,以及对 MDN 中的许多知识点做了梳理和过滤。另一方面,源码共读第一期活动中,开始关注若川大佬,若川大佬提供了大量的源码阅读的方向。

正所谓,背有大树好乘凉。计划集中1~2月的精力,进行一波源码阅读。

本篇是对 arrify 源码的解读,以及在解读过程中,关于实现代码的新想法和 tsd 类型校验工具库的使用。

核心代码

arrify的核心代码很短,共19行。这19行,共包括5类转换方式。

5类转换方式

值不存在时,转换成空数组

if (value === null || value === undefined) {
  return [];
}

值为 Array 类型时,值不需要额外的转换

if (Array.isArray(value)) {
  return value;
}

值为字符串类型时,返回转成数组类型的最终值

if (typeof value === 'string') {
  return [value];
}

值为拥有迭代器的类型时,使用扩展运算符取出可遍历的值并放到数组中

if (typeof value[Symbol.iterator] === 'function') {
		return [...value];
	}

不属于以上四种中的任何一种情况,返回转成数组类型的最终值

return [value];

为什么要加 Symbol.iterator 相关的校验?

一些内置类型拥有默认的迭代器行为,而 Symbol.iterator 则可以为它们定义默认的迭代器。

使用 typeof 进行 Symbol.iterator 方法的类型校验是否是 function,可以区分这类的类型值。

都有哪些内置类型

MDN 中一个罗列了五种:

  • [Array.prototype@@iterator]
  • [TypedArray.prototype[@@iterator()]]
  • [String.prototype[@@iterator()]]
  • [Map.prototype[@@iterator()]]
  • [Set.prototype[@@iterator()]]

下面用 Set 类型举个小例子

const set1 = new Set();
let res = typeof set1[Symbol.iterator] === 'function';
console.log(res); 

运行结果

// => true

讨论:转换方式 ❹ 是否可以代替 ❷

既然拥有迭代器行为的内置类型包括 Array类型,那么转换方式 不就包含了方式

const list1 = [1, 2, 3];
let res = typeof list1[Symbol.iterator] === 'function';
console.log(res);

运行结果

// => true

结论

我将修改之后的代码,用测试用例跑了一下,结果和修改前的一致。所以我个人感觉是可以只保留方式 ❹ 的(需要提前到方式 ❷ 的位置)。

掘友们怎么看?

测试用例

来跑一遍所有的测试用例,看看结果是否和想的一致。

console.log(arrify(null));
console.log(arrify(undefined));
console.log(arrify('1'));
console.log(arrify(['1']));
console.log(arrify(true));
console.log(arrify(1));
console.log(arrify({}));
console.log(arrify([1, 'foo']));
console.log(arrify(new Set(['1', true])));
console.log(arrify(new Set([1, 2])));
console.log(arrify(['1']));
console.log(arrify(['1']).push(''));
console.log(arrify(false ? [1, 2] : null));
console.log(arrify(false ? [1, 2] : undefined));
console.log(arrify(false ? [1, 2] : '1'));
console.log(arrify(false ? [1, 2] : ['1']));
console.log(arrify(false ? [1, 2] : true));
console.log(arrify(false ? [1, 2] : 3));
console.log(arrify(false ? [1, 2] : {}));
console.log(arrify(false ? [1, 2] : [1, 'foo']));
console.log(arrify(false ? [1, 2] : new Set(['1', true])));
console.log(arrify(false ? [1, 2] : false ? true : '1'));

运行结果

[]
[]
[ '1' ]
[ '1' ]
[ true ]
[ 1 ]
[ {} ]
[ 1, 'foo' ]
[ '1', true ]
[ 1, 2 ]
[ '1' ]
2
[]
[]
[ '1' ]
[ '1' ]
[ true ]
[ 3 ]
[ {} ]
[ 1, 'foo' ]
[ '1', true ]
[ '1' ]

tsd | 类型检查工具库

使用 tsd 提供的 expectType 方法,可以帮助检查预期类型与表达式或变量的类型是否一致,语法也比较简单:

expectType<预期类型>(表达式或变量)

所以可以看到源码中的如下代码:

expectType<[]>(arrify(null));

前面的转换方式中 arrify(null) 的结果是 [],所以上面的用例是通过的。

代码中还有一个 expectAssignable 方法,可以帮助检查表达式或变量的类型是否能够赋值给预期类型

expectAssignable<number[] | [boolean]>(arrify(false ? [1, 2] : true));
expectAssignable<number[]>(arrify(false ? [1, 2] : true));

上面两行代码运行结果是,第一行通过 ,第二行不通过。因为布尔值结果不可以分配给数值类型。

总结

最近源码读的多了,一个学习设计模式之外的收获就是自己尝试的独立思考,比如上面讨论里的另一种实现方式。

「书读百遍其义自见」,发散思维,校验技术的熟练性,也可以帮助突破一些瓶颈。

在这之前,我一直纠结的总是走别人已经走过的路,没有自己的创意。也在这一步一个印记中,消散了许多。

如果对源码感兴趣,但是没什么学习目标和方向的,此处手动@若川,可以跟川哥一起学习源码。

转载自:https://juejin.cn/post/7187323527614169148
评论
请登录