对于 Vue 的工做过程,咱们能够从下面这张图中获得一点思路。javascript
咱们能够从两个方面来解析 Vue 的工做过程:初始化阶段、数据修改阶段。html
在 Vue 初始化阶段,咱们建立了一个 Vue 实例并将其挂载在了页面上:vue
init()
方法。它作了什么事情呢?它将传入的props、事件、data等都作了初始化。$mount()
方法,实现了 Vue 实例的挂载。这个$mount()
方法,最主要作的事情是什么呢?它经过调用 render()
函数生成了 virtual DOM,即虚拟DOM树。 render()
函数在执行的时候,会touch
一下 对应属性的getter
,这一步即为触发getter
进行依赖收集的过程。patch()
方法生成真实DOM,挂载在页面上。在数据修改阶段:java
setter
。patch()
方法,对比新旧 virtual DOM,获得页面的最小修改,执行页面刷新。我想要试试本身实现一个简单的 Vue。它将会是怎样的呢:node
{{}}
。文件会有五个:算法
首先,给出做为测试用的 index.html:数组
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<div id="app">
{{test}}
<p k-text="test"></p>
<p k-html="html"></p>
<p>
<input type="text" k-model="test">
</p>
<p>
<button @click="onClick">按钮</button>
</p>
</div>
<script src="fvue.js"></script>
<script src="fcompile.js"></script>
<script src="watcher.js"></script>
<script src="dep.js"></script>
<script> const fVue = new FVue({ el: "#app", data: { test: "hello, frank", foo: { bar: "bar" }, html: '<button>adfadsf</button>' }, methods: { onClick() { alert('blabla') } }, }); //模拟数据修改 setTimeout(function(){ fVue.$data.test = "hello,fVue!"; console.log("setTimeout : ",fVue.$data.test); }, 2000); </script>
</body>
</html>
复制代码
为了验证想法,写了这四个文件。代码尽可能简单。app
//fvue.js
class FVue {
constructor(options){
this.$data = options.data;
this.$options = options;
//数据响应化
this.observe(this.$data);
//解析页面模板
new Compile(options.el, this);
}
observe(value){
if(!value || typeof value !== 'object'){
return;
}
Object.keys(value).forEach(key =>{
this.defineReactive(value, key, value[key]);
// 为vue的data作属性代理:this.xxx = this.$data.xxx
this.proxyData(key);
})
}
defineReactive(obj, key, val){
//递归
this.observe(val);
//每个 key 都有一个的Dep与之对应
const dep = new Dep();
Object.defineProperty(obj, key, {
get(){
//依赖收集
Dep.target && dep.addDep(Dep.target)
return val;
},
set(newVal){
if(newVal === val) return;
val = newVal;
//执行更新操做
dep.notify();
}
})
}
proxyData(key) {
Object.defineProperty(this, key, {
get(){
return this.$data[key];
},
set(newVal){
this.$data[key] = newVal;
},
});
}
}
复制代码
fvue.js 核心文件实现了 observe 逻辑:即在初始化过程当中,将传入的data
属性作了初始化处理,经过 defineReactive()
方法将data
中每一个属性都作了数据拦截,从新定义了每一个属性的getter
与setter
。更详细的:函数
每个属性都有本身专有的调度模块 Dep。测试
在getter
中,定义了依赖收集的方式(只要有对应的 Watcher 触发了 getter 方法,那么将其放入到 Dep 的数组里)。
在setter
中,定义了响应数据变化的方法(只要对应的setter方法被触发,那么该 Dep 就会执行通知操做,让对应的 Watcher 执行更新)。
再来看 dep.js 与 watcher.js。
//dep.js
class Dep {
constructor(){
this.deps = []
}
addDep(dep){
this.deps.push(dep)
}
notify(){
this.deps.forEach(dep => dep.update())
}
}
//watcher.js
class Watcher{
constructor(vm, key, cb){
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this; //将当前Watcher实例附加到Dep的静态属性上
this.vm[this.key]; //主动触发 getter 属性,触发依赖收集
Dep.target = null; //解除 Dep.target 这个静态变量的锁定
}
update(){
this.cb.call(this.vm, this.vm[this.key]);
}
}
复制代码
咱们将 Dep 当作是一个调度模块,它只负责管理更新。而 Watcher 至关因而一个执行人,它负责执行具体的更新过程。
咱们看到,在 Watcher 初始化的过程当中,咱们主动触发了 getter
属性,触发了依赖收集的过程。可是,尚未看到 Watcher 在哪里被初始化的。其实,在 解析 HTML 模板的过程当中,当咱们发现了自定义变量时,就会触发 Watcher 的初始化。
为了简化,验证可行性。此时咱们的 fcompile.js 会写得很是简单,只处理文本自定义变量的状况(在例子中是{{test}}
)。
class Compile {
//el是宿主元素或者选择器
//vm 是vue实例
constructor(el, vm){
this.$vm = vm;
this.$el = document.querySelector(el); // 简化:经过选择器来获取到文档元素
this.compile(this.$el);
}
compile(el){
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
if(this.isTextParam(node)){
this.compileText(node);
}
//递归
this.compile(node);
})
}
isTextParam(node){
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
compileText(node){
let key = RegExp.$1;
let currentValue = this.$vm[key];
//解析后,须要将真实值挂载到真实页面上
this.textUpdate(node, currentValue)
//建立新的 Watcher 实例
new Watcher(this.$vm, key, (newValue)=>{
this.textUpdate(node, newValue)
})
}
textUpdate(node, value){
node.textContent = value;
}
}
复制代码
Compile 是在 FVue 中调用的。它的工做是最为繁重的:
固然,为了简单起见,此处的 Compile 只处理了一个最简单的状况:文本自定义变量 ({{test}}) 的状况。一个完善的 compile 函数会很是周密且复杂,可查看 Vue 源码。
将代码放在一块儿,它们是能够运转的。页面上的展现变量在定时器时间事后,会发生改变。
在文章最后,让咱们来捋一捋整个 Vue 工做的过程:
整个过程能够看作是 Vue 1.x 的工做方式极端简易版本,虽然与 Vue 2.x 不一样,但但愿不会影响各位读者对 Vue 的理解。