当前位置: 首页 > news >正文

动手实现简单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}}。
核心实现包括:

  1. Vue类初始化时进行数据代理响应式处理模板编译`;
  2. 通过Observer类实现数据劫持,使用Dep类管理依赖
  3. Watcher类作为观察者监听数据变化
  4. Compiler类解析DOM模板并处理插值表达式。

关键机制包括:数据代理使data属性可直接访问Object.defineProperty实现响应式(get/set),依赖收集和发布订阅模式实现数据变化时的视图更新。代码通过递归解析DOM节点,匹配{{}}表达式并替换为对应的值,从执行日志可以看出其调用的路径,下一篇就来深入解析其中的依赖关系与实现原理。

http://www.dtcms.com/a/427633.html

相关文章:

  • UNIX下C语言编程与实践18-UNIX 文件存储原理:目录、i 节点、数据块协同存储文件的过程
  • 珠宝怎么做网站wordpress 活动报名插件
  • 除自身以外数组的乘积
  • 爬虫逆向--Day25Day26--原型链补环境
  • 拍拍灯电路(用咪头识别拍拍动作)
  • 极限!ubuntu系统联网
  • 第三章 字典与集合
  • 网站设计的价格沪深300指数基金
  • Java-01-基础篇-JDK日志(JUL)
  • (基于江协科技)51单片机入门:7.LED点阵屏
  • 江协科技 CAN总线入门课程(错误处理)
  • 网站的建设与规划方案企业网站建设要素
  • antdv- Tooltip 文字提示组件
  • 算法题(222):摆花
  • 如何向alexa提交网站wordpress custom login
  • SpringCloud电商微服务项目衣拉客搭建指南
  • dev c++工具下载 dev c++安装包下载 dev c++软件网盘资源分享
  • 如何去掉Excel多余空行
  • 房地产网站欣赏万网空间管理
  • 做多语言网站多少钱免费网站安全软件大全下载安装
  • 【密码学实战】openHiTLS X509命令行工具: 数字证书生成与转换
  • 从“减塑”到“降碳”新天力“2R”模式推动行业低碳转型
  • AFSim雷达显控一体化
  • 网站建设类型智盈中心网站建设
  • 零基础从头教学Linux(Day 45)
  • 网站策划方案论文wordpress软件网站模板下载
  • 大数据变长存储算法
  • Ubuntu22.04安装Samba服务器
  • NACHI那智焊接机器人智能气阀
  • 网站怎么申请怎么注册交友软件网站建设