更多詳情請參考 http://blog.zhangbing.club/Ja...vue
/** * the super tiny vue.js. 簡介:一個迷你vue庫,雖然小但功能全面,能夠做爲想了解vue背後思想以及想學習vue源碼而又不知如何入手的入門學習資料。 特性: * 數據響應式更新 * 指令模板 * MVVM * 輕量級 ## 功能解讀 <templete> <div id='app'> <div> <input v-model='counter' /> <button v-on-click='add'>add</button> <p v-text='counter'></p> </div> </div> </templete> <script> var vm = new Vue({ id: 'counter', data: { counter: 1 }, methods: { add: function () { this.counter += 1; } } }) </script> 如上爲一段模板以及js腳本,咱們所要實現的目標就是將 vm 實例與id爲app的DOM節點關聯起來,當更改vm data 的counter屬性的時候, input的值和p標籤的文本會響應式的改變,method中的add方法則和button的click事件綁定。 簡單的說就是, 當點擊button按鈕的時候,觸發button的點擊事件回調函數add,在add方法中使counter加1,counter變化後模板中的input 和p標籤會自動更新。vm與模板之間是如何關聯的則是經過 v-model、v-on-click、v-text這樣的指令聲明的。 ### 實現思路詳解 * 查找含指令的節點 * 對查找所得的節點進行指令解析、指令所對應的實現與節點綁定、 節點指令值所對應的data屬性與前一步關聯的指令實現綁定、data屬性值經過setter通知關聯的指令進行更新操做 * 含指令的每個節點單獨執行第二步 * 綁定操做完成後,初始化vm實例屬性值 #### 指令節點查找 首先來看第一步,含指令節點的查找,由於指令聲明是以屬性的形式,因此能夠經過屬性選擇器來進行查找,以下所示: `<input v-model='counter' type='text' />` 則可經過 querySelectorAll('[v-model]') 查找便可。 root = this.$el = document.getElementById(opts.el), els = this.$els = root.querySelectorAll(getDirSelectors(Directives)) root對於根節點,els對應於模板內含指令的節點。 #### 指令解析,綁定 * 1.指令解析 一樣以`<input v-model='counter' type='text' />`爲例,解析即獲得 var directive = { name: 'v-model', value: 'counter' } name對應指令名,value對應指令值。 * 2.指令對應實現與當前節點的綁定(bindDirective) 指令實現可簡單分爲函數或是包含update函數的對象,以下即是`v-text`指令的實現代碼: text: function (el, value) { el.textContent = value || ''; } 指令與節點的綁定即將該函數與節點綁定起來,即該函數負責該節點的更新操做,`v-text`的功能是更新文本值,因此如上所示 更改節點的textContent屬性值。 * 3. 響應式數據與節點的綁定(bindAccessors) 響應式數據這裏拆分爲 data 和 methods 對象,分別用來存儲數據值和方法。 var vm = new Vue({ id: 'counter', data: { counter: 1 }, methods: { add: function () { this.counter += 1; } } }) 咱們上面解析獲得 v-model 對於的指令值爲 counter,因此這裏將data中的counter與當前節點綁定。 經過二、3兩步實現了類型與 textDirective->el<-data.counter 的關聯,當data.counter發生set(具體查看defineProperty set 用法)操做時, data.counter得知本身被改變了,因此通知el元素須要進行更新操做,el則使用與其關聯的指令(textDirective)對自身進行更新操做,從而實現了數據的 響應式。 * textDirective * el * data.counter 這三個是綁定的主體,數據發生更改,通知節點須要更新,節點經過指令更新本身。 * 4.其它相關操做 */ var prefix = 'v'; /** * Directives */ var Directives = { /** * 對應於 v-text 指令 */ text: function (el, value) { el.textContent = value || ''; }, show: function (el, value) { el.style.display = value ? '' : 'none'; }, /** * 對應於 v-model 指令 */ model: function (el, value, dirAgr, dir, vm, key) { let eventName = 'keyup'; el.value = value || ''; /** * 事件綁定控制 */ if (el.handlers && el.handlers[eventName]) { el.removeEventListener(eventName, el.handlers[eventName]); } else { el.handlers = {}; } el.handlers[eventName] = function (e) { vm[key] = e.target.value; } el.addEventListener(eventName, el.handlers[eventName]); }, on: { update: function (el, handler, eventName, directive) { if (!directive.handlers) { directive.handlers = {} } var handlers = directive.handlers; if (handlers[eventName]) { //綁定新的事件前移除原綁定的事件函數 el.removeEventListener(eventName, handlers[eventName]); } //綁定新的事件函數 if (handler) { handler = handler.bind(el); el.addEventListener(eventName, handler); handlers[eventName] = handler; } } } } /** * MiniVue */ function TinyVue (opts) { /** * root/this.$el: 根節點 * els: 指令節點 * bindings: 指令與data關聯的橋樑 */ var self = this, root = this.$el = document.getElementById(opts.el), els = this.$els = root.querySelectorAll(getDirSelectors(Directives)), bindings = {}; this._bindings = bindings; /** * 指令處理 */ [].forEach.call(els, processNode); processNode(root); /** * vm響應式數據初始化 */ let _data = extend(opts.data, opts.methods); for (var key in bindings) { if (bindings.hasOwnProperty(key)) { self[key] = _data[key]; } } function processNode (el) { getAttributes(el.attributes).forEach(function (attr) { var directive = parseDirective(attr); if (directive) { bindDirective(self, el, bindings, directive); } }) } /** * ready */ if (opts.ready && typeof opts.ready == 'function') { this.ready = opts.ready; this.ready(); } } /************************************************************** * @privete * helper methods */ /** * 獲取節點屬性 * 'v-text'='counter' => {name: v-text, value: 'counter'} */ function getAttributes (attributes) { return [].map.call(attributes, function (attr) { return { name: attr.name, value: attr.value } }) } /** * 返回指令選擇器,便於指令節點的查找 */ function getDirSelectors (directives) { /** * 支持的事件指令 */ let eventArr = ['click', 'change', 'blur']; return Object.keys(directives).map(function (directive) { /** * text => 'v-text' */ return '[' + prefix + '-' + directive + ']'; }).join() + ',' + eventArr.map(function (eventName) { return '[' + prefix + '-on-' + eventName + ']'; }).join(); } /** * 節點指令綁定 */ function bindDirective (vm, el, bindings, directive) { //從節點屬性中移除指令聲明 el.removeAttribute(directive.attr.name); /** * v-text='counter' * v-model='counter' * data = { counter: 1 } * 這裏的 counter 即指令的 key */ var key = directive.key, binding = bindings[key]; if (!binding) { /** * value 即 counter 對應的值 * directives 即 key 所綁定的相關指令 如: bindings['counter'] = { value: 1, directives: [textDirective, modelDirective] } */ bindings[key] = binding = { value: '', directives: [] } } directive.el = el; binding.directives.push(directive); //避免重複定義 if (!vm.hasOwnProperty(key)) { /** * get/set 操做綁定 */ bindAccessors(vm, key, binding); } } /** * get/set 綁定指令更新操做 */ function bindAccessors (vm, key, binding) { Object.defineProperty(vm, key, { get: function () { return binding.value; }, set: function (value) { binding.value = value; binding.directives.forEach(function (directive) { directive.update( directive.el, value, directive.argument, directive, vm, key ) }) } }) } function parseDirective (attr) { if (attr.name.indexOf(prefix) === -1) return ; /** * 指令解析 v-on-click='onClick' 這裏的指令名稱爲 'on', 'click'爲指令的參數,onClick 爲key */ //移除 'v-' 前綴, 提取指令名稱、指令參數 var directiveStr = attr.name.slice(prefix.length + 1), argIndex = directiveStr.indexOf('-'), directiveName = argIndex === -1 ? directiveStr : directiveStr.slice(0, argIndex), directiveDef = Directives[directiveName], arg = argIndex === -1 ? null : directiveStr.slice(argIndex + 1); /** * 指令表達式解析,即 v-text='counter' counter的解析 * 這裏暫時只考慮包含key的狀況 */ var key = attr.value; return directiveDef ? { attr: attr, key: key, dirname: directiveName, definition: directiveDef, argument: arg, /** * 指令自己是一個函數的狀況下,更新函數即它自己,不然調用它的update方法 */ update: typeof directiveDef === 'function' ? directiveDef : directiveDef.update } : null; } /** * 對象合併 */ function extend (child, parent) { parent = parent || {}; child = child || {}; for(var key in parent) { if (parent.hasOwnProperty(key)) { child[key] = parent[key]; } } return child; }
參考來源:https://github.com/xiaofuzi/d...git