arrify - 一个很实用的工具函数

657 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

介绍

一个由 sindresorhus 开发的数组转换工具 arrify , 可以将任意值转换为数组

安装

npm install arrify

使用

import arrify from 'arrify';

// string
arrify('🦄');
//=> ['🦄']

// array
arrify(['🦄']);
//=> ['🦄']

// iterable objects
arrify(new Set(['🦄']));
//=> ['🦄']

// null
arrify(null);
//=> []

// null
arrify(undefined);
//=> []

源码

类型声明:

export default function arrify<ValueType>(
	value: ValueType
): ValueType extends (null | undefined)
	? [] // eslint-disable-line  @typescript-eslint/ban-types
	: ValueType extends string
		? [string]
		: ValueType extends readonly unknown[]
			? ValueType
			: ValueType extends Iterable<infer T>
				? T[]
				: [ValueType];

核心源码

export default function arrify(value) {
	if (value === null || value === undefined) {
		return [];
	}

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

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

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

	return [value];
}
  1. 如果传入参数是 nullundefined 返回 []
  2. 如果传入参数是 数组, 直接返回原来的参数
  3. 如果传入参数是 string类型, 则返回 [value]
  4. 如果传入参数是 可迭代对象 , 则使用 扩展运算符 转换为数组

一些问题

1. 能否自定义一个迭代器来实现以上效果呢?

试试看:

var myIterable = {}
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};

arrify(myIterable)
// output: [1, 2, 3]

完全可以的哈😁

2.能否调换这些逻辑判断的顺序?

试试看:

export default function arrify(value) {
	if (value === null || value === undefined) {
		return [];
	}

	if (Array.isArray(value)) {
		return value;
	}
  
  // 判断上移
	if (typeof value[Symbol.iterator] === 'function') {
		return [...value];
	}
  
  // 判断下移
	if (typeof value === 'string') {
		return [value];
	}

	return [value];
}

arrify('foo')
// output: [ 'f', 'o', 'o' ]
// expect: [ 'foo' ]

结果显示, 上述代码不能达到预期效果, 查阅到相关文档:

String、Array、TypedArray、Map 和 Set 都是内置可迭代对象,因为它们的原型对象都拥有一个 Symbol.iterator 方法。

如果上移则提前进入 Symbol.iterator 类型判断, 对其解构 , 偏离预期效果.

3. 可否将一个 类数组 转换为数组 ?

“类数组” 意味着 arguments 有 长度 属性 并且属性的索引是从零开始的, 但是它没有 Array的 内置方法, 例如 forEach() 和 map()都是没有的.

翻了一下仓库 GitHub Issues 还真有人遇到了这个问题: image.png 作者是这样回复的: image.png 大致意思是:

特意这样限制的,如果想要达到这种效果,自己动手实现吧.

Just do it ~

export default function arrify(value) {
	//...

	if (typeof value.length != 'number') return [value]

	const arr = []
	
  for (var i = 0; i < value.length; i++) {
    if (Object.prototype.hasOwnProperty.call(value, i) || i in value) {
      arr.push(value[i])
   	}
 	}

 	if (!arr.length) return []

	return arr

}

const pseudoArray = {
	0:'a',
	1:'b',
	length:2
}

arrify(pseudoArray)
// output -> [ 'a', 'b' ]

疑惑

在判断传入参数是否为 可迭代对象的时候, 可否将 [...value] 替换为 Array.from(value) ?

	if (typeof value[Symbol.iterator] === 'function') {
		return [...value];
    // replace ->  return Array.from(value) ?
	}

我尝试替换为 Array.from(value) , 运行 npm run test 跑一下测试用例, 报错了: image.png 通过搜索引擎找到相关资料 Prefer the spread operator over Array.from() #120 image.png 看了一遍, 还是不明白. 之后又去了解到 Array.from()[...xxx]相比, Array.from() 性能更佳 , 所以为什么用 扩展运算符呢?

如果你知道, 请在评论区分享你的看法和观点 , 非常感谢 !

看了一下 提 issue 的时间是 2015 年 3月 8 日 , 而 ES6 正式发布在 6 月 , 会不会...... ? 😂

参考资料

  1. github.com/sindresorhu…

  2. (sindresorhus / eslint-plugin-unicorn) - Prefer the spread operator over Array.from() #120

  3. (sindresorhus / arrify) - Doesn't work with array-like objects

  4. Convert Iterables to Array using Spread - Samantha Ming

  5. 迭代器 - MDN

  6. 展开语法 - MDN

  7. Array.from() - MDN