// 从Vue的第二个commit来学习数据驱动视图
//Vue里面v-text的实现原理(为了易于理解,以下代码为超简化版)
<div id="test"> <p v-text="date"></p> <p v-text="msg"></p> <span v-text="msg"></span> </div> var bindingMark = "v-text"; //绑定标记 //初始数据 var initData = { date: "2017-05-04", msg: "hello" }; var bindings = {};//内部数据备份 var data = {};//外部数据接口 var root = document.getElementById("test"), //模型根节点 els = root.querySelectorAll("[" + bindingMark + "]");//获取test下 所有的带有 v-test属性的节点 //收集 v-text 里面的值 [].forEach.call(els, function (el) { var variable = el.getAttribute(bindingMark); //获取v-text属性的值 bindings[variable] = {}; }); //数据驱动绑定函数 var bind = function(variable){ bindings[variable].els = root.querySelectorAll('[' + bindingMark + '="' + variable + '"]'); //获取某一 v-text 值的NodeList //删除v-text属性 [].forEach.call(bindings[variable].els, function (e) { e.removeAttribute(bindingMark); }); //添加set get 存取器描述 Object.defineProperty(data, variable, { set: function (newVal) { //数据重新赋值时候 更新dom中相对应的v-text [].forEach.call(bindings[variable].els, function (e) { bindings[variable].value = e.textContent = newVal; }); }, get: function () { return bindings[variable].value; } }) }; //执行绑定 for (var variable in bindings) { bind(variable); } //初始化赋值 if (initData) { for (var variable in initData) { data[variable] = initData[variable]; } } //更新数据函数 var updateData = function(newData){ for(var n in newData){ if(data[n] && (data[n] != newData[n])){ data[n] = newData[n]; } } }; //点击模型元素时 更新数据 然后触发set函数执行,进而驱动视图去改变 root.addEventListener('click', function(){ updateData({ date: "2017-07-02", msg: "world" }); });
本文地址:https://zhuanlan.zhihu.com/p/26744423
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <div id="test"> 9 <p>v-model 绑定的输入框,可同步下面的v-msg文本: <input v-model="msg" placeholder="edit me"></p> 10 <p>v-msg 文本: <span v-text="msg"></span></p> 11 <hr> 12 <p>v-show 显示隐藏 demo: <span v-show="isShow">我是v-show属性绑定元素</span></p> 13 <p>v-on:click,可以触发上面文本的显示隐藏<a href="javascript:;" v-on:click="changeShow" v-text="btn"></a></p> 14 </div> 15 </body> 16 <script> 17 /** 18 * 支持的模板语法 19 * [Directives description] 20 * @type {Object} 21 */ 22 var Directives = { 23 text: function (el, value) { 24 el.textContent = value || '' 25 }, 26 show: function (el, value) { 27 el.style.display = value ? '' : 'none' 28 }, 29 on: { 30 update: function (el, handler, event, directive) { 31 if (!directive.handlers) { 32 directive.handlers = {} 33 } 34 var handlers = directive.handlers 35 if (handlers[event]) { 36 el.removeEventListener(event, handlers[event]) 37 } 38 if (handler) { 39 handler = handler.bind(el); 40 el.addEventListener(event, handler); 41 handlers[event] = handler; 42 } 43 } 44 }, 45 model: { 46 bind: function (el, key, directive, seed) { 47 el.addEventListener('keyup', function (e) { 48 seed.$data[key] = el.value; 49 }); 50 }, 51 update: function (el, value) { 52 el.value = value; 53 } 54 } 55 } 56 57 /** 58 * 工具方法 59 * [Utils description] 60 * @type {Object} 61 */ 62 var Utils = { 63 cloneAttributes: function (attributes) { 64 return [].map.call(attributes, function (attr) { 65 return { 66 name: attr.name, 67 value: attr.value 68 } 69 }) 70 }, 71 parseDirective: function (attr) { 72 if (attr.name.indexOf(prefix) === -1) return; 73 74 var noprefix = attr.name.slice(prefix.length + 1), 75 argIndex = noprefix.indexOf(':'), 76 dirname = argIndex === -1 ? noprefix : noprefix.slice(0, argIndex), 77 def = Directives[dirname], 78 arg = argIndex === -1 ? null : noprefix.slice(argIndex + 1); 79 80 var exp = attr.value, 81 key = exp.trim(); 82 83 return def 84 ? { 85 attr: attr, 86 key: key, 87 definition: def, 88 argument: arg, 89 update: typeof def === 'function' ? def : def.update, 90 bind: typeof def === 'function' ? null : def.bind ? def.bind : null 91 } 92 : null; 93 } 94 }; 95 96 var prefix = 'v', 97 selector = Object.keys(Directives).map(function (d) { 98 return '[' + prefix + '-' + d + ']' 99 }).join(); 100 101 /** 102 * Vue构造函数 103 * [Vue description] 104 * @param {[type]} el [description] 105 * @param {[type]} opts [description] 106 */ 107 function Vue (el, opts) { 108 var self = this, 109 root = self.$el = document.getElementById(el), 110 els = root.querySelectorAll(selector), 111 _bindings = {}; 112 113 self.$opts = opts || {}; 114 115 self.$data = {}; //对外暴露的数据接口 116 117 self.processNode(els, _bindings); 118 119 self.initData(_bindings); 120 } 121 122 /** 123 * 处理node节点 124 * 125 * [processNode description] 126 * @param {[type]} els [description] 127 * @param {[type]} _bindings [description] 128 * @return {[type]} [description] 129 */ 130 Vue.prototype.processNode = function(els, _bindings){ 131 var self = this; 132 [].forEach.call(els, function(el){ 133 Utils.cloneAttributes(el.attributes).forEach(function (attr) { 134 var directive = Utils.parseDirective(attr); 135 if (directive) { 136 self.bindDirective(el, _bindings, directive) 137 } 138 }); 139 }); 140 } 141 142 //属性移除 指令绑定 143 Vue.prototype.bindDirective = function(el, _bindings, directive){ 144 var self = this; 145 146 el.removeAttribute(directive.attr.name); 147 var key = directive.key, 148 binding = _bindings[key]; 149 if (!binding) { 150 _bindings[key] = binding = { 151 value: undefined, 152 directives: [] 153 } 154 } 155 directive.el = el; 156 binding.directives.push(directive); 157 158 if (directive.bind) { 159 directive.bind(el, key, directive, self); 160 } 161 if (!self.$data.hasOwnProperty(key)) { 162 self.bind(key, binding); 163 } 164 } 165 166 //绑定 赋值拦截 set 方法 167 Vue.prototype.bind = function(key, binding) { 168 var that = this; 169 Object.defineProperty(that.$data, key, { 170 set: function (value) { 171 binding.value = value; 172 173 binding.directives.forEach(function (directive) { 174 directive.update( 175 directive.el, 176 value, 177 directive.argument, 178 directive, 179 self 180 ) 181 }) 182 }, 183 get: function () { 184 return binding.value; 185 } 186 }) 187 }; 188 //实例初始化 赋值 189 Vue.prototype.initData = function(_bindings) { 190 var self = this; 191 for (var variable in _bindings) { 192 this.$data[variable] = self.$opts[variable]; 193 } 194 }; 195 196 /** 197 * 创建vm实例 198 */ 199 var vm = new Vue('test', { 200 msg: 'aaa', 201 isShow: true, 202 btn: '点击我', 203 changeShow: function(){ 204 vm.$data.isShow = !vm.$data.isShow; 205 } 206 }); 207 </script> 208 </html>
精品文章
vue学习笔记:http://jiongks.name/blog/vue-code-review/