实现的效果
目标 实现一个简单的vue函数,包括双向数据绑定,数据的响应式
思路 1. 首先想到要实现vue的数据绑定,根据vue的双向数据绑定,把{{xxx}} 与data{}里面的值进行绑定。将值绑定后,我们想到要将这些数据渲染出来,这里就需要知道页面的各个节点与各节点的子节点
2. 再到数据响应模块,使用object.defineProperty来实现数据的响应式,用get方法来读取属性的值,用set方法来写入属性的值,以此来实现数据绑定
3. 最后为Vue添加构造函数,例如说this.$el 来表示绑定的元素,$data来表示绑定的数据,$vnode代表虚拟节点
模块划分
编译模块
虚拟节点模块
渲染模块
数据响应模块
VUE构造函数模块
编译模块
提供一个compile函数,将一个模板文本(数据)和环境对象(DOM节点)编译成一个结果 思想:
使用正则表达式匹配到html代码中包含 括号 的字符串
拿到字符串之后把两边括号替换掉,把表达式分割成数组
这需要与envObj环境变量进行配合使用,绑定后变成一个数组
compile.js 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function getFragments (template ) { var matches = template.match(/{{[^}]+}}/g ); return matches || []; } function getValue (fragment, envObj ) { var exp = fragment.replace("{{" , "" ).replace("}}" , "" ); var props = exp.split("." ); var obj = envObj; for (var i = 0 ; i < props.length; i++) { obj = obj[props[i]]; } return obj; } export default function compile (template, envObj ) { var flags = getFragments(template); var result = template; for (var i = 0 ; i < flags.length; i++) { var flag = flags[i]; result = result.replace(flag, getValue(flag, envObj)) } return result; }
虚拟DOM:
提供一个函数createVNode,根据提供真实的DOM,构建一个虚拟DOM树 思想:
需要创建虚拟DOM 以便以后操作数据
需要判断真实节点是否为文本节点,如果是就要记录到虚拟节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function VNode (realDom, template ) { this .realDom = realDom; this .template = template; this .children = []; } export default function createVNode (realDom ) { var root = new VNode(realDom, "" ); if (realDom.nodeType === Node.TEXT_NODE) { root.template = realDom.nodeValue; } else { for (var i = 0 ; i< realDom.childNodes.length;i++){ var childNode = realDom.childNodes[i]; var vNode = createVNode(childNode) root.children.push(vNode); } } return root; }
渲染模块
用于提取虚拟节点,将其模板编译结果设置到真实的dom中,对虚拟节点的子节点也做同样的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import compile from "./compile.js" ;export default function render (vnode, envObj ) { if (vnode.realDom.nodeType === Node.TEXT_NODE) { vnode.realDom.nodeValue = compile(vnode.template, envObj); } else { for (var i = 0 ; i < vnode.children.length; i++) { var childNode = vnode.children[i]; render(childNode, envObj); } } }
数据响应模块
主要负责将原始对象的数据附加到代理对象上,代理对象能够监听到数据的更改,当数据发生改变时,执行某个回调函数(就可以实现数据响应)
使用Object.defineProperty来实现数据的响应式
这里有一个关键是当代理一个对象是,发现对象里面还有属性没法代理。这时候就要重新申请一个新的代理(如下图)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 function proxyProp (originalObj, targetObj, prop, callback ) { if (typeof originalObj[prop] === "object" ) { var newTarget = {}; createResponsive(originalObj[prop], newTarget, callback); Object .defineProperty(targetObj,prop,{ get :function(){ return newTarget; }, set :function(value){ originalObj[prop] = value; newTarget = value; callback && callback(prop); } }) } else { Object .defineProperty(targetObj, prop, { get : function () { return originalObj[prop]; }, set : function (value) { originalObj[prop] = value; callback && callback(prop); } }) } } export default function createResponsive (originalObj, targetObj, callback ) { for (var prop in originalObj) { proxyProp(originalObj, targetObj, prop, callback); } }
为vue写一个构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import createVNode from './vnode.js' import createResponsive from './dataResponsive.js' import render from './render.js' export default function vue (option ) { this .$el = option && option.el; this .$data = option && option.data; this .$vnode = createVNode(document .querySelector(this .$el)) var that = this ; createResponsive(this .$data, this , function ( ) { that.render(); }) this .render() } vue.prototype.render = function ( ) { render(this .$vnode, this ); }
在前端页面写一个简单的VUE结构
在前端页面写一个简单的VUE结构,测试一下是否正确
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script> import Vue from "./vue/index.js" window .vm = new Vue({ el: "#app" , data: { name:"FengZe" , age:16 , addr:{ province:"广东" , city:"佛山" } } }) </script>
大概就是这样实现的了.
源码地址:https://github.com/FengZeHe/vue_demo