实现的效果

目标

​ 实现一个简单的vue函数,包括双向数据绑定,数据的响应式

思路

1. 首先想到要实现vue的数据绑定,根据vue的双向数据绑定,把{{xxx}} 与data{}里面的值进行绑定。将值绑定后,我们想到要将这些数据渲染出来,这里就需要知道页面的各个节点与各节点的子节点
 2. 再到数据响应模块,使用object.defineProperty来实现数据的响应式,用get方法来读取属性的值,用set方法来写入属性的值,以此来实现数据绑定
 3. 最后为Vue添加构造函数,例如说this.$el 来表示绑定的元素,$data来表示绑定的数据,$vnode代表虚拟节点

模块划分

  1. 编译模块
  2. 虚拟节点模块
  3. 渲染模块
  4. 数据响应模块
  5. VUE构造函数模块

编译模块

提供一个compile函数,将一个模板文本(数据)和环境对象(DOM节点)编译成一个结果
思想:

  1. 使用正则表达式匹配到html代码中包含 括号 的字符串
  2. 拿到字符串之后把两边括号替换掉,把表达式分割成数组
  3. 这需要与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
//过程: 先拿到{{xxx}}里面的字符串,然后再对字符串进行加工
function getFragments(template) {
var matches = template.match(/{{[^}]+}}/g); //万一有人的用户名叫12} 所以这里要处理一下
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树
思想:

  1. 需要创建虚拟DOM 以便以后操作数据
  2. 需要判断真实节点是否为文本节点,如果是就要记录到虚拟节点
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) {
// node 构造函数
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;
// 判断真实节点是否为文本节点,如果是,就要记录到虚拟节点
// 文本节点代表的数字是3

}
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);
}
}
}

数据响应模块

主要负责将原始对象的数据附加到代理对象上,代理对象能够监听到数据的更改,当数据发生改变时,执行某个回调函数(就可以实现数据响应)

  1. 使用Object.defineProperty来实现数据的响应式

  2. 这里有一个关键是当代理一个对象是,发现对象里面还有属性没法代理。这时候就要重新申请一个新的代理(如下图)

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
// 将原始对象的prop属性添加到代理对象中
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);
}
})
}

}




// 将原始对象的属性,提取到代理对象中
// org是原始对象, target是代理对象 当代理对象被赋值的时候要调用回调函数
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

评论