vue原始碼探究(第四彈)
vue原始碼探究(第四彈)
結束了上一part的資料代理,這一部分主要講講vue的模板解析,感覺這個有點難理解,而且內容有點多,hhh。
模板解析
廢話不多說,先從簡單的入手。
按照之前的套路,先舉一個例子:
<div id="test"> <p>{{name}}</p></div><script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/mvvm/compile.js"></script><script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/mvvm/mvvm.js"></script><script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/mvvm/observer.js"></script><script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/mvvm/watcher.js"></script><script type="text/javascript"> new MVVM({ el: '#test', data: { name: '喵喵喵' } }) // 這時候,我們的頁面還是渲染出 喵喵喵</script>
接下來講講內部的相關實現:
我們的MVVM中的建構函式中有什麼東西,可以解析我們的模板呢?
// 建立一個用來編譯模板的compile物件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中所有子節點, 封裝在一個framgment物件中 // 這裡的node2Fragment 就是將node -> 放入 Fragment中,documentFragment將node進行批次處理 this.$fragment = this.node2Fragment(this.$el); // 2. 編譯fragment中所有層次子節點 this.init(); // 3. 將fragment新增到el中 this.$el.appendChild(this.$fragment); }}Compile.prototype = { node2Fragment: function (el) { var fragment = document.createDocumentFragment(), child; // 將原生節點複製到fragment while (child = el.firstChild) { fragment.appendChild(child); } return fragment; }, init: function () { // 編譯fragment this.compileElement(this.$fragment); }, compileElement: function (el) { // 得到所有子節點 var childNodes = el.childNodes, // 儲存compile物件 me = this; // 遍歷所有子節點 [].slice.call(childNodes).forEach(function (node) { // 得到節點的文字內容 var text = node.textContent; // 正則物件(匹配大括號表示式) var reg = /{{(.*)}}/; // {{name}} // 這裡提出一個問題,為什麼這裡的正則匹配要用/{{(.*)}}/,而不是/{{.*}}/呢? // 其實/{{.*}}/就可以匹配到{{xxx}},這裡加一個()的意義是,用於.$1,來取得{{}}中的值,eg:name // 如果是元素節點 if (me.isElementNode(node)) { // 編譯元素節點的指令屬性 me.compile(node); // 如果是一個大括號表示式格式的文字節點 } else if (me.isTextNode(node) && reg.test(text)) { // 編譯大括號表示式格式的文字節點 me.compileText(node, RegExp.$1); // RegExp.$1: 表示式 name } // 如果子節點還有子節點 if (node.childNodes && node.childNodes.length) { // 遞迴呼叫實現所有層次節點的編譯 me.compileElement(node); } }); }, compile: function (node) { // 得到所有標籤屬性節點 var nodeAttrs = node.attributes, me = this; // 遍歷所有屬性 [].slice.call(nodeAttrs).forEach(function (attr) { // 得到屬性名: v-on:click var attrName = attr.name; // 判斷是否是指令屬性 if (me.isDirective(attrName)) { // 得到表示式(屬性值): 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) { // 呼叫編譯工具物件解析 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) { /*實現初始化顯示*/ // 根據指令名(text)得到對應的更新節點函式 // 取到一個object的屬性,有2個方法,一個是obj. 一個是obj[] // 當我們要取得屬性是一個變數的時候,使用obj[] var updaterFn = updater[dir + 'Updater']; // 如果存在呼叫來更新節點 updaterFn && updaterFn(node, this._getVMVal(vm, exp)); // 建立表示式對應的watcher物件 new Watcher(vm, exp, function (value, oldValue) {/*更新介面*/ // 當對應的屬性值發生了變化時, 自動呼叫, 更新對應的節點 updaterFn && updaterFn(node, value, oldValue); }); }, // 事件處理 eventHandler: function (node, vm, exp, dir) { // 得到事件名/型別: click var eventType = dir.split(':')[1], // 根據表示式得到事件處理函式(從methods中): test(){} fn = vm.$options.methods && vm.$options.methods[exp]; // 如果都存在 if (eventType && fn) { // 繫結指定事件名和回撥函式的DOM事件監聽, 將回撥函式中的this強制繫結為vm node.addEventListener(eventType, fn.bind(vm), false); } }, // 得到表示式對應的value _getVMVal: function (vm, exp) { // 這裡為什麼要forEach呢? // 如果你的exp是a.b.c.c.d呢 就需要forEach 如果只是一層 當然不需要遍歷啦 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) { // 非最後一個key,更新val的值 if (i < exp.length - 1) { val = val[k]; } else { val[k] = value; } }); }};// 包含多個用於更新節點方法的物件 var updater = { // 更新節點的textContent textUpdater: function (node, value) { node.textContent = typeof value == 'undefined' ? '' : value; }, // 更新節點的innerHTML htmlUpdater: function (node, value) { node.innerHTML = typeof value == 'undefined' ? '' : value; }, // 更新節點的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; }, // 更新節點的value modelUpdater: function (node, value, oldValue) { node.value = typeof value == 'undefined' ? '' : value; }};
最後
未完待續...
接下來,還有一個更有趣的東西
下一章繼續~
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1600/viewspace-2822853/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Vue原始碼探究-原始碼檔案組織Vue原始碼
- Vue原始碼探究-事件系統Vue原始碼事件
- Vue原始碼探究-生命週期Vue原始碼
- Vue原始碼探究-核心類的實現Vue原始碼
- Vue原始碼探究-構建版本的區別Vue原始碼
- Vue原始碼探究-資料繫結的實現Vue原始碼
- Vue原始碼探究-資料繫結邏輯架構Vue原始碼架構
- Vue原始碼探究-類初始化函式詳情Vue原始碼函式
- .NET Core Session原始碼探究Session原始碼
- .NET Core HttpClient原始碼探究HTTPclient原始碼
- Mybatis日誌原始碼探究MyBatis原始碼
- Java集合原始碼探究~ListJava原始碼
- UITableView+FDTemplateLayoutCell 原始碼探究UIView原始碼
- 執行緒池原始碼探究執行緒原始碼
- Proxy.newProxyInstance原始碼探究原始碼
- .Netcore HttpClient原始碼探究NetCoreHTTPclient原始碼
- 直播商城原始碼,vue 彈窗 慣性滾動 加速滾動原始碼Vue
- 淺談.Net Core DependencyInjection原始碼探究原始碼
- 從原始碼探究JAVA的equals和==原始碼Java
- ImageLoader深入原始碼學習探究原始碼
- .Net Core Configuration原始碼探究原始碼
- Vue原始碼解析Vue原始碼
- Vue原始碼解析:Vue例項Vue原始碼
- AQS原始碼探究之競爭鎖資源AQS原始碼
- 直播app原始碼,Flutter 彈窗元件APP原始碼Flutter元件
- vue原始碼學習Vue原始碼
- vue.js原始碼Vue.js原始碼
- vue原始碼剖析(一)Vue原始碼
- Vue 1.0.28 原始碼解析Vue原始碼
- Vue 原始碼分析系列一:new Vue()Vue原始碼
- 線上直播系統原始碼,彈出警告/提示類彈窗原始碼
- 記一次mpvue-loader原始碼探究Vue原始碼
- ]Iterator原始碼探究及其與Collection類的關係原始碼
- vue生命週期探究(一)Vue
- 深入研究 Laravel 原始碼第四天Laravel原始碼
- 人人都能懂的Vue原始碼系列(一)—Vue原始碼目錄結構Vue原始碼
- 彈彈彈,彈走魚尾紋的彈出選單(vue)Vue
- 【vue-系列】vue-router原始碼分析Vue原始碼