Skip to content

插入Function类型的节点

Function在JS中是一个基础类型,任何语言都离不开函数。在JS中函数还有一个特性arguments,还记得redux吗?里面是不是有一个高阶函数connect,是关联viewmodel的。而在nodeJS中我们可以依据入参长度来走不同的逻辑分支。

下面是几种实现函数方式:

  1. 函数声明

    javascript
    function foo () {
      console.log('=====> foo')
    }
  2. 函数表达式

    javascript
    var foo = function () {
      consle.log('=====> foo')
    }
  3. 构造函数

    javascript
    var foo = new Function ('params', 'console.log("======> foo")')
  4. 箭头函数

    javascript
    var foo = () => { console.log('=====> foo') }
  5. 异步箭头函数

    javascript
    var foo = async () => {
      await console.log('======> foo.await')
    }
  6. 异步函数

    javascript
    async function foo () {
      await console.log('=====> foo.await')
    }
  7. generator函数

    javascript
    function* foo() {
      yield 'a'
    }
  8. 对象属性

    javascript
    var obj = {
      say() {
        console.log('======> obj.say')
      }
    }
  9. 原型链属性

    javascript
    function foo () {}
    
    foo.prototype.say = function () {
      console.log('=====> foo.say')
    }
  10. Class中的属性

    javascript
    class Foo {
      constructor () {}
      say () {
        console.log('=====> foo.say')
      }
    }
  11. IIFE 立即执行函数

    javascript
    (function () {
      console.log(this)
    })(window)

到目前为止,在日常项目中创建函数的方法基本上都已经给出来了。我们日常的开发根本用不到这许多方法,比如IIFE我们很少用到它,它几乎只出现在node_modules内部。而在Class类中声明函数和原型链声明函数,几乎可以与在对象中声明函数同类。

创建函数有很多的方法,当然我上面的列出来的方法肯定还有遗漏,或者是分类重复,但是大致上已经是我们日常使用的全部了。

下面就一一列出利用types创建的例子。

  1. 函数声明

    javascript
    	/**
    	 * @NOTE 使用函数声明创建函数
    	 * function foo () {
    	 *  console.log('=====> foo')
    	 * }
    	 */
    	const funcNode1 = t.functionDeclaration(
    		t.identifier('foo'),
    		[t.identifier('p1'), t.identifier('p2'), t.identifier('...p')],
    		t.blockStatement([
    			t.expressionStatement(
    				t.callExpression(
    					t.memberExpression(t.identifier('console'), t.identifier('log')),
    					[t.stringLiteral('=====> foo.p1'), t.identifier('p1')]
    				)
    			),
    			t.expressionStatement(
    				t.callExpression(
    					t.memberExpression(t.identifier('console'), t.identifier('log')),
    					[t.stringLiteral('=====> foo.p'), t.identifier('p')]
    				)
    			),
    		])
    	)

    还是老流程,其最终目的就是将源代码转化为AST语句。之前我们已经认识了variableDeclaration,现在我们又认识了与Function息息相关的functionDeclaration。前者是声明变量,后者是声明函数。

    memberExpression成员表达式,在Object中已经认识过了,只要代码里面出现.号,就需要用到它。

    callExpression函数调用表达式,只要遇到了执行函数就需要用到它。

    blockStatement块语句,我们的块语句就是{},所以遇到了花括号就需要用到它。

    expressionStatement表达式语句,这里与《重学前端》联动,可以去看《语法篇》,里面详细介绍了什么是语句,什么是表达式。

    identifier字面直接量,不多介绍,我们的老朋友。

  2. 函数字面量

    javascript
    	/**
    	 * @NOTE 使用函数字面量
    	 * var foo = function () {
    	 *  console.log('=======> foo')
    	 * }
    	 */
    	const node2FuncExpression = t.functionExpression(
    		t.identifier(''),
    		[t.identifier('p1')],
    		t.blockStatement([
    			t.expressionStatement(
    				t.callExpression(
    					t.memberExpression(t.identifier('console'), t.identifier('log')),
    					[t.identifier('p1')]
    				)
    			),
    		])
    	)
    	const funcNode2 = t.variableDeclaration('const', [
    		t.variableDeclarator(t.identifier('foo'), node2FuncExpression),
    	])

    这里在等号的左侧出现了const关键字,所以必须使用variableDeclaration来执行一个变量声明操作。与例1不同,现在这个函数是字面量,在声明时不需要指定函数名。

  3. 利用构造函数

    javascript
    	/**
    	 * @NOTE 利用构造函数
    	 * var foo = new Function('p1', 'p2', `console.log('======> foo', p1)`)
    	 */
    	const node3NewExpression = t.newExpression(t.identifier('Function'), [
    		t.identifier('"p1"'),
    		t.identifier('"p2"'),
    		t.stringLiteral('console.log(' + "'=====> foo'" + ', p1)'),
    	])
    	const funcNode3 = t.variableDeclaration('const', [
    		t.variableDeclarator(t.identifier('foo'), node3NewExpression),
    	])

    这种方法在我们的日常需求中出现的次数几乎是没有,但是它高频出现在面试题。

​ 一个老朋友new关键字,其他的都是一些字面量,我们只需要对Function对象进行实例化即可。

  1. 箭头函数

    javascript
    	/**
    	 * @NOTE  箭头函数
    	 * var foo = () => { console.log('======> foo') }
    	 */
    	const node4ArrowFunc = t.arrowFunctionExpression(
    		[t.identifier('p1'), t.identifier('t2')],
    		t.blockStatement([
    			t.expressionStatement(
    				t.callExpression(
    					t.memberExpression(t.identifier('console'), t.identifier('log')),
    					[t.stringLiteral('=====> foo'), t.identifier('p1')]
    				)
    			),
    		])
    	)
    	const funcNode4 = t.variableDeclaration('const', [
    		t.variableDeclarator(t.identifier('foo'), node4ArrowFunc),
    	])

    在ES6中出现了一个特殊函数,不带上下文的箭头函数,它的出现极大地解决了this指向性问题。为了与普通函数对象区分,types准备了另外一个声明箭头函数的方法arrowFunctionExpression

    除了换了一个声明方式之外,还有该方法移除了函数名的具名入参,其他的与普通函数无异议。

  2. 异步箭头函数

    javascript
      /**
    	 * @NOTE 实现await 异步箭头函数
    	 * var foo = async () => {
    	 *  await console.log('=====> foo')
    	 * }
    	 */
    	const arrowAwaitFunc = t.arrowFunctionExpression(
    		[t.identifier('p1'), t.identifier('p2')],
    		t.blockStatement([
    			t.expressionStatement(
    				t.awaitExpression(
    					t.callExpression(
    						t.memberExpression(t.identifier('console'), t.identifier('log')),
    						[t.stringLiteral('======> foo', t.identifier('p1'))]
    					)
    				)
    			),
    		]),
    		true
    	)
    	const funcNode5 = t.variableDeclaration('const', [
    		t.variableDeclarator(t.identifier('foo'), arrowAwaitFunc),
    	])

    这里出现了新的节点awaitExpressionawait表达式,没错就是为了搭配async函数的关键字。

    使用方法很简单就是在callExpression函数执行表达式外包一层,我们可以从源代码就看出来从属关系,await关键字一定出现在 执行语句的之前。

    arrowFunctionExpress函数提供的第三个入参的位置,值类型为Boolean类型,其目的就是为了控制函数是否是异步函数。

    functionDecalaration函数也提供了相同的功能,但是略微有点差异。我们都知道async/await关键字其实是一对语法糖,其实现核心其实是generator函数,所以普通函数进行异步其实质就是generator/yield来实现的异步。

    functionDecalaration中,第四个入参值类型为Boolean,用于控制是否异步函数。同时还有第五个入参,值类型为Boolean,用于控制是否使用await语法糖,若为false,那么函数即以generator函数的标记*,如下面例子。

  3. 异步函数

    javascript
    /**
     * @NOTE 使用函数声明,实现异步函数
     */
    const funcNode6 = t.functionDeclaration(
    	t.identifier('foo'),
    	[t.identifier('p1')],
    	t.blockStatement([
    		t.expressionStatement(
    			t.awaitExpression(
    				t.callExpression(
    					t.memberExpression(t.identifier('console'), t.identifier('log')),
    					[t.identifier('p1')]
    				)
    			)
    		),
    	]),
    	false,
    	true
    )
  4. 对象属性函数

    javascript
    /**
    	 * @NOTE 在对象中声明函数
    	 *
    	 * var obj = {
    	 *  say (p1) {
    	 *    console.log('=====> foo', p1)
    	 *  }
    	 * }
    	 */
    	const objNode1 = t.variableDeclaration('const', [
    		t.variableDeclarator(
    			t.identifier('obj'),
    			t.objectExpression([
    				t.objectProperty(
    					t.identifier('say'),
    					t.functionExpression(
    						null,
    						[t.identifier('p1')],
    						t.blockStatement([
    							t.expressionStatement(
    								t.callExpression(
    									t.memberExpression(
    										t.identifier('console'),
    										t.identifier('log')
    									),
    									[t.identifier('p1')]
    								)
    							),
    						])
    					)
    				),
    			])
    		),
    	])

    Object章类似的用法,之前在Object里没有添加函数属性,这里是一个补充。就是给对象加一个类型为函数的属性。

  5. 使用原型链

    javascript
    	/**
    	 * @NOTE 使用构造函数的原型链
    	 *
    	 * function Foo() {}
    	 * Foo.prototype = function (p1) {
    	 *   console.log(p1);
    	 * }
    	 */
    	const funcNode7 = t.functionDeclaration(
    		t.identifier('Foo'),
    		[],
    		t.blockStatement([])
    	)
    	const node7Statement = t.assignmentExpression(
    		'=',
    		t.memberExpression(t.identifier('Foo'), t.identifier('prototype')),
    		t.functionExpression(
    			null,
    			[t.identifier('p1')],
    			t.blockStatement([
    				t.expressionStatement(
    					t.callExpression(
    						t.memberExpression(t.identifier('console'), t.identifier('log')),
    						[t.identifier('p1')]
    					)
    				),
    			])
    		)
    	)

    assignentExpress赋值表达式,之前已经出现和使用过了,看到=号就必须使用它。

  6. Class类中的函数属性

    javascript
    	/**
    	 * @NOTE 利用Class的属性,声明一个函数
       * 
       * class Foo {
       *  say (p1) {
       *    console.log('=======> say', p1)
       *  }
       * }
    	 */
    	const classBody = t.classBody([
    		t.classMethod(
    			'method',
    			t.identifier('say'),
    			[t.identifier('p1')],
    			t.blockStatement([
    				t.expressionStatement(
    					t.callExpression(
    						t.memberExpression(t.identifier('console'), t.identifier('log')),
    						[t.identifier('p1')]
    					)
    				),
    			])
    		),
    	])
    	const funcNode8 = t.file(
    		t.program([t.classDeclaration(t.identifier('Foo'), null, classBody)])
    	)

    终于出现了新的函数classDeclarationclassBodyclassMethod。从函数名大致上就可以看出来它们是分别代表了创建类中属性的函数。

    这里有一个需要注意的问题,在最后生成节点后,需要使用t.file(t.program[])函数将生成的类的上下文关联到ast中。

  7. IIFE 立即执行函数

    javascript
      /**
       * @NOTE  使用IIFE构建函数
       * 
       * (function () {
       *  console.log(this)
       * })(window)
       */
      const funcNode9 = t.expressionStatement(t.callExpression(t.functionExpression(
        null,
        [],
        t.blockStatement([
          t.expressionStatement(
            t.callExpression(
              t.memberExpression(t.identifier('console'), t.identifier('log')),
              [t.thisExpression()]
            )
          )
        ])
      ), [t.identifier('window')]))

    IIFE立即执行函数,一个特别的函数,用于隔离作用域,防止变量污染。

    它很特殊,外层就是一个expressionStatement表达式语句,就是最外层的()括号。括号内部就是一个函数表达式。

源码地址:

  1. 新增Function类型节点: https://github.com/stack-wuh/mini-cli/blob/main/core/create-function-express/babel.js

MIT License.