vue源碼探究(第四彈)

vue源碼探究(第四彈)

結(jié)束了上一part的數(shù)據(jù)代理容劳,這一部分主要講講vue的模板解析,感覺這個(gè)有點(diǎn)難理解闸度,而且內(nèi)容有點(diǎn)多竭贩,hhh。

模板解析

廢話不多說莺禁,先從簡單的入手留量。

按照之前的套路,先舉一個(gè)例子??:

<div id="test">
  <p>{{name}}</p>
</div>
<script type="text/javascript" src="js/mvvm/compile.js"></script>
<script type="text/javascript" src="js/mvvm/mvvm.js"></script>
<script type="text/javascript" src="js/mvvm/observer.js"></script>
<script type="text/javascript" src="js/mvvm/watcher.js"></script>
<script type="text/javascript">
  new MVVM({
    el: '#test',
    data: {
      name: '喵喵喵'
    }
  })
  // 這時(shí)候哟冬,我們的頁面還是渲染出 喵喵喵
</script>

接下來講講內(nèi)部的相關(guān)實(shí)現(xiàn):

我們的MVVM中的構(gòu)造函數(shù)中有什么東西楼熄,可以解析我們的模板呢?

// 創(chuàng)建一個(gè)用來編譯模板的compile對(duì)象
this.$compile = new Compile(options.el || document.body, this)

什么是Compile浩峡?

一行一行注釋著解讀

function Compile(el, vm) {
  // 保存vm
  this.$vm = vm;
  // 保存el元素
  this.$el = this.isElementNode(el) ? el : document.querySelector(el);
  // 如果el元素存在
  if (this.$el) {
    // 1. 取出el中所有子節(jié)點(diǎn), 封裝在一個(gè)framgment對(duì)象中
    // 這里的node2Fragment 就是將node -> 放入 Fragment中可岂,documentFragment將node進(jìn)行批量處理
    this.$fragment = this.node2Fragment(this.$el);
    // 2. 編譯fragment中所有層次子節(jié)點(diǎn)
    this.init();
    // 3. 將fragment添加到el中
    this.$el.appendChild(this.$fragment);
  }
}

Compile.prototype = {
  node2Fragment: function (el) {
    var fragment = document.createDocumentFragment(),
      child;

    // 將原生節(jié)點(diǎn)拷貝到fragment
    while (child = el.firstChild) {
      fragment.appendChild(child);
    }

    return fragment;
  },

  init: function () {
    // 編譯fragment
    this.compileElement(this.$fragment);
  },

  compileElement: function (el) {
    // 得到所有子節(jié)點(diǎn)
    var childNodes = el.childNodes,
      // 保存compile對(duì)象
      me = this;
    // 遍歷所有子節(jié)點(diǎn)
    [].slice.call(childNodes).forEach(function (node) {
      // 得到節(jié)點(diǎn)的文本內(nèi)容
      var text = node.textContent;
      // 正則對(duì)象(匹配大括號(hào)表達(dá)式)
      var reg = /\{\{(.*)\}\}/;  // {{name}}
      // 這里提出一個(gè)問題,為什么這里的正則匹配要用/\{\{(.*)\}\}/翰灾,而不是/\{\{.*\}\}/呢缕粹?
      // 其實(shí)/\{\{.*\}\}/就可以匹配到{{xxx}},這里加一個(gè)()的意義是稚茅,用于.$1,來取得{{}}中的值平斩,eg:name
      // 如果是元素節(jié)點(diǎn)
      if (me.isElementNode(node)) {
        // 編譯元素節(jié)點(diǎn)的指令屬性
        me.compile(node);
        // 如果是一個(gè)大括號(hào)表達(dá)式格式的文本節(jié)點(diǎn)
      } else if (me.isTextNode(node) && reg.test(text)) {
        // 編譯大括號(hào)表達(dá)式格式的文本節(jié)點(diǎn)
        me.compileText(node, RegExp.$1); // RegExp.$1: 表達(dá)式   name
      }
      // 如果子節(jié)點(diǎn)還有子節(jié)點(diǎn)
      if (node.childNodes && node.childNodes.length) {
        // 遞歸調(diào)用實(shí)現(xiàn)所有層次節(jié)點(diǎn)的編譯
        me.compileElement(node);
      }
    });
  },

  compile: function (node) {
    // 得到所有標(biāo)簽屬性節(jié)點(diǎn)
    var nodeAttrs = node.attributes,
      me = this;
    // 遍歷所有屬性
    [].slice.call(nodeAttrs).forEach(function (attr) {
      // 得到屬性名: v-on:click
      var attrName = attr.name;
      // 判斷是否是指令屬性
      if (me.isDirective(attrName)) {
        // 得到表達(dá)式(屬性值): test
        var exp = attr.value;
        // 得到指令名: on:click
        var dir = attrName.substring(2);
        // 事件指令
        if (me.isEventDirective(dir)) {
          // 解析事件指令
          compileUtil.eventHandler(node, me.$vm, exp, dir);
        // 普通指令
        } else {
          // 解析普通指令
          compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
        }

        // 移除指令屬性
        node.removeAttribute(attrName);
      }
    });
  },

  compileText: function (node, exp) {
    // 調(diào)用編譯工具對(duì)象解析
    compileUtil.text(node, this.$vm, exp);
  },

  isDirective: function (attr) {
    return attr.indexOf('v-') == 0;
  },

  isEventDirective: function (dir) {
    return dir.indexOf('on') === 0;
  },

  isElementNode: function (node) {
    return node.nodeType == 1;
  },

  isTextNode: function (node) {
    return node.nodeType == 3;
  }
};

// 指令處理集合
var compileUtil = {
  // 解析: v-text/{{}}
  text: function (node, vm, exp) {
    this.bind(node, vm, exp, 'text');
  },
  // 解析: v-html
  html: function (node, vm, exp) {
    this.bind(node, vm, exp, 'html');
  },

  // 解析: v-model
  model: function (node, vm, exp) {
    this.bind(node, vm, exp, 'model');

    var me = this,
      val = this._getVMVal(vm, exp);
    node.addEventListener('input', function (e) {
      var newValue = e.target.value;
      if (val === newValue) {
        return;
      }

      me._setVMVal(vm, exp, newValue);
      val = newValue;
    });
  },

  // 解析: v-class
  class: function (node, vm, exp) {
    this.bind(node, vm, exp, 'class');
  },

  // 真正用于解析指令的方法
  bind: function (node, vm, exp, dir) {
    /*實(shí)現(xiàn)初始化顯示*/
    // 根據(jù)指令名(text)得到對(duì)應(yīng)的更新節(jié)點(diǎn)函數(shù)
    // 取到一個(gè)object的屬性亚享,有2個(gè)方法,一個(gè)是obj. 一個(gè)是obj[]
    // 當(dāng)我們要取得屬性是一個(gè)變量的時(shí)候绘面,使用obj[]
    var updaterFn = updater[dir + 'Updater'];
    // 如果存在調(diào)用來更新節(jié)點(diǎn)
    updaterFn && updaterFn(node, this._getVMVal(vm, exp));

    // 創(chuàng)建表達(dá)式對(duì)應(yīng)的watcher對(duì)象
    new Watcher(vm, exp, function (value, oldValue) {/*更新界面*/
      // 當(dāng)對(duì)應(yīng)的屬性值發(fā)生了變化時(shí), 自動(dòng)調(diào)用, 更新對(duì)應(yīng)的節(jié)點(diǎn)
      updaterFn && updaterFn(node, value, oldValue);
    });
  },

  // 事件處理
  eventHandler: function (node, vm, exp, dir) {
    // 得到事件名/類型: click
    var eventType = dir.split(':')[1],
      // 根據(jù)表達(dá)式得到事件處理函數(shù)(從methods中): test(){}
      fn = vm.$options.methods && vm.$options.methods[exp];
    // 如果都存在
    if (eventType && fn) {
      // 綁定指定事件名和回調(diào)函數(shù)的DOM事件監(jiān)聽, 將回調(diào)函數(shù)中的this強(qiáng)制綁定為vm
      node.addEventListener(eventType, fn.bind(vm), false);
    }
  },

  // 得到表達(dá)式對(duì)應(yīng)的value
  _getVMVal: function (vm, exp) {
    // 這里為什么要forEach呢虹蒋?
    // 如果你的exp是a.b.c.c.d呢 就需要forEach 如果只是一層 當(dāng)然不需要遍歷啦
    var val = vm._data;
    exp = exp.split('.');
    exp.forEach(function (k) {
      val = val[k];
    });
    return val;
  },

  _setVMVal: function (vm, exp, value) {
    var val = vm._data;
    exp = exp.split('.');
    exp.forEach(function (k, i) {
      // 非最后一個(gè)key,更新val的值
      if (i < exp.length - 1) {
        val = val[k];
      } else {
        val[k] = value;
      }
    });
  }
};

// 包含多個(gè)用于更新節(jié)點(diǎn)方法的對(duì)象
var updater = {
  // 更新節(jié)點(diǎn)的textContent
  textUpdater: function (node, value) {
    node.textContent = typeof value == 'undefined' ? '' : value;
  },

  // 更新節(jié)點(diǎn)的innerHTML
  htmlUpdater: function (node, value) {
    node.innerHTML = typeof value == 'undefined' ? '' : value;
  },

  // 更新節(jié)點(diǎn)的className
  classUpdater: function (node, value, oldValue) {
    var className = node.className;
    className = className.replace(oldValue, '').replace(/\s$/, '');

    var space = className && String(value) ? ' ' : '';

    node.className = className + space + value;
  },

  // 更新節(jié)點(diǎn)的value
  modelUpdater: function (node, value, oldValue) {
    node.value = typeof value == 'undefined' ? '' : value;
  }
};

最后

未完待續(xù)...
接下來飒货,還有一個(gè)更有趣的東西

下一章繼續(xù)~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市峭竣,隨后出現(xiàn)的幾起案子塘辅,更是在濱河造成了極大的恐慌,老刑警劉巖皆撩,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扣墩,死亡現(xiàn)場離奇詭異,居然都是意外死亡扛吞,警方通過查閱死者的電腦和手機(jī)呻惕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滥比,“玉大人亚脆,你說我怎么就攤上這事∶し海” “怎么了濒持?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寺滚。 經(jīng)常有香客問我柑营,道長,這世上最難降的妖魔是什么村视? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任官套,我火速辦了婚禮,結(jié)果婚禮上蚁孔,老公的妹妹穿的比我還像新娘奶赔。我一直安慰自己,他們只是感情好勒虾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布纺阔。 她就那樣靜靜地躺著,像睡著了一般修然。 火紅的嫁衣襯著肌膚如雪笛钝。 梳的紋絲不亂的頭發(fā)上质况,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音玻靡,去河邊找鬼结榄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛囤捻,可吹牛的內(nèi)容都是我干的臼朗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蝎土,長吁一口氣:“原來是場噩夢啊……” “哼视哑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起誊涯,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤挡毅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后暴构,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跪呈,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年取逾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耗绿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡砾隅,死狀恐怖误阻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晴埂,我是刑警寧澤堕绩,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站邑时,受9級(jí)特大地震影響奴紧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晶丘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一黍氮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浅浮,春花似錦沫浆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至郁油,卻和暖如春本股,著一層夾襖步出監(jiān)牢的瞬間攀痊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工拄显, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苟径,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓躬审,卻偏偏與公主長得像棘街,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子承边,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,077評(píng)論 25 707
  • 用兩張圖告訴你遭殉,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,714評(píng)論 2 59
  • 精要 名字的流行趨勢博助,可以告訴我們時(shí)尚動(dòng)力學(xué)恩沽。 如果老百姓想穿得起和貴族差不多的衣服,貴族必須得變著花樣換新款式的...
    fengtasy閱讀 340評(píng)論 0 0
  • 這是2018年8月12日(周日)“崔律·100天精力和時(shí)間管理訓(xùn)練營”第1.7講的課后實(shí)踐翔始。 <實(shí)踐事項(xiàng)> 回顧本...
    QueenaLuo閱讀 121評(píng)論 0 0
  • 避世常居空谷,悠悠漸忘流年里伯。吟風(fēng)對(duì)月傍石眠城瞎。叢生春韭綠,花綻粉蝶翩疾瓮。 我自心儀神韻脖镀,偏...
    魯筆生花閱讀 411評(píng)論 4 2