源码共读之 arrify 包

欢迎关注我的微信公众号『前端我废了』,查看更多文章!!!

从包名 arrify 我们就能知道这个包能够将各种类型的输入值转换为数组,当我们想要确保传递的值是一个数组时,可以使用 arrify 函数将其转换为数组,防止抛出异常。

用法

arrify(null);      // []
arrify(undefined); // []
arrify(1);         // [1]
arrify([1]);       // [1]
arrify('foo');     // ['foo']
arrify(new Set([1, 2, 3]));  // [1, 2, 3]

接下来阅读下它的源码,只有 10 几行。

源码分析

从仓库的 package.json 文件的 exports 字段可以看到入口文件为 index.js

function arrify(value) {
	// 如果输入值 value 为 null 或 undefined,返回空数组
	if (value === null || value === undefined) {
		return [];
	}
	// 使用 isArray 方法检查输入值 value 是否为数组,是则直接返回 value
	if (Array.isArray(value)) {
		return value;
	}
	// 使用 typeof 操作符来检查输入值是否为字符串。是则将 value 包装在数组中并返回
	if (typeof value === 'string') {
		return [value];
	}
	// 检查输入值是否具有迭代器方法,是则使用扩展运算符 ... 将可迭代对象转换为数组并返回
	if (typeof value[Symbol.iterator] === 'function') {
		return [...value];
	}
	// 其他类型则返回
	return [value];
}

源码里面利用 Array.isArray 方法判断输入值 value 是否为数组,如果为数组的话就没必要再往下执行了,直接返回 value。

还判断了输入值为字符串类型,如果为字符串类型则将 value 包装在数组中并返回。为什么要做这一步判断呢?因为字符串类型其实也是可迭代的,如果不做判断的话,将会去调用字符串的迭代器方法(即 Symbol.iterator 属性),那么输出就不符合我们预期了;例如:arrify(’foo’) 将会返回 ['f', 'o', 'o'] 而不是 ['foo'] ,显然这不是我们想要的结果。

扩展了解

Symbol.iterator

Symbol.iterator 是 ES6 引入的一个类型为 Symbol 的特殊值,用于指定对象的迭代器方法。迭代器方法是一个特殊的函数,执行迭代器方法会返回一个迭代器对象,可以使用 for…of 来遍历迭代器对象。

在 JavaScript 中,有许多内置对象是可迭代的(String、Array、TypedArray、Map 和 Set),这些对象都默认部署了迭代器方法(即 Symbol.iterator 方法),用于遍历它们的值。

在这里插入图片描述

默认会调用 Iterator 接口(即Symbol.iterator方法)的场合:

  • for … of
  • 解构赋值
  • 扩展运算符
  • yield*
  • 任何接受数组作为参数的场合,都调用了遍历器接口,因为数组的遍历会调用遍历器接口;例如Set()Promise.all()

了解更多:

包入口配置:exports 字段

在 arrify 包的 package.json 文件中的入口定义使用的 exports 而不是 main 字段,那就来了解下这个字段吧。

package.json 文件中,mainexports 字段都可以用于指定 ESM 或 CommonJS 模块的入口,但是 main 能力有限,只能定义一个主入口,因此 node v12.7.0 引入了exports ****字段来替代 main,在支持 exports 字段的 Node.js 版本中,exports 的优先级高于 main

exports 作为 main 的替代方案,有以下优势:

  • 可以定义多入口;
  • 具有封装性;所有入口都需要在 exports 中显示定义,否则在导入未定义的入口时将报错 ERR_PACKAGE_PATH_NOT_EXPORTED
  • 支持条件导出;
  • 可以自定义条件导出。可以通过选项 --conditions 设置任意数量的自定义条件,例如 node --conditions=foo --conditions=bar index.js

多入口定义

例如有一个包 my-package,有三个文件 index.js , lib.js , feature.js ,它的 package.json 如下定义:

{
	"name": "my-package",
	"exports": {
		".": "./index.js",
		"./lib": "./lib.js"
	}
}

那么可以这样导入

// CommonJS 方式引入模块
const mypkg = require("my-package");
const lib = require("my-package/lib")

由于我们未在 exports 中定义 feature.js 文件的入口,导入 feature 子模块将报错 ERR_PACKAGE_PATH_NOT_EXPORTED ,这种封装性使得包所有对外暴露的模块都需要显示定义,让包的使用及维护升级更可靠。

// 报错:Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './feature' is not defined by "exports" ...
const feature = require("my-package/feature")

条件导出

exports 字段提供的条件导出功能,可以定义每个环境对应不同的模块包的入口文件。

以 vue 为例

// package.json
{
	“name”: "vue",
	// 定义包的入口
	"exports": {
		// 主入口
		".": {
			// 通过 ESM 方式,例如 import, import() ...
      		"import": {
				// 在 node 环境中
        		"node": "./index.mjs",
				// 默认入口作为回退方案,放在最后
        		"default": "./dist/vue.runtime.esm-bundler.js"
      		},
			// 通过 require() 方式
      		"require": "./index.js",
      		"types": "./dist/vue.d.ts" // 指定 ts 声明文件
    	},
	  // ...
	}
}

上面 exports 字段定义表示,如果是

  1. ESM 方式导入模块:
    1. 在node 环境中,则入口文件为 ./index.mjs
    2. 其他 js 运行环境,则入口文件为 ./dist/vue.runtime.esm-bundler.js
  2. CJS 方式导入模块,则入口文件为 ./index.js

其中 . 代表包导出的主入口,如果只定义一个入口的话,可以直接设为 exports 的值,例如本文 arrify 包的写法:

// package.json
{
	"name": "arrify",
	"exports": "./index.js"
  // 等同于
	// “exports”: {
	// 	 ".": "./index.js"
	// }
	// ...
}

自定义条件导出

除此之外,exports 还支持通过选项 --conditions 设置用户自定义条件;如下例子,在 main.js 中导入一个包 my-package,执行 main.js 文件时指定 --conditions=foo ,即 node --conditions=foo main.js

// main.js
const myPkg = require('my-package')

在 my-package 的 package.json 文件中的 exports 字段定义了一个自定义条件 foo

// package.json
{
	"name": "my-package",
	"exports": {
		"foo": "./index.js"
	}
}

那么包 my-package 的入口文件就是 ./index.js ,即会匹配为 exports 字段中定义的 foo 条件对应的文件。

优先级

如果 packege.json 中同时定义了 exportsmainexports 字段优先于 mainexports 定义的内容顺序也是有优先级的,越先定义被匹配的优先级越高。

例子:下面 exports 字段如下定义,default 定义在 require 前面,那么就会先匹配到 default 条件。

{
	"name": "my-package",
	"main": "./index.js",
	"exports": {
    "default": "./index.js",
    "require": "./main.js"
	}
}

了解更多 Node.js 文档 - Package entry points

总结

从 arrify 包的源码中,拓展了解了 Symbol.iterator ;通过 package.json 的入口文件配置( exports 字段),了解到如何声明一个模块的入口文件,感觉每个知识点都可以再单独展开写一篇文章,找时间再深入学习下。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值