arrify 万物皆可转换为数组

2022/05/26
4分钟阅读

介绍

一个由 sindresorhus (opens in a new tab) 开发的数组转换工具 arrify (opens in a new tab) , 可以将任意值转换为数组

安装

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. 如果传入参数是 可迭代对象 (opens in a new tab) , 则使用 扩展运算符 (opens in a new tab) 转换为数组

一些问题

  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类型判断,对其解构 , 偏离预期效果。.

  1. 可否将一个 类数组 (opens in a new tab) 转换为数组 ?

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

翻了一下仓库 GitHub Issues (opens in a new tab)还真有人遇到了这个问题::

GitHub issue

作者是这样回复的:

作者回复

大致意思是:

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

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跑一下测试用例,结果显示未通过::

测试用例未通过

通过搜索引擎找到相关资料 Prefer the spread operator over Array.from() #120 (opens in a new tab)

Prefer the spread operator over Array.from()

看了一遍,还是不明白。之后又去了解到 Array.from()[...xxx]相比, Array.from()性能更佳,所以为什么用 扩展运算符呢?

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

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

参考资料

  1. (sindresorhus / eslint-plugin-unicorn) - Prefer the spread operator over Array.from() #120 (opens in a new tab)
  2. (sindresorhus / arrify) - Doesn't work with array-like objects (opens in a new tab)
  3. Convert Iterables to Array using Spread - Samantha Ming (opens in a new tab)
  4. 迭代器 - MDN (opens in a new tab)
  5. 展开语法 - MDN (opens in a new tab)
  6. Array.from() - MDN (opens in a new tab)

阅读更多

返回
+1