本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
学习目标
arrify 如何转数组
源码地址
源码解读
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];
}
null和undefined转换为空数组- 如果是数组则直接返回
- 这里字符串单独列出来并放在
Symbol.iterator属性之前,是因为string原生具备 Iterator接口(即Symbol.iterator方法),直接包裹[ ]输出即可,否则还被拆成单个字符组成的数组 typeof value[Symbol.iterator] === 'function'用于判断是否部署了遍历器属性,即是否可直接使用for...of...遍历- [...value]这里的扩展运算符(...)会调用默认的 Iterator 接口
- 其他类型则直接包裹[ ]输出即可
知识点总结
关于Symbol
symbol 是一种基本数据类型,Symbol() 函数会返回 symbol 类型的值
const symbol1 = Symbol();
console.log(typeof symbol1); // symbol
// 参数为可选的字符串类型 用于描述 不会将字符串转换为Symbol
console.log(Symbol('foo') === Symbol('foo')); // false
// 虽然是内置函数 但是不可以用new运算符
var sym = new Symbol(); // TypeError
Symbol.iterator
1. 为何出现?
为所有数据结构(数组、Map、Set、对象),提供了一种统一的访问机制(for...of循环)。
当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口
一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)
2. Symbol.iterator是啥?
Symbol.iterator属性本身是一个函数,执行这个函数,就会返回一个遍历器。Symbol.iterator是预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内。
3. 如何部署?
原生具备 Iterator 接口数据结构如下:
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的属性没有顺序之分,是非线性的结构。 那么,如何给对象部署Iterator 接口呢?
部署关键点如下:
- 定义对象的
Symbol.iterator属性,值为一个函数,执行函数返回一个遍历器对象 - 这个返回的遍历器对象必须包含
next方法,return()和throw()可选。for...of循环提前退出(break语句或出错)会调用return() - 这个next方法返回一个包含
value和done属性的对象
实践案例:
- 给一个普通对象部署Iterator 接口
// 普通版
const obj = {
data: ['hello','friends'] ,
[Symbol.iterator]: function() {
const self = this
const index = 0
return {
next() {
if(index < self.data.length) {
return {
// 索引需要自增 这里的value依然是self.data.index 然后改变index值
value: self.data[index++], done: false
}
}
return {value: undefined, done: true}
}
}
}
}
Iterator 接口接口最简单的实现是用Generator 函数
// 简单版
const obj = {
data: ['hello','friends'] ,
[Symbol.iterator]: function* () {
yield 'hello';
yield 'friends';
}
// 简写版
// *[Symbol.iterator]() {
// yield 'hello';
// yield 'friends';
// }
}
- 给类数组(存在数值键名和
length属性)对象部署Iterator 接口
// 普通对象使用此方式会报错
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
4. 何时调用?
- 解构赋值
- 扩展运算符
- yield*
let generator = function* () { yield 1; yield* [2,3,4]; yield 5; }; var iterator = generator(); iterator.next() // { value: 1, done: false } iterator.next() // { value: 2, done: false } iterator.next() // { value: 3, done: false } iterator.next() // { value: 4, done: false } iterator.next() // { value: 5, done: false } iterator.next() // { value: undefined, done: true } - 数组作为参数的场合
- for...of
- Array.from()
- Map(), Set(), WeakMap(), WeakSet()(比如
new Map([['a',1],['b',2]])) - Promise.all()
- Promise.race()
for...of
for...of循环内部调用的是数据结构的Symbol.iterator方法
特点:
- 遍历数组的键值
- 不同于
forEach方法,它可以与break、continue和return配合使用 - 提供了遍历所有数据结构的统一操作接口
- 用于字符串时,会正确识别 32 位 UTF-16 字符
for...in循环:
- 遍历数组的索引
for...in循环会包括原型链上的键- 某些情况下,
for...in循环会以任意顺序遍历键名
总结: for...in循环主要是为遍历对象而设计的,不适用于遍历数组。数组等内置迭代器对象的使用for...of循环。
总结
学习不光靠输入,最重要的还是总结和输出,这样不光可以加深印象,还能更加系统地学习知识。