携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情
前言
lodash里的to系列中,将数据转换为目标类型数组的方法主要是toArray方法。
toArray
toArray方法主要是将参数value转换为一个数组。
使用如下:
_.toArray({ 'a': 1, 'b': 2 });
// => [1, 2]
_.toArray('abc');
// => ['a', 'b', 'c']
_.toArray(1);
// => []
_.toArray(null);
// => []
_.toArray()
// => []
_.toArray({})
// => []
实现上,toArray方法需要借助内部封装的各个方法实现,具体处理逻辑如下:
- 对于不存在的参数直接返回空数组。
- 对于类数组,会先判断是否属于字符串,是的话调用stringToArray方法,不是的话调用copyArray方法。
- 如果是身上存在迭代器的对象,则调用iteratorToArray方法。
- 如果是Map对象则调用mapToArray,如果是Set对象则调用setToArray,否则调用values方法。
源码如下:
import copyArray from './.internal/copyArray.js'
import getTag from './.internal/getTag.js'
import isArrayLike from './isArrayLike.js'
import isString from './isString.js'
import iteratorToArray from './.internal/iteratorToArray.js'
import mapToArray from './.internal/mapToArray.js'
import setToArray from './.internal/setToArray.js'
import stringToArray from './.internal/stringToArray.js'
import values from './values.js'
const mapTag = '[object Map]'
const setTag = '[object Set]'
const symIterator = Symbol.iterator
function toArray(value) {
if (!value) {
return []
}
if (isArrayLike(value)) {
return isString(value) ? stringToArray(value) : copyArray(value)
}
if (symIterator && value[symIterator]) {
return iteratorToArray(value[symIterator]())
}
const tag = getTag(value)
const func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values)
return func(value)
}
copyArray
copyArray方法是内部方法,通过该方法可以生成一个全新的数组,内部通过while实现数组项的拷贝,所以并不是深度拷贝。
function copyArray(source, array) {
let index = -1
const length = source.length
array || (array = new Array(length))
while (++index < length) {
array[index] = source[index]
}
return array
}
iteratorToArray
iteratorToArray是一个内部方法,该方法会对迭代器参数进行遍历。
function iteratorToArray(iterator) {
let data
const result = []
while (!(data = iterator.next()).done) {
result.push(data.value)
}
return result
}
mapToArray
mapToArray方法是一个内部方法,该方法通过调用forEach循环遍历Map对象,输出一个二维数组。
function mapToArray(map) {
let index = -1
const result = new Array(map.size)
map.forEach((value, key) => {
result[++index] = [key, value]
})
return result
}
setToArray
setToArray方法是一个内部方法,该方法通过调用forEach循环遍历Set对象,输出一个二维数组。
function setToArray(set) {
let index = -1
const result = new Array(set.size)
set.forEach((value) => {
result[++index] = value
})
return result
}
stringToArray
stringToArray是一个内部方法,在实现上通过内部封装的asciiToArray、hasUnicode、unicodeToArray三个方法处理不同编码类型的数据,将字符串参数转换为数组类型。
stringToArray源码如下:
import hasUnicode from './hasUnicode.js'
import asciiToArray from './asciiToArray.js'
import unicodeToArray from './unicodeToArray.js'
function stringToArray(string) {
return hasUnicode(string)
? unicodeToArray(string)
: asciiToArray(string)
}
接下来我们先看各个依赖方法的实现。
hasUnicode
hasUnicode方法是一个内部方法,主要判断字符串是否含有指定的编码类型,实现上内部枚举了各个编码范围,通过正则匹配返回匹配结果。
const rsAstralRange = '\ud800-\udfff'
const rsComboMarksRange = '\u0300-\u036f'
const reComboHalfMarksRange = '\ufe20-\ufe2f'
const rsComboSymbolsRange = '\u20d0-\u20ff'
const rsComboMarksExtendedRange = '\u1ab0-\u1aff'
const rsComboMarksSupplementRange = '\u1dc0-\u1dff'
const rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange + rsComboMarksExtendedRange + rsComboMarksSupplementRange
const rsVarRange = '\ufe0e\ufe0f'
const rsZWJ = '\u200d'
const reHasUnicode = RegExp(`[${rsZWJ + rsAstralRange + rsComboRange + rsVarRange}]`)
function hasUnicode(string) {
return reHasUnicode.test(string)
}
asciiToArray
asciiToArray方法是一个内部方法,实现上调用原型链上的split方法切割字符串为数组。
function asciiToArray(string) {
return string.split('')
}
unicodeToArray
unicodeToArray方法主要是将unicode编码的字符串转换为数组,内部定义了一系列编码匹配规则,通过正则匹配相应的编码,返回匹配结果。
const rsAstralRange = '\ud800-\udfff'
const rsComboMarksRange = '\u0300-\u036f'
const reComboHalfMarksRange = '\ufe20-\ufe2f'
const rsComboSymbolsRange = '\u20d0-\u20ff'
const rsComboMarksExtendedRange = '\u1ab0-\u1aff'
const rsComboMarksSupplementRange = '\u1dc0-\u1dff'
const rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange + rsComboMarksExtendedRange + rsComboMarksSupplementRange
const rsVarRange = '\ufe0e\ufe0f'
const rsAstral = `[${rsAstralRange}]`
const rsCombo = `[${rsComboRange}]`
const rsFitz = '\ud83c[\udffb-\udfff]'
const rsModifier = `(?:${rsCombo}|${rsFitz})`
const rsNonAstral = `[^${rsAstralRange}]`
const rsRegional = '(?:\ud83c[\udde6-\uddff]){2}'
const rsSurrPair = '[\ud800-\udbff][\udc00-\udfff]'
const rsZWJ = '\u200d'
const reOptMod = `${rsModifier}?`
const rsOptVar = `[${rsVarRange}]?`
const rsOptJoin = `(?:${rsZWJ}(?:${[rsNonAstral, rsRegional, rsSurrPair].join('|')})${rsOptVar + reOptMod})*`
const rsSeq = rsOptVar + reOptMod + rsOptJoin
const rsNonAstralCombo = `${rsNonAstral}${rsCombo}?`
const rsSymbol = `(?:${[rsNonAstralCombo, rsCombo, rsRegional, rsSurrPair, rsAstral].join('|')})`
const reUnicode = RegExp(`${rsFitz}(?=${rsFitz})|${rsSymbol + rsSeq}`, 'g')
function unicodeToArray(string) {
return string.match(reUnicode) || []
}
values
values方法是对外导出的方法,接受对象类型的参数,该方法返回包含对象值的一个数组。
import baseValues from './.internal/baseValues.js'
import keys from './keys.js'
function values(object) {
return object == null ? [] : baseValues(object, keys(object))
}
baseValues
baseValues方法是values方法的核心,返回一个数组,数组项是对象参数的值。
function baseValues(object, props) {
return props == null ? [] : props.map((key) => object[key])
}
keys
keys方法返回包含对象参数一系列键值的数组,该方法是lodash对外导出的方法。
实现上借助arrayLikeKeys方法和isArrayLike方法判断类数组。如果是类数组则调用arrayLikeKeys方法,普通数组则直接调用原生的Object.keys方法。
import arrayLikeKeys from './.internal/arrayLikeKeys.js'
import isArrayLike from './isArrayLike.js'
function keys(object) {
return isArrayLike(object)
? arrayLikeKeys(object)
: Object.keys(Object(object))
}
arrayLikeKeys
arrayLikeKeys方法借助一系列is方法判断,通过循环遍历类数组输出每一项的键值。
import isArguments from '../isArguments.js'
import isBuffer from '../isBuffer.js'
import isIndex from './isIndex.js'
import isTypedArray from '../isTypedArray.js'
const hasOwnProperty = Object.prototype.hasOwnProperty
function arrayLikeKeys(value, inherited) {
const isArr = Array.isArray(value)
const isArg = !isArr && isArguments(value)
const isBuff = !isArr && !isArg && isBuffer(value)
const isType = !isArr && !isArg && !isBuff && isTypedArray(value)
const skipIndexes = isArr || isArg || isBuff || isType
const length = value.length
const result = new Array(skipIndexes ? length : 0)
let index = skipIndexes ? -1 : length
while (++index < length) {
result[index] = `${index}`
}
for (const key in value) {
if ((inherited || hasOwnProperty.call(value, key)) &&
!(skipIndexes && (
(key === 'length' ||
isIndex(key, length))
))) {
result.push(key)
}
}
return result
}
小结
本篇章我们通过toArray的实现认识了lodash里封装的一系列内部方法,以及前面篇章介绍到的is系列判断方法,同时也了解到了对外导出的keys方法的实现。