formDataToJSON 抽丝剥茧 formData 与 Object 的转换【玩转源码】

举报
叶一一 发表于 2023/07/30 15:44:21 2023/07/30
【摘要】 功能函数会依据实际需求去实现功能。本文主要分享通过分析 axios 源码中的两个重点的功能函数,学习复杂功能如何设计,同时还补充了些有助于今后开发的知识点,以及对于目前代码中有些判断条件简化的收获。

前言

通过axios 源码阅读,实现formDataToJSON 抽丝剥茧 formData 与 Object 的转换,接下来详细分享整个过程。

formDataToJSON 抽丝剥茧 formData 与 Object 的转换

FormData 对象

FormData 对象用以将数据编译成键值对,以便用XMLHttpRequest来发送数据。

FormData 对象主要用于发送表单数据,但亦可用于发送带键数据 (keyed data),而独立于表单使用。一般文件流数据的发送,会用到 FormData 对象。

第一条丝线— parsePropPath

matchAll

matchAll 函数会根据入参的正则表达式和字符串,返回所有匹配项的数组。

/**
 * It takes a regular expression and a string, and returns an array of all the matches
 *
 * @param {string} regExp - The regular expression to match against.
 * @param {string} str - The string to search.
 *
 * @returns {Array<boolean>}
 */
const matchAll = (regExp, str) => {
  let matches;
  const arr = [];

	// matches 为每次匹配正确的值
  while ((matches = regExp.exec(str)) !== null) {
    arr.push(matches);
  }

  return arr;
}

regExp.exec 方法

在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null。

匹配的标准用法就是如上功能中的那样,如果需要进一步了解可以阅读 MDN 文档

parsePropPath

parsePropPath 函数会将字符串中的字母匹配出来放到数组中。

但看描述和函数还不太确定具体用法,但是经典的来了。

当我们设计的某个函数不好用文字做描述,可以用举例的方式辅助描述。比如下面这个函数,不但举例,还举了多个例子,所以这个函数的用途一目了然。

/**
 * It takes a string like `foo[x][y][z]` and returns an array like `['foo', 'x', 'y', 'z']
 *
 * @param {string} name - The name of the property to get.
 *
 * @returns An array of strings.
 */
function parsePropPath(name) {
  // foo[x][y][z]
  // foo.x.y.z
  // foo-x-y-z
  // foo x y z
  return utils.matchAll(/\w+|\[(\w*)]/g, name).map(match => {
  	// 匹配值为数组,当匹配的第一个元素为空数组时返回空字符串,否则返回第二个元素或第一个元素存在值的那个。
    return match[0] === '[]' ? '' : match[1] || match[0];
  });
}

第二条丝线— arrayToObject

arrayToObject 函数会将数组转换对象。

/**
 * Convert an array to an object.
 *
 * @param {Array<any>} arr - The array to convert to an object.
 *
 * @returns An object with the same keys and values as the array.
 */
function arrayToObject(arr) {
  const obj = {};
	// 返回数组索引值的数组
  const keys = Object.keys(arr);
  let i;
  const len = keys.length;
  let key;
  for (i = 0; i < len; i++) {
    key = keys[i];
    obj[key] = arr[key];
  }
  return obj;
}

第三条丝线— forEachEntry

forEachEntry 函数会循环一个可迭代的对象,直到循环结束,把对象的 key 和 value 返回。

/**
 * For each entry in the object, call the function with the key and value.
 *
 * @param {Object<any, any>} obj - The object to iterate over.
 * @param {Function} fn - The function to call for each entry.
 *
 * @returns {void}
 */
const forEachEntry = (obj, fn) => {
  const generator = obj && obj[Symbol.iterator];

  const iterator = generator.call(obj);

  let result;

  // 循环直到迭代器已将序列迭代完毕
  while ((result = iterator.next()) && !result.done) {
    const pair = result.value;
    fn.call(obj, pair[0], pair[1]);
  }
};

我找了一个可迭代对象测试了一下输出

const data = new Map();

data.set('a', 1);
data.set('b', 2);
data.set('c', 3);

forEachEntry(data, (name, value) => {
  let res = name + '-' + value;
  console.log('name-value:', res);
});

// 输出结果
// > name-value: a-1
// > name-value: b-2
// > name-value: c-3

formDataToJSON

formDataToJSON函数接受 一个 FormData 对象最终返回 JavaScript 对象。

/**
 * It takes a FormData object and returns a JavaScript object
 *
 * @param {string} formData The FormData object to convert to JSON.
 *
 * @returns {Object<string, any> | null} The converted object.
 */
function formDataToJSON(formData) {
	/**
   * 递归函数 将 FormData 的键值全部插入到给 target 对象
   * @param {Array} path FormData 的属性名数组
   * @param {string} value FormData 的属性值
   * @param {Object} target 最终的目标对象
   * @param {number} index FormData 的属性值数组的索引值
   * @returns 递归是否中止的布尔值
   */
  function buildPath(path, value, target, index) {
    let name = path[index++];
		// isFinite 函数会先将测试值转换为数字,然后再对其进行是否为有限数检测。
    const isNumericKey = Number.isFinite(+name);
    const isLast = index >= path.length;
    name = !name && utils.isArray(target) ? target.length : name;

    if (isLast) {
			// 如果检查对象具有该属性,将 value 添加到对应的数组中
      if (utils.hasOwnProp(target, name)) {
        target[name] = [target[name], value];
      } else {
        target[name] = value;
      }

      return !isNumericKey;
    }

    if (!target[name] || !utils.isObject(target[name])) {
      target[name] = [];
    }

    const result = buildPath(path, value, target[name], index);

    if (result && utils.isArray(target[name])) {
      target[name] = arrayToObject(target[name]);
    }

    return !isNumericKey;
  }

	// formData 参数是 FormData 类型且 formData.entries 是一个函数
  if (utils.isFormData(formData) && utils.isFunction(formData.entries)) {
    const obj = {};

    utils.forEachEntry(formData, (name, value) => {
      buildPath(parsePropPath(name), value, obj, 0);
    });

    return obj;
  }

  return null;
}

我找 axios 自带的测试实例中的例子打印了一下结果

对象的值是数组

const formData = new FormData();
formData.append('foo', '1');
formData.append('foo', '2');

const res = formDataToJSON(formData);
console.log('res', res); // -> { foo: ['1', '2'] }

对象

const formData = new FormData();

formData.append('foo', '1');
formData.append('bar', '2');

const res = formDataToJSON(formData);
console.log('res', res); // -> {foo: '1', bar: '2'}

嵌套对象

const formData = new FormData();

formData.append('foo[bar][baz]', '123');

const res = formDataToJSON(formData);
console.log('res', res); // -> { foo : { bar : {baz: '123'} } }

小结

  1. formDataToJSON 函数接受 一个 FormData 对象最终返回 JavaScript 对象。
  2. FormData() 构造函数用于创建一个新的FormData对象。该函数在 Web 中可用,一些环境会报错“FormData is not define”。
  3. FormData 对象转换 JavaScript 对象的功能不是很常用。但是这里的主要收获是,当函数设计的相对复杂的时候,可以用抽丝剥茧的方式,现将支线整理出来,最终拼凑成一个完整的主线。

总结

功能函数会依据实际需求去实现功能。通过分析 axios 源码中的两个重点的功能函数,学习复杂功能如何设计,以及补充了些知识点,还得到目前代码中有些判断条件简化的收获。

很愉快的一次源码阅读体验。

axios 源码阅读历程

捎带分享一下我关于 axios 源码阅读中的历程。

数月前

因为工作中需要弄清楚 axios 的 request 中的入参而在源码中寻找答案,当时想抽时间进行一次源码阅读,但是没有坚持下去,就此作罢。

数日前

前几天做功能联想,重新开始阅读,这次收获颇多。

心得体会

坚持,果然是帮助完成一件事的良策。


作者:非职业「传道授业解惑」的开发者叶一一
简介:「趣学前端」、「CSS畅想」系列作者,华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。