【若川视野 x 源码共读】第33期 | arrify 转数组

218 阅读3分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

学习目标

arrify 如何转数组

源码地址

github.com/sindresorhu…

线上阅读

源码解读

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];
}

  • nullundefined转换为空数组
  • 如果是数组则直接返回
  • 这里字符串单独列出来并放在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 接口呢?

部署关键点如下:
  1. 定义对象的Symbol.iterator属性,值为一个函数,执行函数返回一个遍历器对象
  2. 这个返回的遍历器对象必须包含next方法return()throw()可选。for...of循环提前退出(break语句或出错)会调用return()
  3. 这个next方法返回一个包含 valuedone属性的对象
实践案例:
  1. 给一个普通对象部署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';
    // }
}

  1. 给类数组(存在数值键名和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方法,它可以与breakcontinuereturn配合使用
  • 提供了遍历所有数据结构的统一操作接口
  • 用于字符串时,会正确识别 32 位 UTF-16 字符

for...in循环:

  • 遍历数组的索引
  • for...in循环会包括原型链上的键
  • 某些情况下,for...in循环会以任意顺序遍历键名

总结: for...in循环主要是为遍历对象而设计的,不适用于遍历数组。数组等内置迭代器对象的使用for...of循环。

总结

学习不光靠输入,最重要的还是总结和输出,这样不光可以加深印象,还能更加系统地学习知识。