vue渲染流程

  • 把模板轉(zhuǎn)化成render函數(shù)
  • 調(diào)用render函數(shù)產(chǎn)生虛擬節(jié)點,將虛擬節(jié)點插入到真實節(jié)點上
  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函數(shù)方法: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) {
      //如果是開始標簽桌粉,需要截取掉前面的字符串,并獲取到匹配到的數(shù)據(jù)
      const match = {
        tagName: start[1],
        attrs: [],
      };
      advance(start[0].length);
      let end;
      //如果沒有遇到結(jié)束標簽就不停的解析
      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) {
    //看要解析的內(nèi)容是否存在鳞青,如果存在就不停的解析
    let textEnd = html.indexOf("<"); //可能是開始標簽霸饲,也可能是結(jié)束標簽
    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字符串解析成對應(yīng)的腳本來觸發(fā) 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函數(shù)->虛擬dom(增減額外的屬性)->生成真實的dom
}
創(chuàng)建虛擬節(jié)點方法:createElm
export function patch(oldVnode,vnode){//新的虛擬節(jié)點替換掉老的節(jié)點胶惰,先插入傻工,后刪除
    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;//首次渲染時將老節(jié)點刪除掉,會導(dǎo)致再次更新數(shù)據(jù)孵滞,獲取不到老節(jié)點中捆,所以沒辦法插入,所以每次更新老節(jié)點
    }
}
function createComponent(vnode){
    let i = vnode.data; 
    if((i = i.hook) && (i = i.init)){
        i(vnode);//調(diào)用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)){
            //返回組件對應(yīng)的真實節(jié)點
            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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市匿级,隨后出現(xiàn)的幾起案子蟋滴,更是在濱河造成了極大的恐慌,老刑警劉巖根蟹,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脓杉,死亡現(xiàn)場離奇詭異,居然都是意外死亡简逮,警方通過查閱死者的電腦和手機球散,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來散庶,“玉大人蕉堰,你說我怎么就攤上這事”辏” “怎么了屋讶?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長须教。 經(jīng)常有香客問我皿渗,道長斩芭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任乐疆,我火速辦了婚禮划乖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘挤土。我一直安慰自己琴庵,他們只是感情好,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布仰美。 她就那樣靜靜地躺著迷殿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪咖杂。 梳的紋絲不亂的頭發(fā)上庆寺,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音翰苫,去河邊找鬼止邮。 笑死,一個胖子當著我的面吹牛奏窑,可吹牛的內(nèi)容都是我干的导披。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼埃唯,長吁一口氣:“原來是場噩夢啊……” “哼撩匕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起墨叛,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤止毕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后漠趁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扁凛,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年闯传,在試婚紗的時候發(fā)現(xiàn)自己被綠了谨朝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡甥绿,死狀恐怖字币,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情共缕,我是刑警寧澤洗出,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站图谷,受9級特大地震影響翩活,放射性物質(zhì)發(fā)生泄漏阱洪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一菠镇、第九天 我趴在偏房一處隱蔽的房頂上張望澄峰。 院中可真熱鬧,春花似錦辟犀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至玻佩,卻和暖如春出嘹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咬崔。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工税稼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人垮斯。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓郎仆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親兜蠕。 傳聞我的和親對象是個殘疾皇子扰肌,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容