动手实现简单Vue.js ,探索Vue原理
文章目录
- 前言
- index.html
- zvue.js
- 运行
- 动态响应
- 日志
- 总结
前言
实现简单的 vue.js, 探索 vue 实现原理,这里仅实现 插值功能{{xxx}}
index.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
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 观察dataconsole.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.xxget() {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}} -> valuecompileText(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("");}}
}
运行
动态响应
日志
初始运行
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
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节点
,匹配{{}}表达式并替换为对应的值,从执行日志可以看出其调用的路径,下一篇就来深入解析其中的依赖关系与实现原理。