概述
在之前的章节中,咱们创立了一个没有任何逻辑的vue对象,仅仅只是保障了var app = new Vue({...})
不报错而已,这一篇咱们将构建一个真正的vue对象,实现真正的值绑定。
build(构建)
这是html中创立vue的代码
var app = new Vue({ el: '#app', data: { newTodo: '', todos: [] }, methods: { addTodo: function () { this.todos.push({ text: this.newTodo }); this.newTodo = ''; }, deleteTodo: function (index) { this.todos.splice(index, 1); } } })
思路是
- 创立vue对象
- 将data数据间接挂载到对象上,这样能够实现
vue.newTodo
的拜访成果 - 将method间接挂载到桂香上,能够实现
vue.addTodo
的成果
当然,实际上vue并不是这么实现的,vue通过proxy形式实现间接拜访的成果,咱们的目标是能用就行(大家真正实现一个框架不要报这种想法,本系列是为了让所有人都能了解,都能入门才用该形式实现)。
var vue = {};function build() { for (let k in options.data) { let v = options.data[k]; defineProperty(k, v); } for (let key in options.methods) { vue[key] = options.methods[key]; } }
defineProperty
这个是实现绑定的外围步骤,代码如下:
function defineProperty(name, value) { Object.defineProperty(vue, name, { get: function() { return value; }, set: function(newValue) { value = newValue; let items = subscriber[name]; if (items) { for (let i = 0; i < items.length; ++i) { items[i].change(name, newValue); } } } }) }
当咱们给一个对象新增一个属性或者批改属性值的时候能够间接通过user.name='张三'
实现,也能够通过函数Object.defineProperty
进行定义,显著后者更为麻烦,然而Object.defineProperty
能够监听值的变动,获取值和设置值都能够监听到,这就是为实现值变动更新dom的性能打下了根底。咱们在set中监听值变动,从订阅者外面依据变量名称取出订阅者的指令,顺次调用指令change
办法。defineProperty其余用法参考这里
咱们的data除了newTodo
这个字符串变量外还有todos
这个变量,其中新增代办的代码如下:
addTodo: function () { this.todos.push({ text: this.newTodo }); this.newTodo = ''; }
todos是通过调用push办法进行插入,那这样会触发set事件吗,答案是不会的,只有当this.todos=[]
这种todos赋予新值才会触发值扭转,这个必定是不行的,这个要求开发者必须构建一个push后的数组再赋值给todos,能够用原型解决这个问题,原型最大的益处就是能够从新定义js的规范办法。
function defineArrayProperty() { var method=['push','splice']; for(let i=0;i<method.length;++i) { var origin = Array.prototype[method[i]]; var fn = function () { origin.apply(this, arguments); let names = Object.getOwnPropertyNames(vue); for (let i = 0; i < names.length; ++i) { if (vue[names[i]] === this) { let items = subscriber[names[i]]; if (items) { for (let j = 0; j < items.length; ++j) { items[j].change(names[i], this); } } } } } Object.defineProperty(Array.prototype, method[i], { value: fn }); } }
通过apply
办法调用原来的办法,再找到订阅者告诉订阅者,对于变量能够一个个定义属性,这样能够获取到名称,然而对于批改js根本对象的属性,这里无奈获取变量名,只能通过遍历vue属性找到变量名。
valueTrigger
实现了以上工作,咱们上一篇中的valueTrigger
逻辑就能够实现
function valueTrigger(name, value) { if (vue[name] != undefined) { vue[name] = value; }}
当值发生变化,因为变量都间接绑定到vue对象自身,因而能够间接通过属性名找到并赋值。
事件
咱们将配置中methods的办法也挂载到了vue对象上,那么事件响应就能够做了,咱们略微批改下上一篇中compile函数,减少事件响应
function compile(node) { let element = node.cloneNode(false); for (let i = 0; i < node.childNodes.length; ++i) { element.appendChild(compile(node.childNodes[i])); } if (element.nodeType == 3) { //文本类型解析 let vars = parseVariable(element.textContent); for (let i = 0; i < vars.length; ++i) { let directive = Directive(element, vars[i], element.textContent); addSubscriber(vars[i], directive); } } else if (element.nodeType == 1 && element.attributes) { //元素类型解析 let attrs = element.attributes; for (let i = 0; i < attrs.length; ++i) { let name = attrs[i].name; if (name.startsWith("v-bind") || name.startsWith(":") || name.startsWith("v-model")) { let vars = parseVariable(attrs[i].value); if (vars.length == 0) { let directive = Directive(element, name, attrs[i].value); addSubscriber(attrs[i].value, directive); } else { for (let i = 0; i < vars.length; ++i) { let directive = Directive(element, name, attrs[i].value); addSubscriber(vars[i], directive); } } } //事件响应 if (name.startsWith("v-on:")) { let event = name.substr(5); addEvent(element, event, parseMethod(attrs[i].value)); } } } return element; }
- parseMethod通过正则表达式进行解析
function parseMethod(exp) { var method = {}; let m; let methodRegExp = /([^\(]+)\(([^\)]*)\)/g; if (m = methodRegExp.exec(exp)) { method.name = m[1]; let params = m[2]; params = params.replace(/\s+/g, ''); if (params && params.length > 0) { method.params = params.split(","); } else { method.params = []; } } return method; }
- addEvent代码:
function addEvent(element, event, method) { element.addEventListener(event, function(e) { let params = []; let paramNames = method.params; if (paramNames) { for (let i = 0; i < paramNames.length; ++i) { params.push(vue[paramNames[i]]); } } vue[method.name].apply(vue, params); }) }
通过调用addEventListener给节点减少事件,比方v-on:click
就会减少click事件,通过apply办法动静调用定义在vue中的办法,实现事件的响应,
成果
能够点击这里查看成果,为了用上style属性,批改了下html,减少了style属性,成果如下:
残缺的js代码能够点击这里查看
参考
点击余下链接,查看该系列其余文章
- 模拟vue本人入手写响应式框架(一) – Vue实现todo利用
- 模拟vue本人入手写响应式框架(二) – Vue对象创立用
- 模拟vue本人入手写响应式框架(三) – dom解析
- 模拟vue本人入手写响应式框架(四) – Vue对象构建