从arrify.js源码中认识可迭代对象和协议

150 阅读2分钟

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

这是源码共读的第33期 | arrify 转数组

源码仓库

Github仓库地址:arrify

阅读准备

关于迭代器

关于迭代对象MDN说明

迭代器本身是一个对象,且需要符合迭代器协议,并提供一个next方法

next方法返回一个具有两个属性的对象:

  1. done 如果迭代器可以产生序列中下一个值 则为false,如果不能则为true
  2. value 迭代器返回的任意Javascript值,done为true则为undefined
const names = ["a", "b", "c"];

let index = 0;
const nameIterator = {
	next: function () {
		if (index < names.length) {
			return { done: false, value: names[index++] };
		} else {
			return { done: true, value: undefined };
		}
	},
};

console.log(nameIterator.next()); //{ done: false, value: 'a' }
console.log(nameIterator.next()); //{ done: false, value: 'b' }
console.log(nameIterator.next()); //{ done: false, value: 'c' }
console.log(nameIterator.next()); //{ done: false, value: undefined }

生成迭代器的函数

function createArray(arrIterator) {
  let index = 0;
  return {
    next: function () {
      if (index < arrIterator.length) {
        return { done: false, value: arrIterator[index++] };
      } else {
        return { done: true, value: undefined };
      }
    },
  };
}

const names = ["a", "b", "c"];

const namesIterator = createArray(names);

console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());

关于迭代对象

当一个对象实现了iterable protocol协议,才是一个可迭代对象,这个对象要求拥有 @@iterator 方法,在代码中它的实现是 [Symbol.iterator] 这个属性上,值为一个迭代器方法

const iterableObj = {
	names:['abc','sad'],
	[Symbol.iterator]:function(){
		let index = 0
		return {
			next:()=>{
				if(index < this.names.length){
					return {done:false,value:this.names[index++]}
				}else{
					return {done:true,value:undefined}
				}
			}
		}
	}
  }

 //通过迭代器生成一个可迭代对象
  const iterable1 = iterableObj[Symbol.iterator]()
  console.log(iterable1.next()); //{done:false,value:'abc'}

补充:原生迭代对象

大部分Javascript原生对象已经实现了迭代协议,所以他们是具有可迭代的特性,这些对象在被创建的时候,在原型上就实现了这个迭代器属性

image.png

常见的已经实现了迭代器协议的对象有这些: Array 、 String 、 Map 、Set 、 Arguments 、NodeList

另外需要提起的还有for...of关键字和 扩展运算符,它的本质就是迭代器的语法糖,只有具有可迭代的特性 才能使用for...of遍历或使用 扩展运算符

一些应用场景

const arrA = [1, 2, 3];
const arrB = [4, 5, 6];
const arrC = [...arrA, ...arrB]; //[1,2,3,4,5,6]
console.log(arrC);
const [one, two, three] = arrC;
console.log(one, two, three); // 1 2 3

Array.from(具有可迭代特性的对象)  //将一个可迭代对象转换为数组
  • 需要留意的是 Object对象解构 并不是基于迭代特性实现, 它是ES9 新增的特性,只是提供了用于对于对象的解构
const obj = {name:"Harexs",age:22}
const newObj = {...obj}
const {name,age} = obj

源码阅读

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. arrify函数将接受一个value值,如果它是一个 null 或者undefined 那么直接返回一个空数组
  2. 如果它本身是一个数组 (通过Array.isArray判断) ,那么直接返回它本身不做任何处理
  3. 如果它是一个字符串, 则返回一个转为含有这个字符的数组 'a' => ['a']
  4. 如果它具有可迭代特性,即判断它的是否具有 [Symbol.iterator] 属性且是一个function类型,直接通过扩展运算法迭代,返回一个包括所有迭代结果的数组
  5. 如果以上条件都不符合直接返回一个 含有这个值的数组

package.json拓展

exports

//默认
"exports": "./index.js"

//不同路径的声明
"exports":{
    ".":"./index.js",
    "./foo":"./foo.js"
}
//对应的引入
import { one } from 'arrify' //来源 ./index.js
import { two } from 'arrify/foo' //来源 ./foo.js

const { one } = require("arrify/foo") //来源 ./index.js
const { two } = require("arrify/foo") //来源 ./foo.js

//模块化引入
"exports":{
    "import":"./es.js",
    "require":"./common.cjs"
}

exports字段定义了不同路径的导出,并且它的优先级大于 main 以及 browermodule 字段, 它还支持对于不同模块化情况下的引入

依赖

"devDependencies": {
        "ava": "^3.15.0",
        "tsd": "^0.14.0",
        "xo": "^0.39.1"
}

ava

基于Node.js 的单元测试包,具有简洁的API且快速,并支持Typescript

xo

集成了linter规则的ESLint包,不需要再做额外配置

tsd

通过.test-d.ts 文件做类型定义检查,它们不会被编译测试,只会对类型定义进行静态分析

总结

  1. 认识可迭代对象以及特性
  2. 认识原生可迭代对象
  3. 认识扩展运算符以及for of 的本质
  4. 认识package.json exports字段的特性