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

深入理解 Vue.js 原理

文章目录

    • 前言
    • zvue.js 架构图
    • Object.defineProperty()
    • 读取数据:vm[key]
    • 更新数据
    • 完整 zvue.js 代码

前言

前一篇 简单实现vue.js 一文中,我们实现一个简化版的 Vue.js(zvue.js),本文来深入研究 vue 结构及实现原理

zvue.js 架构图

先上一张 zvue.js 架构图,本文我们就对此详细展开研究:

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 字段

  // data 来源于vue.$dataproxy(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;},});});}

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 的变化。

  // 会放到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 到观察者列表

        // 加入列表Dep.target && dep.addSub(Dep.target);

更新 key(set)时候通知 监视器 Watcher 进行 token 流替换,从而实现页面渲染更新。

// 通知监视器更新 token 流,实现页面渲染更新dep.notify();

在浏览器中可以查看到 $data 的信息,一点开里面的 message,立刻执行了装饰逻辑,打印出日志 doDefProp -1- get data[key] --> dep.addSub null

读取数据:vm[key]

Watcher 构造函数中 有一句 this.oldVal = vm[key];

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

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

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 观察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.$dataproxy(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) =>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}} -> 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("");}}
}
http://www.dtcms.com/a/486106.html

相关文章:

  • 基于bert-base-chinese的外卖评论情绪分类项目
  • OpenSSL EVP编程介绍
  • 网站服务器组建中国国际贸易网站
  • 上新!功夫系列高通量DPU卡 CONFLUX®-2200P 全新升级,带宽升 40% IOPS提60%,赋能多业务场景。
  • Spring Boot 3零基础教程,properties文件中配置和类的属性绑定,笔记14
  • 以数据智能重构 OTC 连锁增长逻辑,覆盖网络与合作生态双维赛跑
  • 【推荐100个unity插件】基于节点的程序化无限地图生成器 —— MapMagic 2
  • 71_基于深度学习的布料瑕疵检测识别系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
  • 工控机做网站服务器网络模块
  • Mac——文件夹压缩的简便方法
  • Playwright自动化实战一
  • 电商网站开发面临的技术问题做seo网站诊断书怎么做
  • 【Qt】QTableWidget 自定义排序功能实现
  • WPF 疑点汇总2.HorizontalAlignment和 HorizontalContentAlignment
  • 【Qt】3.认识 Qt Creator 界面
  • 垂直网站建设付费小说网站怎么做
  • PDFBox - PDDocument 与 byte 数组、PDF 加密
  • 【Pytorch】分类问题交叉熵
  • 如何轻松删除 realme 手机中的联系人
  • Altium Designer怎么制作自己的集成库?AD如何制作自己的原理图库和封装库并打包生成库文件?AD集成库制作好后如何使用丨AD集成库使用方法
  • Jackson是什么
  • 代码实例:Python 爬虫抓取与解析 JSON 数据
  • 襄阳建设网站首页百度知识营销
  • 山东住房和城乡建设厅网站电话开发软件都有哪些
  • AbMole| Yoda1( M9372;GlyT2-IN-1; Yoda 1)
  • LLM监督微调SFT实战指南(Qwen3-0.6B-Base)
  • 【基础算法】多源 BFS
  • *@UI 视角下主程序与子程序的菜单页面架构及关联设计
  • Virtio 半虚拟化技术解析
  • 网站设计怎么好看律师做网络推广哪个网站好