评论

使用Babel插件简单实现go中的iota

本文介绍了一种在js中快速定义枚举值的方式,并且通过Babel插件对代码进行转换,实现了静态的语句声明

go 语言中,存在一个特殊的常量值 iota,通常用来快速定义枚举值,在编译时将会被替换成基本的数字,基本用法如下:

package main
import "fmt"

// 声明了3个常量,并且值分别是 0 1 2
const (
    A = iota
    B
    C
)

func main() {
    fmt.Println(A, B, C)
}

运行结果为:

0 1 2

写起来非常方便,只需要写一次iota,后面就会自动自增赋值。

而在 javascript 中,如果要定义枚举值,通常需要一个一个的赋值,例如:

const A = 0, B = 1, C = 2;

console.log(A, B, C);

可以看到,写起来有一些繁琐,需要手动维护每一个枚举值,还可能写重复

如果是 typescript 中,可以使用 const enum 来实现相同的功能,具体写法如下:

const enum SomeEnum {
    A,
    B,
    C,
};

console.log(SomeEnum.A, SomeEnum.B, SomeEnum.C); // 输出 0 1 2

那如果不使用 typescript,该如何实现一个类似于 go 中的 iota,同时又能满足js语法的效果呢?

本文在此将实现一种使用Babel进行简单语法转换的方式,实现上面描述的功能,仅用于学习,毕竟这个并没有什么用,你可能永远不会在项目中使用。😛

语法设计

首先我们需要找到一种js语法形式,能快速定义多个常量,并且只需要赋值一次,那在js中,解构赋值就能满足我们的需要

const [A, B, C] = iota;

那聪明的你可能已经想到了,这里的 iota 如果定义成可迭代对象,不也能满足需求吗?还是原生的js写法,连Babel都省了

const iota = {
    [Symbol.iterator]() {
        let v = 0;
        return {
            next: () => ({ value: v++ })
        }
    }
};

const [A, B, C] = iota;
const [D, E, F] = iota;

console.log(A, B, C); // 输出 0 1 2
console.log(D, E, F); // 输出 0 1 2

🤔。。。本文结束🎉。。。

可是作为一个有理想的前端人,不是很想在项目中定义枚举值时,用了运行时计算的方式,还有就是要完成本文的标题任务(主要是这个),在此,将会介绍使用Babel插件实现。

希望转换的成果

希望通过转换后,得到如下的效果,从代码层面上消除 iota 标识符,同时将所有由iota声明的常量进行自增赋值:

const A = 0, B = 1, C = 2;

插件编写

废话不多说,接下来将直接在 astexplorer.net 上写一个这个插件

转换前的代码如下,在此特地在一个 const 语句中声明了2个iota语句,并且第一个赋值缺省了D这个标识符

export const [A, B, C, , E] = iota, [F, G, H] = iota;

那我们的插件将按照下面的方式进行编写:

export default function ({ types: t }) {
  // 用于判断是否是 iota 赋值
  const filterIota = (declarator) => t.isIdentifier(declarator.init, { name: "iota" });

  return {
    name: "babel-plugin-iota",

    visitor: {
      // 仅处理变量声明语句
      VariableDeclaration(nodePath) {
        const { node } = nodePath;

        // 如果不是 const 声明,则跳过
        if (node.kind !== "const") {
          return;
        }

        // 如果不存在 iota 赋值,则跳过
        if (!node.declarations.some(filterIota)) {
          return;
        }

        // 新的定义语句
        const newDeclarations = [];
        node.declarations.forEach((it) => {
          // 如果是 iota 赋值语句
          if (filterIota(it)) {
            // 校验是数组解构赋值
            t.assertArrayPattern(it.id);
            // 展开每个标识符
            it.id.elements.forEach((el, idx) => {
              // 可能存在缺省声明
              if (el) {
                // 校验必须是标识符,不能是其它形式
                t.assertIdentifier(el);
                // 转换成 XX = 数字 的形式
                newDeclarations.push(t.variableDeclarator(el, t.numericLiteral(idx)));
              }
            });
          } else {
            // 其它形式的赋值语句保持原样
            newDeclarations.push(it);
          }
        });
        // 替换原 const 变量声明语句
        nodePath.replaceWith(t.variableDeclaration("const", newDeclarations));
      }
    }
  };
}

以下是一个转换的效果图

至此,我们使用Babel插件完成了类似go语言中的iota定义枚举值的一个功能

真·完结撒花🎉

最后一次编辑于  2022-12-26  
点赞 0
收藏
评论
登录 后发表内容