聊聊你对Vue.js的template编译的理解-,vue template详解

  聊聊你对Vue.js的template编译的理解?,vue template详解

  本文主要介绍了vue中模板编译过程的综合分析,具有很好的参考价值。希望对大家有帮助。如有错误或不足之处,请不吝赐教。

  

目录

  过程简述vue的渲染过程parseparse过程summary generate生成渲染函数

  

简述过程

  VuTemplate模板的编译过程通过parse()生成ast(抽象语法树),优化静态节点,通过generate()生成渲染字符串

  之后,调用新的Watcher()函数来监控数据的变化。数据监控的回调调用render函数,结果是重新生成vnode。

  当这个render函数字符串在挂载或绑定数据中第一次更新时,它将被调用来生成Vnode。

  如果是数据更新,那么Vnode会和数据改变前的Vnode不一样,改变内容后会更新到我们真正的DOM。

  

vue的渲染过程

  

parse

  在了解解析过程之前,我们需要了解AST。AST的全称是抽象语法树,也就是所谓的抽象语法树,用来表示代码的数据结构。

  在Vue中,我把它理解为一个嵌套的JS对象,它带有标签名、属性和父子关系。DOM结构用树来表示。

  vue中的ast类型有以下3种

  as element={//AST标记元素

  类型:1;

  标签:字符串;

  attrs list:Array { name:string;value:any };

  attrs map:{[key:string]:any };

  parent:as element void;

  儿童:ArrayASTNode

  .

  }

  as expression={//AST expression { { } }

  类型:2;

  表达式:字符串;

  文本:字符串;

  tokens:array string Object;

  静电?布尔型;

  };

  ASTText={//AST text

  类型:3;

  文本:字符串;

  静电?布尔型;

  isComment?布尔型;

  };

  由子字段形成逐层嵌套的树形结构。vue中定义了很多规则(判断标签、属性、vue指令、文本的开始和结束)。通过递归匹配html内容,满足条件的字符串被截取。将字符串类型的html转换为AST结构

  parse函数的作用就是把字符串型的template转化为AST结构

  例如,假设我们有一个元素

  解析后,文本将变成以下结构并返回:

  ele1={

  类型:1,

  标签:“div”,

  属性列表:[{名称: id ,值:测试 }],

  属性映射:{id:测试 },

  父:未定义,

  孩子:[{

  类型:3,

  文本:“文本文本”

  }

  ],

  平原:真的,

  属性:[{名称: id ,值: 测试 }]

  }

  那么具体是怎么分析拦截的呢?

  举个例子

  差异

  p我是{{name}}/p

  /div

  他的拦截过程主要如下

  //初始

  差异

  p我是{{name}}/p

  /div

  //截取第一次的剩余部分(包括空格)

  p我是{{name}}/p

  /div

  //第二次拦截

  p我是{{name}}/p

  /div

  //第三次拦截

  我是{{name}}/p

  /div

  //第四次拦截

  /p

  /div

  //

  /div

  //

  /div

  那么,他的截取规则是什么呢?

  vue中的拦截规则主要是通过判断模板中html.indexof( )的值来决定我们是要拦截标签还是文本。

  等于0:这意味着这是comments、conditional comments、doctype、start tag和end tag中的一个,大于等于0:这意味着text和expression小于0:这意味着html标签已经被解析,如果等于0,可能会留下一些文本和expression。

  如果它等于0,则执行常规匹配以查看它是否是开始标记、结束标记、注释、条件注释和doctype之一。如果是开始标记,截取对应的开始标记,定义ast的基本结构,分析属性(标记名)、指令等。在标签上。

  当然,这里的attrs也是有规则匹配的。具体方法是匹配标签上对应的属性,然后推送到attrs中。

  匹配时的正则表达式如下。

  常量属性=/^\s*([^\s\/=])(?\s*(=)\s*?([^]*) ([^]*) ([^\s=`]))))?/

  const NC name=[a-zA-Z _][\ \ w \ \-\ \。]*

  const qnameCapture=`((?${ncname}\\:)?$ { NC name })` 0

  const startTagOpen=新RegExp(`^${qnameCapture}`)

  const startTagClose=/^\s*(\/?)/

  const endTag=新regexp(`^\\/${qnamecapture}[^]*`)

  const doctype=/^!DOCTYPE [^] /i

  const comment=/^!\ - /

  const condition comment=/^!\[/

  同时需要注意的是,vue中还需要维护一个stack(可以理解为一个数组),用来标记DOM的深度是关于栈的。

  堆栈中的最后一项总是当前被解析元素的parentNode。

  通过栈解析器,将建立当前解析的元素与栈中最后一个元素的父子关系。即,将当前节点推送到堆栈最后一个节点的子节点,并将其自身的父节点设置为堆栈的最后一个节点。

  当然,因为我们的标签中有一个自闭症标签(比如input),这种类型的标签没有子元素,所以不会被推入堆栈。

  如果是结束标记,需要通过这个结束标记的标记名从后向前匹配堆栈中每一项的标记名,并删除匹配项之后的所有项,表示这段已经被解析。如果不是以上五种类型之一,则表示文本等于或大于0。

  如果等于0,不满足以上五个条件或者大于0,说明是文本或者表达式。

  此时,它会判断其余部分是否符合标签的格式。如果不符合,则继续判断在rest中的位置,继续判断1,直到rest的格式符合标签。

  let textEnd=html.indexOf( )

  让短信,休息,下一个

  if (textEnd=0) {

  rest=html.slice(textEnd)

  HTML的其余部分不符合标记的格式,所以它必须是文本

  //它仍然是以开头的文本

  而(

  !endTag.test(rest)

  !startTagOpen.test(rest)

  !注释.测试(rest)

  !条件注释测试(休息)

  ) {

  //在纯文本中,宽容一些,把它当作文本

  next=rest.indexOf( ,1)

  if(下一个0)中断

  textEnd=下一个

  rest=html.slice(textEnd)

  }

  text=html.substring(0,textEnd)

  html=html.substring(0,textEnd)

  }

  关于文本的截取

  一般有两种文字。

  Hard /div Im {{name}}/div如果文本包含表达式,需要解析文本中的变量。

  Constexpression=parseText (text,delimiters)//解析变量{{name}}=_s(name)

  children.push({

  类型:2,

  表情,

  文本

  })

  //经过上面例子的解析,形成下面的结构。

  {

  表达式:“_s(name)”,

  文字:“我是{{name}}”,

  类型:2

  }

  现在我们再来看最开始的例子

  差异

  p我是{{name}}/p

  /div

  1.首先,如果第一次判断的位置等于0,并且能匹配开始标签,就截取这个标签。

  //第一次截取后剩余

  p我是{{name}}/p

  /div

  2.继续判断位置,大于0(因为有空格),判断为文本,截取此文本。

  //第二次截取后剩余

  p我是{{name}}/p

  /div

  3.继续判断位置,等于0,是开始标签,截取这部分,维护堆栈,将当前解析元素的parnet设置为堆栈中的最后一项,将当前解析的元素推入堆栈中最后一项的子元素中。

  //这里有一个判断,因为非自闭症标签有孩子,所以非自闭症标签推入堆栈。

  如果(!一元){

  currentParent=元素

  stack.push(元素)

  }

  //建立父子关系

  currentParent.children.push(元素)

  element.parent=currentParent

  //此时堆栈

  [女主角,过去]

  //第三次截取后剩余

  我是{{name}}/p

  /div

  4.继续判断位置。如果该位置大于0,则判断剩余部分是否属于一种标签。如果剩余部分可以匹配结束标签,则表示为文本。

  //第四次截取后剩余

  /p

  /div

  5.继续判断位置,等于0,匹配结束标签。此时,我们将在堆栈中寻找满足与当前标记名相同的标记名的最后一个项目,并删除其后的所有项目。

  //此时堆栈

  [女主角]

  //第五次截取余数

  /div

  6.继续用上述方法拦截,直到全部拦截完成。

  

parse过程总结

  简单来说,template的解析过程其实就是不断截取字符串并解析的过程。

  在这个过程中,如果一个未结束的标签被拦截,它将被推入堆栈,如果结束标签被拦截,它将被弹出。

  optimize优化

  Optimize主要用于优化生成的AST的静态内容,标记静态节点。所谓静态内容是指和数据没有关系,不需要每次都更新的内容。

  标记静态节点的作用是确定dom diff后是否需要打补丁。Diff算法会直接跳过静态节点,从而减少比较过程,优化补丁的性能。

  1.如果是表达式AST节点,直接返回false2。2.如果是文本AST节点,直接返回true3。3.如果元素是元素节点,则在阶段1中有v-pre指令。没有指令、数据绑定、事件绑定等。

  2.没有v-if和v-for

  3.不是插槽和组件

  4.这是一个HTML保留标签

  5.如果它不是template标记的直接子元素,并且不包含在for循环中,则返回true。

  简单来说,不使用vue特有语法的节点可以称为静态节点。

  要判断一个父元素是静态节点,就要判断它的所有子元素都是静态节点,否则就不是静态节点。

  标记静态节点的过程是一个递归过程。

  for(设i=0,l=node . children . length;I l;i ) {

  const child=node.children[i]

  markStatic(子级)

  如果(!child.static) {

  节点.静态=假

  }

  }

  markStatic方法用于标记静态节点,它会不断循环子节点。如果孩子还有孩子,也会遵循同样的逻辑。所有这样的节点都将被标记。

  在循环中,会判断子节点是否为静态节点,如果不是,则其父节点不是静态节点。

  

generate生成render函数

  Generate是将AST转换成renderfunction字符串的过程。他递归地递归AST,结果是呈现的字符串。

  render函数是一个返回a _ c (tagname ,data,children)的方法。

  1.第一个参数是标签名。

  2.第二个参数是他的一些数据,包括属性/指令/方法/表达式等等。

  3.第三个参数是当前标签的子标签。同样,每个子标签的格式是_ c(标记名,数据,子标签)。

  Generate通过不断的递归形成了这样的树形结构。

  GenElement:用于生成基本的render结构或者createElement结构genData:用于处理ast结构的一些属性,用于生成datagenChildren:用于处理ast的子元素,内部调用genElement形成子元素的_c()方法render字符串内部有几种方法

  几种内部方法

  _c:对应于createElement方法。顾名思义就是创建一个元素(Vnode)_v:创建一个文本节点。_s:将值转换为字符串。(例如:{{data}})_m:呈现静态内容模板

  div id=应用程序

  {{val}}

  img src=http://xx.jpg

  /div

  /模板

  {

  render: with(this) {

  return _c(div ,{

  属性:{

  id :应用程序

  }

  },[_v(\n _s(val) \n ),

  _c(img ,{

  属性:{

  src :

  }

  })

  ]

  )

  }

  }

  那么问题来了,_ c(标记名,数据,子体)是怎么拼接的,数据是怎么拼接的,子体是怎么拼接的?

  //使用genElement方法拼接每个item _c(tagName ,data,children)

  函数gen element(El:as element,state: CodegenState) {

  const data=el.plain?未定义:genData(el,state)

  const children=El . inline template?null : genChildren(el,state,true)

  let code=`_c(${el.tag}${

  数据?`,${data}` : //数据

  }${

  孩子?`,${children}` : //孩子

  })`

  返回代码

  }

  从线路看数据的拼接逻辑。

  //

  函数genData(El:as element,state: CodegenState): string {

  让数据={

  //键

  if (el.key) {

  data=`key:${el.key},` 1

  }

  //ref

  if (el.ref) {

  data=`ref:${el.ref},` 0

  }

  if (el.refInFor) {

  data= refin for:true,

  }

  //.类似的情况还有很多。

  data=data.replace(/,$/, ) }

  返回数据

  }

  从上面可以看出,数据的拼接过程就是不断的解读ast上的一些属性是否存在,然后拼在数据上,最后返回这个数据。

  那么children怎么拼出来呢?

  函数genChildren(

  艾尔:作为一个元素,

  州:CodegenState

  ):string void {

  const儿童=el.children

  if (children.length) {

  return `[$ { children . map(c=gen node(c,state))。join(,)}]`

  }

  }

  函数genNode(节点:ASTNode,状态:CodegenState): string {

  if (node.type===1) {

  返回genElement(节点,状态)

  } if(node . type===3 node . is comment){

  返回genComment(节点)

  }否则{

  返回生成文本(节点)

  }

  }

  最后,通过执行render函数将形成虚拟DOM。

  以上个人经历,希望能给大家一个参考,也希望大家多多支持我们。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: