Skip to content

插入Object类型的节点

Object数据类型,在js中是一个极其特殊的存在。在js中存在着一些分歧,那就是JS到底是面向对象还是基于对象

Array类型一样,Object也是一个复杂引用类型,我们可以再次回忆一下,创建一个对象有几种方法。

  1. 字面量声明

    javascript
    var obj1 = { 
      name: 'shadow'
    }
  2. 通过new关键字

    javascript
    var obj2 = new Object({ name: 'shadow' })
  3. 通过ES6的create方法

    javascript
    var obj3 = Object.create({ name: 'shadow' })
  4. 通过工厂函数

    javascript
    function createObject (o) {
      function F () {}
      F.prototype = o
      retunr new F()
    }
    
    var obj4 = createObject({ name: 'shadow' })
  5. ES6中的特殊对象 Set

    javascript
    var obj5 = new Set()
  6. ES6的特殊对象 Map

    javascript
    var obj6 = new Map()
    
    obj6.set('a', 1)

我们可以看到的,在这里出现了很多新的语句,比如在第四个例子中的赋值语句return语句,在第六个例子中出现的map.set函数表达式。

从代码的实现上看,我们就可以明显的看到里面多了很多前面没有用到的语句,下面是实现的代码的实例。

  1. 字面量创建,利用objectExpress对象表达式和objectProperty属性表达式

    javascript
      /**
       * @NOTE 字面量声明对象
       * 
       * @example
       * var obj1 = { name: 'shadow' }
       */
      const obj1Node = t.objectExpression([
        t.objectProperty(t.identifier('name'), t.stringLiteral('shadow'))
      ])
      const obj1 = t.variableDeclaration('const', [
        t.variableDeclarator(t.identifier('obj1'), obj1Node)
      ])

    只需要满足AST对于对象类型的解析规则,就可以随意添加节点类型。对于一个普通对象,对象的KEY值类型必须是字符串,所以对应在obejctProperty里既可以使用identifier直接量声明KEY,也可以使用stringLiteral声明KEY。

    对于VALUE部分,就可以是任意类型,我现在添加的就是一个字符串节点,用stringLiteral声明了一个值为"shadow"的节点。

  2. new关键字创建,这种方法在创建Array时已经使用过了,通过AST我们可以很清晰将new关键字的内部机制了解清楚

    javascript
      /**
       * @NOTE new关键字创建对象
       * @example
       * var obj2 = new Object({ name: 'shadow' })
       */
      const obj2CallNode = t.newExpression(t.identifier('Object'), [
        t.objectExpression([
          t.objectProperty(t.identifier('name'), t.stringLiteral('shadow'))
        ])
      ])
      const obj2 = t.variableDeclaration('const', [
        t.variableDeclarator(t.identifier('obj2'), obj2CallNode)
      ])

    可以依据代码中出现的语句顺序,依次解析语法结构,手动将其映射为AST语法。obj2CallNode其实就是new Obejct({ name: 'shadow' })部分的重构,第一我们需要使用newExpressionnew表达式,代码中函数有入参,入参类型是对象类型,所以还是可以继续按照上面方式,创建一个Object类型的节点。

    var obj2 = new Object({ name: 'shadow' }),一旦源代码出现了声明语句,就必须使用variableDeclaration来声明。

  3. 使用Object.create函数构建,这里涉及到函数调用,所以重构的语句中必须出现callExpression表达式

    javascript
      /**
       * @NOTE  通过Object.create 方法
       * @example
       * var obj3 = Object.create({ name; 'shadow' })
       */
      const obj3CallNode = t.callExpression(
        t.memberExpression(t.identifier('Object'), t.identifier('create')),
        [t.objectExpression([
          t.objectProperty(t.identifier('name'), t.stringLiteral('shadow'))
        ])]
      )
      const obj3 = t.variableDeclaration('const', [
        t.variableDeclarator(t.identifier('obj3'), obj3CallNode)
      ])
  4. 使用工厂函数构建,这里遇到一个新的声明语句,函数的声明functionDelcaration,除此之外还有块语句blockStatemtn以及returnStatementreturn语句

    javascript
      /**
       * @NOTE 使用工厂函数创建对象
       * @example
       * function createObject (o) {
       *    function F() {}
       *    F.prototype = o
       *    return new F()
       * }
       * var obj4 = createObject({ name: 'shadow' })
       */
      const obj4Func = t.functionDeclaration(
        t.identifier('createObject'),
        [t.identifier('o')],
        t.blockStatement([
          t.functionDeclaration(t.identifier('F'), [], t.blockStatement([])),
          t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.identifier('F'), t.identifier('prototype')), t.identifier('o'))),
          t.returnStatement(t.newExpression(t.identifier('F'), []))
        ])
      )
      const obj4Call = t.callExpression(t.identifier('createObject'), [
        t.objectExpression([
          t.objectProperty(t.identifier('name'), t.stringLiteral('shadow'))
        ])
      ])
      const obj4 = t.variableDeclaration('const', [
        t.variableDeclarator(t.identifier('obj4'), obj4Call)
      ])

    memberExpression成员表达式是一个比较熟悉的,在Array那部分就已经接触过了,形如obj.name或者是arr[0]出现了.号都需要使用成员表达式。

    returnStatementreturn语句,我们经常会遇到的场景,返回一个方法、一个对象、一个Boolean值或者是数组,这个场景遇到的就是返回一个函数表达式。

    functionDeclaration函数声明,我们对一个普通函数做结构操作function foo (...props) {}并不会报错,其原因就是函数有一个隐藏类数组对象arguments,与之对应,functionDeclaration第一个入参作用是给函数命名,第二个就是其入参的位置。

    随后就是函数体部分的抽象,{}遇到这种符号,其实就是块语句,那就必须使用blockStatement函数来抽象,这个函数的入参是数组类型,里面就是具体的源代码的抽象。在上面例子中,我们先抽象了一个空函数F。然后遇到了赋值语句assignmentExpression

    最后,就是一句赋值语句var obj4 = createObject({ name: 'shadow' })。前面说过了,遇到了函数执行就必须使用callExpression表达式,凡是有声明语句var obj4就必须使用variableDeclaration

  5. 创建Set对象,继续使用newExpression表达式

    javascript
      /**
       * @NOTE 使用Set创建对象
       * @NOTE 其实就是使用new 关键字 var s = new Set()
       */
      const obj5New = t.newExpression(t.identifier('Set'), [
        t.arrayExpression([
          t.numericLiteral(1),
          t.numericLiteral(2)
        ])
      ])
      const obj5 = t.variableDeclaration('const', [
        t.variableDeclarator(t.identifier('obj5'), obj5New)
      ])

    这一段就没必要说些什么了吧,出现的api都是一些熟面孔,只需要按照自己的需求设值就好了。

  6. 创建Map对象,继续使用newExpression表达式,出现了一个新面孔expressionStatement表达式语句

    javascript
      /**
       * @NOTE 使用Map创建对象
       * @NOTE 其实质就是使用new 关键字 var m = new Map()
       * @example
       * var m = new Map()
       * m.set('a', 1)
       */
      const obj6New = t.newExpression(t.identifier('Map'), [])
      const obj6 = t.variableDeclaration('const', [
        t.variableDeclarator(t.identifier('m'), obj6New)
      ])
      const obj6Assign = t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('m'), t.identifier('set')), [
        t.stringLiteral('a'),
        t.numericLiteral(1)
      ]))

    这里又出现了一个面孔,expressionStatement表达式语句。出现的原因就是m.set('a', 1),它是一个语句statement,同时它又有调用函数的操作,所以它也有expression表达式属性。

总结:

  1. Object在js数据类型中属于复杂类型,它的创建方式多种多样
  2. 新认识了一些新的API,用于配合使用创建符合要求的节点类型,遇到新的没碰到过的语句,完全可以根据语句的语义分段构建AST语句

源码地址:

  1. babel创建Object节点: https://github.com/stack-wuh/mini-cli/blob/main/core/create-object-babel/babel.js

MIT License.