记一次递归在我项目中所发挥的作用

陈大鱼头 ... 2021-8-3 Js
  • 前端
  • Js
  • 递归
  • 进阶
About 7 min

# 背景

在最近的项目中,有这么一个功能点,就是要获取在WEB IDE里用户所写的注释中的一段特殊规则,然后解析成一段JS config 对象
例如:

//% width="100px" height="200px"
//% pos.top="50px" pos.left="50px"
//% writable=true
//% q.b.d.w.r.f=30 q.b.d.w.r.a=40
1
2
3
4

要转成

{
    width: '100px',
    height: '200px',
    pos: {
        top: '50px',
        left: '50px'
    },
    writable: true,
    q: {
        b: {
            d: {
                w: {
                    r: {
                        f: 30,
                        a: 40
                    }
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

类似的规则
悲伤蛙

# 什么是递归

来自百度的解释:
递归:程序调用自身的编程技巧称为递归 (recursion) (opens new window)
尾递归:如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。 (opens new window)

就是 复读机 复读机

# 递归怎么写?

一般

const fibonacci = num => (num === 1 ? 1 : num * fibonacci(num - 1))
1

尾递归

const fibonacci = (num, total = 1) => (num === 0 ? total : fibonacci(num - 1, num * total))
1

Array.reduce

const getArray = count => Array.from({ length: count }, (value, key) => key)
const fibonacci = num => getArray(num).reduceRight((accumulator, currentValue) => accumulator * currentValue)
1
2

# 功能实现

# 分步实现

  1. 过滤常规内容,获取特殊的备注信息,去除空格,并且转成数组
    • 此时的数组内容为
      [
          '//% blockId="sloth_servo_write"  block="set servo %channel|degree %degree"',
          '//% advanced=true',
          '//% weight=50',
          '//% degree.min=0 degree.max=180',
          '//% channel.fieldEditor="gridpicker" channel.fieldOptions.columns=4',
          '//% a.b.c.d=20 a.b.c.e=222',
          '//% q.b.d.w.r.f=30 q.b.d.w.r.a=40'
      ]
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
    const code = `
    //% width="100px" height="200px"
    //% pos.top="50px" pos.left="50px"
    //% writable=true
    //% q.b.d.w.r.f=30 q.b.d.w.r.a=40`
    // 获取特殊注释数组
    const annotation_array_filter = annotation_item => annotation_item.indexOf('//%') >= 0;
    // 去除特殊注释前后的空格
    const annotation_array_remove_space = annotation_item => annotation_item.trim();
    const annotation_array = code.split('\n')
                                 .filter(annotation_array_filter)
                                 .map(annotation_array_remove_space)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  2. 遍历特殊规则数组,把每一项配置都压入一个新对象
    • 此时的新对象内内容为
      {
          a.b.c.d: 20,
          a.b.c.e: 222,
          advanced: true,
          block: "set servo %channel|degree %degree",
          blockId: "sloth_servo_write",
          channel.fieldEditor: "gridpicker",
          channel.fieldOptions.columns: 4,
          degree.max: 180,
          degree.min: 0,
          q.b.d.w.r.a: 40,
          q.b.d.w.r.f: 30,
          weight: 50,
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      const annotation_array_loop = annotation_item => {
        // 把注释中的每一项配置转成对象
        const result_forEach = result_item => {
          let annotation_sub_object = {};
          // 如果特殊注释数组中的每一项包含多个配置,则扁平化
          const array_flattened = data => {
            const is_array = (this.type(data) === '[object Array]');
            const object_recursion = () => {
              const [key, value] = data.split('=');
              const annotation_sub_object = {};
              try {
                annotation_sub_object[key] = JSON.parse(value);
              } catch (error) {
                annotation_sub_object[key] = JSON.parse(value + '"')
              };
              annotation_object = {
                ...annotation_object,
                ...annotation_sub_object
              };
            };
            // 判断注释数组项中每一个元素是否有多个配置,如果有则递归,否则则注入对象
            is_array ? data.forEach(e => { array_flattened(e); }) : object_recursion();
          };
          array_flattened(result_item);
        };
        // 去除特殊数组中每一项多余的内容
        const result_map = result_item => (result_item.match(/\=/g).length > 1 ? result_item.split(' ') : result_item);
        const result = annotation_item.replace('//% ', '')
                                      .split('/\" /g')
                                      .map(result_map);
        result_forEach(result);
      };
      let annotation_object = {}; // 承载每一个配置的对象
      annotation_array.forEach(annotation_array_loop);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  1. 把数组里的元素转成对象
    • 此时数组内容为
      [
          {
              blockId: "sloth_servo_write"
          },
          {
              advanced: true
          },
          ...
      ]
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      let main_array = []; // 承载每一个配置的数组
      const annotation_object_keys = Object.keys(annotation_object); // 获取扁平化后的注释对象的key
      const annotation_object_keys_loop = annotation_object_key => { // 循环变量每一项注释
        const annotation_object_key_array = annotation_object_key.split('.'); // 把多级对象转成数组
        const annotation_object_value = annotation_object[annotation_object_key]; // 获取每一项元素的值
        let sub_object = {}; // 暂时承载配置对象的对象
        const key_reduce = (accumulator, current_value, current_index, array) => { // key值递归,对每一项配置进行合并
          if (current_index === 0) { // 如果当前遍历的元素为第一项,也就是说为配置的顶级对象,所以直接压入对象,并且输出
            sub_object[current_value] = (current_index === array.length - 1 ? annotation_object_value : {});
            return sub_object[current_value];
          }
          accumulator[current_value] = {}; // 如果当前遍历的元素不为第一项,则当前对象元素变为对象
          if (current_index === array.length - 1) { // 如果当前遍历的元素为数组最后一项,说明是配置对象最底的元素,可以直接赋值
            accumulator[current_value] = annotation_object_value;
          }
          return accumulator[current_value];
        };
        let level_object = annotation_object_key_array.reduce(key_reduce, annotation_object_key_array[0]);
        level_object = undefined; // 清空level_object
        main_array.push(sub_object);
        sub_object = undefined; // 清空sub_object
      }
      annotation_object_keys.forEach(annotation_object_keys_loop);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  1. 递归合并对象
    • 此时的对象为
      {
          a: {b: {…}},
          advanced: true,
          block: "set servo %channel|degree %degree",
          blockId: "sloth_servo_write",
          channel: {fieldEditor: "gridpicker", fieldOptions: {…}},
          degree: {min: 0, max: 180},
          q: {b: {…}},
          weight: 50
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const annotation_tree = {};
      const tree_data = (key, value, object) => { // 递归合并对象
        if (this.type(value) !== '[object Object]') { // 如果当前传入元素为对象,则直接压入对象中
          object[key] = value;
        } else { // 否则继续递归
          if (!object[key]) {
            object[key] = {};
          };
          for (let item in value) {
            tree_data(item, value[item], object[key]);
          }
        };
      };
      const main_array_forEach = item => { // 循环遍历配置数组
        const key = Object.keys(item)[0];
        const value = Object.values(item)[0];
        tree_data(key, value, annotation_tree);
      };
      main_array.forEach(main_array_forEach);
      main_array = undefined; // 清空main_array
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 完整代码

// 代码转换器
((wid, dcm) => {
  'use strict';
  const win = wid;
  const doc = dcm;

  // 基础信息
  const base_info = {
    'version': '0.0.1',
    'author': 'kris',
  };

  // 输出的函数
  const funcs = {
    annotation_parser (annotation) {
      // 配置树初始化
      this.annotation_tree = {};
      // 获取特殊注释数组
      const annotation_array_filter = annotation_item => annotation_item.indexOf('//%') >= 0;
      // 去除特殊注释前后的空格
      const annotation_array_remove_space = annotation_item => annotation_item.trim();
      // 循环遍历特殊注释数组
      const annotation_array_loop = annotation_item => {
        // 把注释中的每一项配置转成对象
        const result_forEach = result_item => {
          let annotation_sub_object = {};
          // 如果特殊注释数组中的每一项包含多个配置,则扁平化
          const array_flattened = data => {
            const is_array = (this.type(data) === '[object Array]');
            const object_recursion = () => {
              const [key, value] = data.split('=');
              const annotation_sub_object = {};
              try {
                annotation_sub_object[key] = JSON.parse(value);
              } catch (error) {
                annotation_sub_object[key] = JSON.parse(value + '"')
              };
              annotation_object = {
                ...annotation_object,
                ...annotation_sub_object
              };
            };
            // 判断注释数组项中每一个元素是否有多个配置,如果有则递归,否则则注入对象
            is_array ? data.forEach(e => { array_flattened(e); }) : object_recursion();
          };
          array_flattened(result_item);
        };
        // 去除特殊数组中每一项多余的内容
        const result_map = result_item => (result_item.match(/\=/g).length > 1 ? result_item.split(' ') : result_item);
        const result = annotation_item.replace('//% ', '')
                                      .split('/\" /g')
                                      .map(result_map);
        result_forEach(result);
      };
      let annotation_object = {}; // 承载每一个配置的对象
      annotation.filter(annotation_array_filter)
                .map(annotation_array_remove_space)
                .forEach(annotation_array_loop);
      let main_array = []; // 承载每一个配置的数组
      const annotation_object_keys = Object.keys(annotation_object); // 获取扁平化后的注释对象的key
      const annotation_object_keys_loop = annotation_object_key => { // 循环变量每一项注释
        const annotation_object_key_array = annotation_object_key.split('.'); // 把多级对象转成数组
        const annotation_object_value = annotation_object[annotation_object_key]; // 获取每一项元素的值
        let sub_object = {}; // 暂时承载配置对象的对象
        const key_reduce = (accumulator, current_value, current_index, array) => { // key值递归,对每一项配置进行合并
          if (current_index === 0) { // 如果当前遍历的元素为第一项,也就是说为配置的顶级对象,所以直接压入对象,并且输出
            sub_object[current_value] = (current_index === array.length - 1 ? annotation_object_value : {});
            return sub_object[current_value];
          }
          accumulator[current_value] = {}; // 如果当前遍历的元素不为第一项,则当前对象元素变为对象
          if (current_index === array.length - 1) { // 如果当前遍历的元素为数组最后一项,说明是配置对象最底的元素,可以直接赋值
            accumulator[current_value] = annotation_object_value;
          }
          return accumulator[current_value];
        };
        let level_object = annotation_object_key_array.reduce(key_reduce, annotation_object_key_array[0]);
        level_object = undefined; // 清空level_object
        main_array.push(sub_object);
        sub_object = undefined; // 清空sub_object
      }
      annotation_object_keys.forEach(annotation_object_keys_loop);
      const tree_data = (key, value, object) => { // 递归合并对象
        if (this.type(value) !== '[object Object]') { // 如果当前传入元素为对象,则直接压入对象中
          object[key] = value;
        } else { // 否则继续递归
          if (!object[key]) {
            object[key] = {};
          };
          for (let item in value) {
            tree_data(item, value[item], object[key]);
          }
        };
      };
      const main_array_forEach = item => { // 循环遍历配置数组
        const key = Object.keys(item)[0];
        const value = Object.values(item)[0];
        tree_data(key, value, this.annotation_tree);
      };
      main_array.forEach(main_array_forEach);
      main_array = undefined; // 清空main_array
    },
  };
  // 引用的资源
  const libs = {};
  // 工具函数
  const tools = {
    // 获取元素类型
    type (object) {
      return Object.prototype.toString.call(object);
    },
    // 分离传入的代码跟配置
    separate_code_and_config (data) {
      data.split('\n')
          .forEach(item => {
        item.indexOf('//%') >= 0 ? this.blockly_config_array.push(item.trim()) : this.python_code_array.push(item);
      });
    },
  };
  // 定义的元素
  const vars = {
    blockly_config_array: [],
    python_code_array: [],
    annotation_tree: {},
    python_tree: {},
  };
  // 根对象
  const code_transformer = {
    ...base_info,
    ...libs,
    ...funcs,
    ...tools,
    ...vars,
  };

  const _global = (() => {
    return this || (0, eval)('this');
  })();
  if (typeof module !== 'undefined' && module.exports) {
    module.exports = code_transformer;
  } else if (typeof define === 'function' && define.amd) {
    define([], function () {
      return code_transformer;
    });
  } else {
    !('code_transformer' in _global) && (_global.code_transformer = code_transformer);
  };
})(window, document);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148

# 备注:函数体积好大呀,但这只是业务里的一个小小小功能,流下了不会优化代码的泪水~

哭

如果你喜欢探讨技术,或者对本文有任何的意见或建议,非常欢迎加鱼头微信好友一起探讨,当然,鱼头也非常希望能跟你一起聊生活,聊爱好,谈天说地。 鱼头的微信号是:krisChans95 也可以扫码关注公众号,订阅更多精彩内容。 https://bucket.krissarea.com/blog/base/qrcode-all1.png

Last update: June 25, 2023 00:16
Contributors: fish_head , 陈大鱼头