美文网首页
vue渲染流程

vue渲染流程

作者: 小码农_影 | 来源:发表于2021-11-22 20:11 被阅读0次
    • 把模板转化成render函数
    • 调用render函数产生虚拟节点,将虚拟节点插入到真实节点上
      let oldTemplate = `<div>{{message}}</div>`;
      let vm1 = new Vue({data:{message:'hello world'}});
      const render1 = compileToFunction(oldTemplate);
      const oldVnode = render1.call(vm1);//虚拟dom
      document.body.appendChild(createElm(oldVnode));
    
    生成render函数方法:compileToFunction
    //parse.js
    const attribute =
      /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; //属性正则
    const unicodeRegExp =
      /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;
    const dynamicArgAttribute =
      /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
    const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeRegExp.source}]*`; //标签名
    const qnameCapture = `((?:${ncname}\\:)?${ncname})`; //用来获取标签名的,match后索引为1的
    const startTagOpen = new RegExp(`^<${qnameCapture}`); //开始标签
    const startTagClose = /^\s*(\/?)>/; //<div/>
    const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); //闭合标签
    const doctype = /^<!DOCTYPE [^>]+>/i;
    // #7298: escape - to avoid being passed as HTML comment when inlined in page
    const comment = /^<!\--/;
    const conditionalComment = /^<!\[/;
    
    export function parseHtml(html) {
      let root = null;
      let stack = [];
      function advance(len) {
        html = html.substring(len);
      }
      function parseStartTag() {
        const start = html.match(startTagOpen);
        if (start) {
          //如果是开始标签,需要截取掉前面的字符串,并获取到匹配到的数据
          const match = {
            tagName: start[1],
            attrs: [],
          };
          advance(start[0].length);
          let end;
          //如果没有遇到结束标签就不停的解析
          let attr;
          while (
            !(end = html.match(startTagClose)) &&
            (attr = html.match(attribute))
          ) {
            match.attrs.push({
              name: attr[1],
              value: attr[3] || attr[4] || attr[5],
            });
            advance(attr[0].length);
          }
          if (end) {
            advance(end[0].length);
          }
          return match;
        } else {
          //说明不是开始标签
          return false;
        }
      }
      while (html) {
        //看要解析的内容是否存在,如果存在就不停的解析
        let textEnd = html.indexOf("<"); //可能是开始标签,也可能是结束标签
        if (textEnd == 0) {
          //说明存在,
          const startTagMatch = parseStartTag(html); //解析开始标签;
          if (startTagMatch) {
            start(startTagMatch.tagName, startTagMatch.attrs);
            continue;
          }
          const endTagMatch = html.match(endTag);
          if (endTagMatch) {
            end(endTagMatch[1]);
            advance(endTagMatch[0].length);
            continue;
          }
        }
        let text;
        if (textEnd > 0) {
          text = html.substring(0, textEnd);
        }
        if (text) {
          charts(text);
          advance(text.length);
        }
      } //html字符串解析成对应的脚本来触发 tokens <div id="app"></div>
      function createAstElement(tagName, attrs) {
        return {
          tag: tagName,
          children: [],
          attrs,
          type: 1,
          parent: null,
        };
      }
    
      function start(tagName, attributes) {
        let parent = stack[stack.length - 1];
        let element = createAstElement(tagName, attributes);
        if (!root) {
          root = element;
        }
        if (parent) {
          element.parent = parent; //当放入栈时,记录父亲是谁
          parent.children.push(element);
        }
        stack.push(element);
      }
      function end(tagName) {
        let last = stack.pop();
        if (last.tag != tagName) {
          throw new Error("标签闭合有误");
        }
      }
      function charts(text) {
        text = text.replace(/\s/g, "");
        let parent = stack[stack.length - 1];
        if (text) {
          parent.children.push({
            type: 3,
            text,
          });
        }
      }
      return root;
    }
    
    //generate.js
    const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;
    function getProps(el){//{[name:'id',value:app]}
        let str = ''
        el.forEach(attr=>{
          //修改style前_c('div'),{d:"app",class:"app",style:"color:red;background:blue"}
          if (attr.name == "style") {
            let styleObj = {};
            attr.value.replace(/([^:;]+):([^:;]+)/g, function () {
              styleObj[arguments[1]] = arguments[2];
            });
            attr.value = styleObj;
          }
          //修改style之后_c('div'),{d:"app",class:"app",style:{"color":"red","background":"blue"}}
          str += `${attr.name}:${JSON.stringify(attr.value)},`;
        })
        return `{${str.slice(0,-1)}}`
    }
    function gen(el){
      if(el.type == 1){
        return generate(el);
      }else{
        let text = el.text;
        if(!defaultTagRE.test(text)){
           return `_v("${text}")`
        }else{
          // 'hello' + arr + 'world' hello{{arr}}world
          let tokens = [];
          let match;
          let lastIndex = defaultTagRE.lastIndex = 0;
          while(match = defaultTagRE.exec(text)){//看有没有匹配到
            let index = match.index;
            if(index > lastIndex){
              tokens.push(JSON.stringify(text.slice(lastIndex,index)))
            }
            tokens.push(`_s(${match[1].trim()})`)
            lastIndex = index + match[0].length
          }
          if(lastIndex < text.length){
            tokens.push(JSON.stringify(text.slice(lastIndex)));
          }
          return `_v(${tokens.join('+')})`
        }
      }
    }
    function generateChildren(el){
      let children = el.children
      if(children){
        return children.map(c=>gen(c)).join(',')
      }
      return false
    }            
    export function generate(el){
      //遍历树,将树拼接成字符串
      let children  = generateChildren(el);
      let str = el.attrs.length>0?getProps(el.attrs):'undefined';
      let code = `_c('${el.tag}',${str}${children?`,${children}`:''})`;
      return code
    }
    
    
    //index.js
    import { generate } from "./generate";
    import { parseHtml } from "./parse";
    
    export function compileToFunction(el) {
      const root = parseHtml(el);
        //生成代码 _c('div',{id:'app',a:1},[text])
      let code = generate(root);
      let render = new Function (`with(this){return ${code}}`)
     
      return render
      //html->ast(只能描述语法,语法不存在的属性无法描述)->render函数->虚拟dom(增减额外的属性)->生成真实的dom
    }
    
    创建虚拟节点方法:createElm
    export function patch(oldVnode,vnode){//新的虚拟节点替换掉老的节点,先插入,后删除
        if(!oldVnode){
            return createElm(vnode)
        }
        if(oldVnode.nodeType == 1){
            const parent = oldVnode.parentNode;
            const newElm = createElm(vnode);
            parent.insertBefore(newElm, oldVnode.nextSibling);
            parent.removeChild(oldVnode);
            return newElm;//首次渲染时将老节点删除掉,会导致再次更新数据,获取不到老节点,所以没办法插入,所以每次更新老节点
        }
    }
    function createComponent(vnode){
        let i = vnode.data; 
        if((i = i.hook) && (i = i.init)){
            i(vnode);//调用init方法
        }
        if (vnode.componentInstance) {
          //有属性说明子组件new完毕了,并且组件的真实dom挂载到了vnode。componentInstance
          return true;
        }
    } 
    export function createElm(vnode){
        let {vm,tag,data,children,text} = vnode;
        if(typeof tag === 'string'){
            //判断是否是组件
            if( createComponent(vnode)){
                //返回组件对应的真实节点
                console.log(vnode.componentInstance.$el);
                return vnode.componentInstance.$el
            }
            vnode.el = document.createElement(tag);
            if(children.length){
                children.forEach(child=>{
                    vnode.el.appendChild(createElm(child));
                })
            }
        }else{
            vnode.el = document.createTextNode(text);
        }
        return vnode.el;
    }
    

    相关文章

      网友评论

          本文标题:vue渲染流程

          本文链接:https://www.haomeiwen.com/subject/pssvzltx.html