我们来看一下 babel 中是如何把箭头函数转换成普通函数的,然后我们也来实现一个这样的插件。

安装包

  • @babel/core babel 核心模块
  • @babel/types 用来生成或者判断节点的AST语法树的节点
  • babel-plugin-transform-es2015-arrow-functions 转换插件
    1. yarn add @babel/core @babel/types babel-plugin-transform-es2015-arrow-functions

    使用

    ```javascript const core = require(‘@babel/core’); const types = require(‘@babel/types’);

const arrowFunctionPlugin = require(‘babel-plugin-transform-es2015-arrow-functions’);

const sourceCode = const sum = (a, b) => { return a + b; };;

const targetSource = core.transform(sourceCode, { plugins: [arrowFunctionPlugin] });

console.log(targetSource.code);

运行代码后的结果如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/1584826/1658117008672-fce151e4-2f71-4ff6-b447-8e69cb951bc8.png#clientId=u5967db25-a393-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=241&id=u5faac3eb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=482&originWidth=1200&originalType=binary&ratio=1&rotation=0&showTitle=false&size=122280&status=done&style=none&taskId=ud2ecd2e8-4784-44f5-a506-15216ddcf51&title=&width=600)<br />箭头函数被转换成了 function 声明。
<a name="IgHrW"></a>
## 手动实现转换插件
接下来我们自己写一个 babel-plugin-transform-es2015-arrow-functions 插件。<br />我们替换掉上面代码中的 arrowFunctionPlugin 对象。<br />在这个对象中有一个 访问者属性 visitor,用来对语法树进行转换,其中包含了各种类型节点的触发函数。
```javascript
//...

const arrowFunctionPlugin = {
  // 访问者对象
  visitor:{
    // 如果是箭头函数,那么就会进来此函数,参数是箭头函数的节点路径对象
    ArrowFunctionExpression(path){
      const node = path.node;
      node.type = 'FunctionExpression';
    }
  }
}

//...

节点原来的 type 是 ArrowFunctionExpression,我们把节点的 type 改为 FunctionExpression,就能转换箭头函数了。
完整代码如下:

const core = require('@babel/core');
const types = require('@babel/types');

const arrowFunctionPlugin = {
  // 访问者对象
  visitor:{
    // 如果是箭头函数,那么就会进来此函数,参数是箭头函数的节点路径对象
    ArrowFunctionExpression(path){
      const node = path.node;
      node.type = 'FunctionExpression';
    }
  }
}

const sourceCode = `
const sum = (a, b) => {
  return a + b;
};
`;

const targetSource = core.transform(sourceCode, {
  plugins: [arrowFunctionPlugin]
});

console.log(targetSource.code);

执行结果和原来一摸一样,如下:
image.png

箭头函数中的 this 的处理

众所周知,在箭头函数中是没有 this 的,那么我们的插件是如何实现的呢?
先看看原版的插件

// babel 核心模块
const core = require('@babel/core');
//用来生成或者判断节点的AST语法树的节点
const types = require('@babel/types');

const arrowFunctionPlugin = require('babel-plugin-transform-es2015-arrow-functions');
// const arrowFunctionPlugin = {
//   visitor: {
//     // 如果是箭头函数,那么就会进来此函数,参数是箭头函数的节点路径对象
//     ArrowFunctionExpression(path){
//       const node = path.node;
//       node.type = 'FunctionExpression';
//     }
//   }
// }

const sourceCode = `
const sum = (a, b) => {
  console.log(this)
  return a + b;
}
`
const targetSource = core.transform(sourceCode, {
  plugins: [arrowFunctionPlugin]
});

console.log(targetSource.code);

运行结果
image.png
插件在函数外声明了一个 _this 变量给函数内部使用。
这样我们也来实现下

// babel 核心模块
const core = require('@babel/core');
//用来生成或者判断节点的AST语法树的节点
const types = require('@babel/types');

// const arrowFunctionPlugin = require('babel-plugin-transform-es2015-arrow-functions');
const arrowFunctionPlugin = {
  visitor: {
    // 如果是箭头函数,那么就会进来此函数,参数是箭头函数的节点路径对象
    ArrowFunctionExpression(path){
      const node = path.node;
      // 处理 this 方法
      hostFunctionEnvironment(path);
      node.type = 'FunctionExpression';
    }
  }
}
/**
 * 1.要在函数的外面声明一个 _this 变量,值是 this
 * 2.在函数的内容,把 this 变成 _this
 * @param {*} path 
 */
function hostFunctionEnvironment(path){
  //确定我的 this 变量在哪个环境里生成,向上查找,是普通函数或者是根节点 Program
  const thisEnvFn = path.findParent(parent => {
    return (parent.isFunction() && !path.isArrowFunctionExpression()) || parent.isProgram();
  });

  const thisBindings = '_this';
  // 如果已经有一个 _this 绑定了,那就不重新添加
  if(!thisEnvFn.scope.hasBinding(thisBindings)){
    thisEnvFn.scope.push({
      id: types.identifier(thisBindings), //_this
      init:types.thisExpression(), // this
    });
  }

  // 替换this
  const thisPaths = getScopeInfo(path);
  thisPaths.forEach(thisPath => {
    // 把 this 替换成 _this
    thisPath.replaceWith(types.identifier(thisBindings));
  })
}

function getScopeInfo(path){
  let thisPaths = [];
  path.traverse({
    ThisExpression(path){
      thisPaths.push(path);
    }
  });
  return thisPaths;
}

const sourceCode = `
const sum = (a, b) => {
  console.log(this);
  const minus = (c, d)=>{
    console.log(this)
    return c - d;
  }
  return a + b;
}
`
const targetSource = core.transform(sourceCode, {
  plugins: [arrowFunctionPlugin]
});

console.log(targetSource.code);

打包结果
image.png