文章目录
前言
实现简单的 vue.js, 探索 vue 实现原理,这里仅实现 插值功能{``{xxx}}
index.html
html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>TestVue</title>
</head>
<body>
<div id="app">
<p>{{message}}--{{message}} </p>
</div>
<!-- vue2 开发环境版本: https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js -->
<!-- 简版手动实现 -->
<script src="zvue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});
</script>
</body>
</html>
zvue.js
自己实现的简单 vue.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);
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
function 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) => doDefProp(data, key, data[key], dep));
}
}
// 会放到dep里面相关联,dep是传参进来的,与Observer 对应绑定
function 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 = vm.$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("");
}
}
}
运行

动态响应
日志
初始运行
log
zvue.js:15 Vue.constructor -1- proxy(this, this.$data) Vue {$options: {...}, $data: {...}, el: '#app', $el: div#app}
zvue.js:19 Vue.constructor -2- new Observer(this.$data) Vue {$options: {...}, $data: {...}, el: '#app', $el: div#app}
zvue.js:49 Observer.constructor -1- data {message: 'Hello world!'}
zvue.js:91 Dep.constructor -1-
zvue.js:58 Observer.walk -2- this: Observer {data: {...}, dep: Dep}
zvue.js:23 Vue.constructor -3- new Compiler(this, this.el) Vue {$options: {...}, $data: {...}, el: '#app', $el: div#app}
zvue.js:171 Compiler.compileText -1- result: (2) ['{{message}}', 'message', index: 0, input: '{{message}}--{{message}}', groups: undefined]
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:36 proxy -1- get data[key]: Hello world!
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:183 Compiler.compileText -2- new Watcher
zvue.js:117 Watcher.constructor -1- Dep.target = this Watcher {vm: Vue, key: 'message', callback: ƒ}
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:119 Watcher.constructor -2- this.oldVal = vm[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: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
zvue.js:171 Compiler.compileText -1- result: (2) ['{{message}}', 'message', index: 13, input: '{{message}}--{{message}}', groups: undefined]
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:36 proxy -1- get data[key]: Hello world!
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:183 Compiler.compileText -2- new Watcher
zvue.js:117 Watcher.constructor -1- Dep.target = this Watcher {vm: Vue, key: 'message', callback: ƒ}
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:119 Watcher.constructor -2- this.oldVal = vm[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: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=12345
log
app.message=12345
zvue.js:40 proxy -2- set data[key]= 12345
zvue.js:77 doDefProp -2- set data[key]= 12345
zvue.js:101 Dep.notify -3- : (8) [Watcher, Watcher, Watcher, Watcher, Watcher, Watcher, Watcher, Watcher]
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:36 proxy -1- get data[key]: 12345
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:126 Watcher.update -4- newVal: 12345
zvue.js:185 Compiler.compileText -3- tokens[pos] = newVal (3) ['Hello world!', '--', 'Hello world!'] 0 12345
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:36 proxy -1- get data[key]: 12345
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:126 Watcher.update -4- newVal: 12345
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:36 proxy -1- get data[key]: 12345
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:126 Watcher.update -4- newVal: 12345
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:36 proxy -1- get data[key]: 12345
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:126 Watcher.update -4- newVal: 12345
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:36 proxy -1- get data[key]: 12345
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:126 Watcher.update -4- newVal: 12345
zvue.js:185 Compiler.compileText -3- tokens[pos] = newVal (3) [12345, '--', 'Hello world!'] 2 12345
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:36 proxy -1- get data[key]: 12345
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:126 Watcher.update -4- newVal: 12345
zvue.js:72 doDefProp -1- get data[key] --> dep.addSub null
zvue.js:36 proxy -1- get data[key]: 12345
总结
本文实现了一个简易版Vue.js,主要实现了插值功能{{xxx}}。
核心实现包括:
Vue类
初始化时进行
数据代理、
响应式处理和
模板编译`;- 通过
Observer类实现数据劫持
,使用Dep类管理依赖
; Watcher类
作为观察者监听数据变化
;Compiler类解析
DOM模板并处理插值表达式。
关键机制包括:数据代理
使data属性可直接访问
,Object.defineProperty
实现响应式
(get/set),依赖收集和发布订阅
模式实现数据变化时的视图更新
。代码通过递归解析DOM节点
,匹配{{}}表达式并替换为对应的值,从执行日志可以看出其调用的路径,下一篇就来深入解析其中的依赖关系与实现原理。