Vue2源码解析 代码生成器

目录

1  介绍

2  通过AST生成代码字符串

3  代码生成器的原理

3.1  元素节点

3.2  文本节点

3.3  注释节点

4  总结


1  介绍

代码生成器是模板编译的最后一步,它的作用是将AST转换成渲染函数中的内容,这个内容可以称为代码字符串

代码字符串可以被包装在函数中执行,这个函数就是渲染函数。

渲染函数被执行之后,可以生成一份VNode,而虚拟dom可以通过这个VNode来渲染视图。

例子:

<div  id="el"> Hello  {{ name }} </div>

转换成AST并且经过优化之后:

{
  type:1,
  tag:"div",
  attrsList:[{
    name:"id",
    value:"el"
  }],
  attrsMap:{
    id:"el"
  },
  children:[{
    type:2,
    expression:'"Hello " + _s(name)',
    text:'Hello {{name}}',
    static:false
  }],
  plain:false,
  attrs:[{
    name:"id",
    value:'"el"'
  }],
  static:false,
  staticRoot:false
}

代码生成器可以通过AST来生成代码字符串,结果如下:

`with(this){
  return _c(
    "div",
    {
      attrs:{id:'el'},
    },
    [
      _v("Hello " + _s(name))
    ]
  )
}`

创建虚拟节点方法的三个参数分别是:

        1.标签名;

        2.一个包含模板相关属性的数据对象;

        3.子节点列表;

渲染函数其实是执行了createElement,而createElement可以创建一个VNode。

2  通过AST生成代码字符串

生成字符串是一个递归的过程,从顶向下依次处理每一个AST节点。

节点有三种类型:

        类型:元素节点  , 创建方法: createElement , 别名: _c;

        类型:文本节点  , 创建方法:createTextVNode,别名: _v;

        类型:注释节点  , 创建方法: createEmptyVNode ,别名:_e;

例如:

<div id="el">
    <div>
      <p>Hello {{name}}</p>
    </div>
</div>

插入dom的结构图:

 上面的整段代码会被包裹在with语句中,伪代码如下:

`with(this){ return ${code} }`

3  代码生成器的原理

节点有不同的类型,例如元素节点、文本节点和注释节点。

不同类型的生成方式是不一样,下面我们将分别介绍生成每个类型的节点。

3.1  元素节点

生成元素节点,其实就是生成一个_c的函数调用字符串,代码如下:

function genElement(el,state){
  // 如果el.plain是true,则说明节点没有属性
  const data = el.plain ? undefined : genData(el,state)
  const children = genChildren(el,state)
  code = `_c('${el.tag}'${
    data ? `,${data}` : "" // data
  }${
    children ? `,${children}` : "" // children
  })`
  return code
}

代码中el的plain属性是在编译时发现的。如果节点没有属性,就会把plain设置为true。这里我们可以通过plain来判断是否需要获取节点得属性数据。

代码中得主要逻辑是用genData和genChildren分别获取data和children,然后将它们分别拼到字符串中指定得位置。

genData代码如下:

function genData(el:ASTElement,state:CodegenState){
  let data = "{"
  // key
  if(el.key){
    data += `key:${el.key},`
  }
  // ref
  if(el.ref){
    data += `ref:${el.ref},`
  }
  // pre
  if(el.pre){
    data += `pre:true,`
  }
  // 类似得还有很多情况
  data = data.replace(/,$/,'') + '}'
  return data
}

其实也是拼字符串。先给data赋值一个'{',然后发现节点存在哪些属性数据,就将这些数据拼接到data中,最后拼接一个'}',此时一个完整得data就拼好了。

genChildren代码:

function genChildren(el,state){
  const children = el.children
  if(children.length){
    return `[${children.map(c=>genNode(c,state)).join(",")}]`
  }
  function genNode(node,state){
    if(node.type ===1){
      return genElement(node,state)
    } else if(node.type === 3 && node.isComment){
      return genComment(node)
    }else{
      return genText(node)
    }
  }
}

3.2  文本节点

genText函数实现代码:

function genText(text){
  return `_v(${text.type === 2
    ? text.expression
    : JSON.stringify(text.text) })`
}

如果是动态文本,则使用expression;如果是静态文本,则使用text。

3.3  注释节点

代码如下:

function genComment(comment){
  return `_e${JSON.stringify(comment.text)}`
}

4  总结

代码生成器的作用及其内部原理,了解了代码生成器其实就是字符串拼接的过程。通过递归AST来生成字符串,最先生成根节点,然后在子节点字符串生成后,将其拼接在根节点的参数中,子节点的子节点拼接在子节点的参数中,这样一层一层地拼接,直到最后拼接成完整的字符串。

最后,当字符串拼接好后,会将字符串拼在with中返回给调着用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值