文章目录
-
- 前言
- [zvue.js 架构图](#zvue.js 架构图)
- Object.defineProperty()
- 读取数据:vm[key]
- 更新数据
- [完整 zvue.js 代码](#完整 zvue.js 代码)
前言
前一篇 简单实现vue.js 一文中,我们实现一个简化版的 Vue.js(zvue.js
),本文来通过这个 zvue.js 来一窥 vue 结构及实现原理
zvue.js 架构图
先上一张 zvue.js 架构图,本文我们就对此详细展开研究:

各个类的主要功能如下:
-
VUE
: 主类,执行入口 -
Observer
: 给vue.$data
下每个属性都包装成一个有get/set
方法的对象,并在get/set
方法中夹带私货
(Dep 与 Watcher之间的互动) -
Compiler
: Dom 视图解析成 token 流,把 html 中有vue相关
的部分找出来,每个位置都记录下来并分配一个Watcher
进行监管,一旦数据有变,最终会执行到 Watcher 的更新,从而使页面更新 -
Dep
: 管理观察者(Watcher)列表,并通知全部的 Watcher 对象 -
Watcher
: 观察者模式中的实体观察者,一旦数据有变,最终会执行到 Watcher 的更新,从而使页面实时更新
Object.defineProperty()
在上面代码中,有两处使用到 Object.defineProperty()
方法,一个是 Vue 类中的 proxy()
方法,一个是 Observer 类中的 doDefProp()
,其本质上是一个装饰器,给一个数据包装成一个有 get/set
方法的对象,并在 get/set
方法中加入装饰逻辑
,一旦有 读取或更新
数据时触发执行到装饰逻辑。
Vue 类中的 proxy()
方法比较简单,没有什么装饰逻辑,其实就是想要 无中生有
,把 Vue.$data
下的属性当作是 Vue 的自己属性
,即实现 vm.xx==vm.$data.xx
,这里起到的就是代理作用,简化省掉$data
而已。
类似 java 中的 getXXX/setXXX() 方法,可以没有 XXX 字段
js
// data 来源于vue.$data
proxy(target, data) {
Object.keys(data).forEach((key) => {
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
//目的:this.xx==this.$data.xx
get() {
console.log("proxy -1- get data[key]:", data[key]);
return data[key];
},
set(newVal) {
console.log("proxy -2- set data[key]=", newVal);
data[key] = newVal;
},
});
});
}
Observer 类中的 doDefProp()
,是给 data 每个 key 对应的数据 value
进行封装成一个对象,有 get/set 逻辑
例如 value 本身是一个姓名字符串
"张三",经过 Object.defineProperty
摇身一变成了一个 Person 对象
,取值设置要调用里面的 get/set 方法
:
并且还可以夹带私货
(装饰逻辑),例如代码中的 Dep.target && dep.addSub(Dep.target);
,这个可以说是 Vue 的灵魂所在,后面我们会看到这里 target
其实就是 Watcher
对象,data 的每个 key 值都有一个 Watcher 监视器
,监听 value 的变化。
js
// 会放到dep里面相关联,dep是传参进来的,与Observer 对应绑定
doDefProp(data, key, val, dep) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
// Dep.target 是公用的
console.log("doDefProp -1- get data[key] --> dep.addSub", Dep.target);
// 加入列表
Dep.target && dep.addSub(Dep.target);
return val;
},
set(newVal) {
console.log("doDefProp -2- set data[key]=", newVal);
if (val === newVal) return;
val = newVal;
if (typeof val === "object" && val !== null) {
new Observer(val);
}
// 通知监视器更新 token 流,实现页面渲染更新
dep.notify();
},
});
}
注意
这里要划重点了,在 读取 key(get)
时添加 监视器 Watcher
到观察者列表
js
// 加入列表
Dep.target && dep.addSub(Dep.target);
在更新 key(set)
时候通知 监视器 Watcher
进行 token 流替换,从而实现页面渲染更新。
js
// 通知监视器更新 token 流,实现页面渲染更新
dep.notify();
在浏览器中可以查看到 $data 的信息,一点开里面的 message
,立刻执行了装饰逻辑,打印出日志 doDefProp -1- get data[key] --> dep.addSub null
读取数据:vm[key]
在 Watcher
构造函数中 有一句 this.oldVal = vm[key];
,
js
class Watcher {
constructor(vm, key, callback) {
this.vm = vm;
this.key = key;
this.callback = callback;
console.log("Watcher.constructor -1- Dep.target = this", this);
Dep.target = this;
console.log("Watcher.constructor -2- this.oldVal = vm[key]");
this.oldVal = vm[key];
console.log("Watcher.constructor -3- Dep.target = null");
Dep.target = null;
}
...
}
通过日志可以看出其调用的路径,vm[key] -> get -> dep.addSub
log
zvue.js:119 Watcher.constructor -2- this.oldVal = vm[key]
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub Watcher {vm: Vue, key: 'message', callback: ƒ}
zvue.js:96 Dep.addsub -2- : Watcher {vm: Vue, key: 'message', callback: ƒ}
zvue.js:36 proxy -1- get data[key]: Hello world!
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub Watcher {vm: Vue, key: 'message', callback: ƒ}
zvue.js:96 Dep.addsub -2- : Watcher {vm: Vue, key: 'message', callback: ƒ}
zvue.js:121 Watcher.constructor -3- Dep.target = null
更新数据
在浏览器中手动执行 app.message=1234
,页面渲染马上变成 1234--1234
,执行路径为 set -> Dep.notify -> Watcher.update
,到此就完成页面的更新了
完整 zvue.js 代码
zvue.js
js
class Vue {
constructor(options) {
// $xx 表示对象
this.$options = options || {};
this.$data = options.data || {};
// el 可能为字符串或对象
this.el = options.el;
// el 对象
this.$el =
typeof this.el == "string" ? document.querySelector(this.el) : this.el;
//属性注入vue实例(setX),即data属性变成Vue的对象
console.log("Vue.constructor -1- proxy(this, this.$data)", this);
this.proxy(this, this.$data);
//observer 观察data
console.log("Vue.constructor -2- new Observer(this.$data)", this);
new Observer(this.$data);
//dom视图解析
console.log("Vue.constructor -3- new Compiler(this, this.el)", this);
new Compiler(this, this.$el);
}
// // data 来源于vue.$data
proxy(target, data) {
Object.keys(data).forEach((key) => {
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
//目的:this.xx==this.$data.xx
get() {
console.log("proxy -1- get data[key]:", data[key]);
return data[key];
},
set(newVal) {
console.log("proxy -2- set data[key]=", newVal);
data[key] = newVal;
},
});
});
}
}
class Observer {
constructor(data) {
console.log("Observer.constructor -1- data", data);
this.data = data;
// 注意: Dep new 出来了
this.dep = new Dep();
// 重点
this.walk(data, this.dep);
}
walk(data, dep) {
console.log("Observer.walk -2- this:", this);
//data中属性包装成对象
Object.keys(data).forEach((key) =>
this.doDefProp(data, key, data[key], dep)
);
}
// 会放到dep里面相关联,dep是传参进来的,与Observer 对应绑定
doDefProp(data, key, val, dep) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
// Dep.target 是公用的
console.log("doDefProp -1- get data[key] --> dep.addSub", Dep.target);
Dep.target && dep.addSub(Dep.target);
return val;
},
set(newVal) {
console.log("doDefProp -2- set data[key]=", newVal);
if (val === newVal) return;
val = newVal;
if (typeof val === "object" && val !== null) {
new Observer(val);
}
dep.notify();
},
});
}
}
// 依赖收集器
class Dep {
constructor() {
console.log("Dep.constructor -1-");
this.subs = [];
}
addSub(sub) {
console.log("Dep.addsub -2- : ", sub);
this.subs.push(sub);
}
notify() {
console.log("Dep.notify -3- : ", this.subs);
Array.from(this.subs).forEach((sub) => {
sub.update();
});
}
}
Dep.target = null;
// 观察者
class Watcher {
constructor(vm, key, callback) {
this.vm = vm;
this.key = key;
this.callback = callback;
console.log("Watcher.constructor -1- Dep.target = this", this);
Dep.target = this;
console.log("Watcher.constructor -2- this.oldVal = vm[key]: ", vm[key]);
this.oldVal = vm[key];
console.log("Watcher.constructor -3- Dep.target = null");
Dep.target = null;
}
update() {
const newVal = this.vm[this.key];
console.log("Watcher.update -4- newVal:", newVal);
if (newVal === this.oldVal) return;
this.callback(newVal);
this.oldVal = newVal;
}
}
// 编译模板
class Compiler {
constructor(vm, el) {
this.vm = vm;
this.el = el;
if (this.el) {
this.compile(this.el);
}
}
compile(el) {
const childNodes = el.childNodes;
Array.from(childNodes).forEach((node) => {
if (node.nodeType === 3) {
// 插值形式,进行替换
this.compileText(node);
} else if (node.nodeType === 1) {
// 元素类型,解析属性
}
// 递归解析
if (node.childNodes && node.childNodes.length) {
this.compile(node);
}
});
}
// 替换 {{key}} -> value
compileText(node) {
const reg = /\{\{(.+?)\}\}/g;
const value = node.textContent.replace(/\s/g, "");
const tokens = [];
let result,
index,
lastIndex = 0;
while ((result = reg.exec(value))) {
console.log("Compiler.compileText -1- result:", result);
index = result.index;
if (index > lastIndex) {
tokens.push(value.slice(lastIndex, index));
}
const key = result[1].trim();
tokens.push(this.vm[key]);
lastIndex = index + result[0].length;
const pos = tokens.length - 1;
console.log("Compiler.compileText -2- new Watcher");
new Watcher(this.vm, key, (newVal) => {
console.log(
"Compiler.compileText -3- tokens[pos] = newVal",
tokens,
pos,
newVal
);
tokens[pos] = newVal;
node.textContent = tokens.join("");
});
}
if (lastIndex < value.lenth) {
tokens.push(value.slice(lastIndex));
}
if (tokens.length) {
node.textContent = tokens.join("");
}
}
}