【慢慢理解Vue的设计思想】

news2024/11/17 3:39:03

# 理解Vue的设计思想

  • MVVM框架的三要素:数据响应式、模板引擎及其渲染

  • 数据响应式:监听数据变化并在视图中更新

    • Object.defineProperty()
    • Proxy
  • 模版引擎:提供描述视图的模版语法

    • 插值:{{}}
    • 指令:v-bind,v-on,v-model,v-for,v-if
  • 渲染:如何将模板转换为html

    • 模板 => vdom => dom

# 数据响应式原理

数据变更能够响应在视图中,就是数据响应式。vue2中利用Object.defineProperty() 实现变更检 测。

简单实现

// 数据响应式:// Object.defineProperty()functiondefineReactive(obj, key, val){// val可能还是对象,此时我们需要递归observe(val)// 参数3是描述对象  Object.defineProperty(obj, key,{get(){      console.log('get', key);return val},set(newVal){if(newVal!== val){        console.log('set', key);// 防止newVal是对象,提前做一次observeobserve(newVal)        val= newVal}}})}functionobserve(obj){if(typeof obj!=='object'|| obj===null){return}// 遍历  Object.keys(obj).forEach(key=>defineReactive(obj, key, obj[key]))}// 对于新加入属性,需要单独处理他的响应式functionset(obj, key, val){defineReactive(obj, key, val)}const obj={foo:'foo',bar:'bar',baz:{a:1}}observe(obj)// defineReactive(obj, 'foo', 'foo')// obj.foo// obj.foo = 'fooooooooo'// obj.bar// obj.bar = 'barrrrrrrr'// obj.baz.a = '10'// obj.baz = {a: 10}// obj.baz.a = 100// 新添加一些属性// obj.dong = 'dong' // no okset(obj,'dong','dong')obj.dong// 前面的方法对于数组是不支持// 思路:拦截数组7个变更方法push、pop。。。,扩展他们,使他们在变更数据的同时// 额外的执行一个通知更新的任务
    
    

defineProperty() 不支持数组

解决数组数据的响应化

# Vue中的数据响应化

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title></head><body><div id="app"><h3>KVue</h3><p>{{counter}}</p><p k-text="counter"></p><p k-html="desc"></p></div><script src="kvue.js"></script><script>const app=newKVue({el:'#app',data:{counter:1,desc:'<span style="color:red">kvue可还行</span>'}})setInterval(()=>{      app.counter++},1000);</script></body></html>
    
    

# 原理分析

  • new Vue()首先执行初始化,对data执行响应化处理,这个过程发生在Observer
  • 同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compile
  • 同时定义一个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数
  • 由于data的某个key在一个视图中可能出现多次,所以每个key都需要一个管家Dep来管理多个Watcher
  • 将来data中数据一旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数

# 涉及类型介绍

  • KVue:框架构造函数
  • Observer:执行数据响应化(分辨数据是对象还是数组)
  • Compile:编译模板,初始化视图,收集依赖(更新函数、watcher创建)
  • Watcher:执行更新函数(更新dom)
  • Dep:管理多个Watcher,批量更新

# 实现Vue

# 框架构造函数:执行初始化

执行初始化,对data执行响应化处理,kvue.js

functionobserve(obj){if(typeof obj!=='object'|| obj===null){return}// 响应式newObserver(obj)}
    
    
functiondefineReactive(obj, key, val){}classKVue{constructor(options){this.$options= options;this.$data= options.data;observe(this.$data)}}classObserver{constructor(value){this.value= valuethis.walk(value);}walk(obj){    Object.keys(obj).forEach(key=>{defineReactive(obj, key, obj[key])})}}
    
    

$data做代理

classKVue{constructor(options){// 。。。proxy(this,'$data')}}functionproxy(vm){  Object.keys(vm.$data).forEach(key=>{    Object.defineProperty(vm, key,{get(){return vm.$data[key];},set(newVal){        vm.$data[key]= newVal;}});})}
    
    

# 编译 Compile

编译模板中vue模板特殊语法,初始化视图、更新视图

1. 初始化视图

根据节点类型编译

classCompile{constructor(el, vm){this.$vm= vm;this.$el= document.querySelector(el);if(this.$el){this.compile(this.$el);}}  console.log("编译元素"+ node.nodeName);}elseif(this.isInterpolation(node)){  console.log("编译插值文本"+ node.textContent);}if(node.childNodes&& node.childNodes.length>0){this.compile(node);}});}isElement(node){return node.nodeType==1;}isInterpolation(node){return node.nodeType==3&&/\{\{(.*)\}\}/.test(node.textContent);}}
    
    

编译插值

compile(el){// ...}elseif(this.isInerpolation(node)){// console.log("编译插值文本" + node.textContent); this.compileText(node);}});}compileText(node){  console.log(RegExp.$1);  node.textContent=this.$vm[RegExp.$1];}
    
    

编译元素

compile(el){//...if(this.isElement(node)){// console.log("编译元素" + node.nodeName); this.compileElement(node)}}compileElement(node){let nodeAttrs= node.attributes;  Array.from(nodeAttrs).forEach(attr=>{let attrName= attr.name;let exp= attr.value;if(this.isDirective(attrName)){let dir= attrName.substring(2);this[dir]&&this[dir](node, exp);}});}isDirective(attr){return attr.indexOf("k-")==0;}text(node, exp){  node.textContent=this.$vm[exp];}
    
    

# 依赖收集

视图中会用到data中某key,这称为依赖。同一个key可能出现多次,每次都需要收集出来用一个

Watcher来维护它们,此过程称为依赖收集。

多个Watcher需要一个Dep来管理,需要更新时由Dep统一通知。 看下面案例,理出思路:

newVue({template:`<div> <p>{{name1}}</p>            <p>{{name2}}</p><p>{{name1}}</p> <div>`,data:{name1:'name1',name2:'name2'}});
    
    

实现思路

  1. defineReactive时为每一个key创建一个Dep实例
  2. 初始化视图时读取某个key,例如name1,创建一个watcher1
  3. 由于触发name1的getter方法,便将watcher1添加到name1对应的Dep中
  4. 当name1更新,setter触发时,便可通过对应Dep通知其管理所有Watcher更新

创建Watcher,kvue.js

const watchers=[];//临时用于保存watcher测试用// 监听器:负责更新视图 class Watcher {constructor(vm, key, updateFn){// kvue实例this.vm= vm;// 依赖keythis.key= key;// 更新函数this.updateFn= updateFn;// 临时放入watchers数组  watchers.pus// 更新 update() {this.updateFn.call(this.vm,this.vm[this.key]);}}
    
    

编写更新函数、创建watcher

// 调用update函数执插值文本赋值 compileText(node) {// console.log(RegExp.$1);// node.textContent = this.$vm[RegExp.$1]; this.update(node, RegExp.$1, 'text')}text(node, exp){this.update(node, exp,'text')}html(node, exp){this.update(node, exp,'html')}update(node, exp, dir){const fn=this[dir+'Updater'] fn&&fn(node,this.$vm[exp])newWatcher(this.$vm, exp,function(val){    fn&&fn(node, val)})}textUpdater(node, val){  node.textContent= val;}htmlUpdater(node, val){  node.innerHTML= val}
    
    

声明Dep

classDep{constructor(){		this.deps=[]}		addDep(dep){this.deps.push(dep)}notify(){this.deps.forEach(dep=> dep.update());}}
    
    

创建watcher时触发getter

classWatcher{constructor(vm, key, updateFn){    Dep.target=this;this.vm[this.key];    Dep.target=null;}}
    
    

依赖收集,创建Dep实例

defineReactive(obj, key, val){this.observe(val);const dep=newDep() Object.defineProperty(obj, key,{get(){      Dep.target&& dep.addDep(Dep.target);return val},set(newVal){if(newVal=== val)return dep.notify()}})}
    
    

# 完整代码

// 任务:// 1. 数据响应式:是data选项中的对象编程响应式的// 2.// 数据响应式:// Object.defineProperty()functiondefineReactive(obj, key, val){// val可能还是对象,此时我们需要递归observe(val)// 创建Dep实例,他和key一对一对应关系const dep=newDep()// 参数3是描述对象  Object.defineProperty(obj, key,{get(){// console.log('get', key);// 依赖收集:Dep.target就是当前新创建Watcher实例      Dep.target&& dep.addDep(Dep.target)return val},set(newVal){if(newVal!== val){        console.log('set', key);// 防止newVal是对象,提前做一次observeobserve(newVal)        val= newVal// 通知更新        dep.notify()}}})}functionobserve(obj){if(typeof obj!=='object'|| obj===null){return}// 响应式newObserver(obj)}// Observer: 辨别当前对象类型是纯对象还是数组,从而做不同响应式操作classObserver{constructor(value){this.value= value// 辨别类型if(Array.isArray(value)){// todo}else{this.walk(value)}}walk(obj){// 对象响应式    Object.keys(obj).forEach(key=>defineReactive(obj, key, obj[key]))}}// 代理函数:可以将$data代理到KVue的实例// vm是KVue实例functionproxy(vm){  Object.keys(vm.$data).forEach(key=>{// 为当前实例做代理,定义一些key和data相对应    Object.defineProperty(vm, key,{get(){return vm.$data[key]},set(newVal){        vm.$data[key]= newVal}})})}// KVue:解析选项,响应式、编译等等classKVue{constructor(options){this.$options= optionsthis.$data= options.data// 对data选项做响应式处理observe(this.$data)// 代理proxy(this)// 执行编译newCompile(options.el,this)}}// Compile: 遍历视图模板,解析其中的特殊模板语法为更新函数// new Compile(el, vm)classCompile{constructor(el, vm){// el:宿主元素选择器// vm:KVue的实例this.$vm= vm;this.$el= document.querySelector(el)// 执行编译this.compile(this.$el)}compile(el){// 遍历子元素,判断他们类型并做响应处理    el.childNodes.forEach(node=>{// 判断类型if(node.nodeType===1){// 元素节点// console.log('编译元素', node.nodeName);this.compileElement(node)}elseif(this.isInter(node)){// 文本节点// console.log('文本节点', node.textContent);// double killthis.compileText(node)}// 递归子节点if(node.childNodes){this.compile(node)}})}// 是否插值绑定isInter(node){return node.nodeType===3&&/\{\{(.*)\}\}/.test(node.textContent)}// 绑定表达式解析compileText(node){// 获取匹配表达式 RegExp.$1,比如counter, vm['counter']// node.textContent = this.$vm[RegExp.$1]this.update(node, RegExp.$1,'text')}// 编译元素节点:判断指令和事件compileElement(node){// 获取属性const attrs= node.attributes    Array.from(attrs).forEach(attr=>{// k-text="counter"// attr是一个对象{name:'k-text', value: 'counter'}const{ name, value}= attr// 判断是否是指令if(name.indexOf('k-')===0){// 截取指令const dir= name.substring(2)// 指令指令this[dir]&&this[dir](node, value)}// 判断是否是事件 @// else if() {// }})}// k-text文本更新text(node, exp){this.update(node, exp,'text')}// k-htmlhtml(node, exp){this.update(node, exp,'html')}// update方法,高阶函数:除了执行dom操作,创建一个额外watcher实例// dir是指令名称update(node, exp, dir){// 获取更新方法const fn=this[dir+'Updater']// 初始化,让用户看到首次渲染结果    fn&&fn(node,this.$vm[exp])// 创建watcher实例newWatcher(this.$vm, exp,val=>{      fn&&fn(node, val)})}// dom执行方法textUpdater(node, value){    node.textContent= value}htmlUpdater(node, value){    node.innerHTML= value}}// Watcher: 管理依赖,执行更新// const watchers = []classWatcher{// vm是KVue实例// key是data中对应的key名称// fn是更新函数,他知道怎么更新domconstructor(vm, key, fn){this.vm= vmthis.key= keythis.fn= fn// watchers.push(this)// 建立dep和watcher之间的关系    Dep.target=thisthis.vm[this.key]// 读一下key的值触发其getter    Dep.target=null}// 更新函数,由Dep调用update(){// 更新函数调用,设置上下文问KVue实例,传参是当前最新值this.fn.call(this.vm,this.vm[this.key])}}// Dep: 管理多个watcher实例,当对应key发生变化时,通知他们更新classDep{constructor(){this.deps=[]}addDep(dep){// 添加订阅者,dep就是watcher实例this.deps.push(dep)}// 通知更新notify(){this.deps.forEach(w=> w.update())}}
    
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1800074.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

能把试卷上的字消除的软件有哪些?推荐三款好用的

能把试卷上的字消除的软件有哪些&#xff1f;在数字化时代&#xff0c;我们越来越依赖科技手段来解决生活中的各种问题。其中&#xff0c;试卷上的字消除问题&#xff0c;就是一个备受关注的痛点。幸运的是&#xff0c;现在市面上已经出现了多款能够轻松消除试卷上字迹的软件&a…

AI日报|智谱AI再降价,同时开源9B系列模型;国内外气象大模型竞逐升级

文章推荐 AI日报&#xff5c;国产大模型迎来新卷王&#xff0c;天工MoE全球首用4090推理&#xff0c;马斯克计划豪掷90亿购买GPU AI日报&#xff5c;斯坦福团队被曝抄袭国内大模型已删库跑路&#xff01;英伟达打破摩尔定律&#xff0c;机器人时代到来 智谱AI全模型矩阵进行…

英码科技推出鸿蒙边缘计算盒子:提升国产化水平,增强AI应用效能,保障数据安全

当前&#xff0c;随着国产化替代趋势的加强&#xff0c;鸿蒙系统Harmony OS也日趋成熟和完善&#xff0c;各行各业都在积极拥抱鸿蒙&#xff1b;那么&#xff0c;边缘计算要加快实现全面国产化&#xff0c;基于鸿蒙系统开发AI应用势在必行。 关于鸿蒙系统及其优势 鸿蒙系统是华…

离线环境下安装NVIDIA驱动、CUDA(HUAWEI Kunpeng 920 + NVIDIA A100 + Ubuntu 20.04 LTS)

文章目录 前言 一、基础环境 1.1、处理器型号 1.2、英伟达显卡型号 1.3、操作系统 1.4、软件环境 二、取消内核自动升级 2.1、查看正在使用的内核版本 2.2、查看正在使用的内核包 2.3、禁止内核更新 三、配置本地apt源 3.1、挂载iso镜像文件 3.2、配置apt源 3.3、…

分享万能点击器免费版,吾爱大佬出品,这个太赞了!

小伙伴们&#xff01;阿星又来给大家推荐神奇的小软件啦&#xff01;这次的主角可是个神器——鼠标连点器&#xff01;你听过没&#xff1f;这玩意儿简直是个“自动小助手”&#xff0c;让你的鼠标在屏幕上飞舞&#xff0c;点得飞快&#xff0c;解放你的双手&#xff0c;让你网…

Docker镜像加载原理(Union文件系统)

联合文件系统 Union文件系统&#xff0c;是一种轻量级的分层高性能服务系统&#xff0c;支持对文件系统的修改来进行一层一层的叠加&#xff0c;同时将不同目录挂载到同一个虚拟文件系统中&#xff0c;Union文件系统是Docker镜像的基础&#xff0c;通过分层来进行集成&am…

Linux系统下 安装 Nginx

一、下载Nginx安装包 压缩包下载地址&#xff1a;nginx: download 服务器有外网&#xff0c;可直接使用命令下载 wget -c https://nginx.org/download/nginx-1.24.0.tar.gz 二、安装Nginx 1、解压 tar -zxvf nginx-1.24.0.tar.gz 2、安装Nginx所需依赖 yum install -y gc…

c++中, 直接写浮点数, 是float 还是 double?

如果直接一个浮点数, 那么他默认是float还是double呢? 测试用例 #include <iostream> using namespace std;int main() {auto x 0.2;float f 0.2;double d 0.2;cout << "x Size : " << sizeof(x) << " bytes" << endl…

React@16.x(21)渲染流程-更新

目录 1&#xff0c;更新的2种场景2&#xff0c;节点更新3&#xff0c;对比 diff 更新3.1&#xff0c;React 的假设3.1.2&#xff0c;key 2.1&#xff0c;找到了对比的目标2.1.1&#xff0c;节点类型一致1&#xff0c;空节点2&#xff0c;DOM节点3&#xff0c;文本节点4&#xf…

width: 100%和 width: 100vw这两种写法有什么区别

width: 100%; 和 width: 100vw; 是两种不同的 CSS 写法&#xff0c;它们在实际应用中会有不同的效果。以下是这两种写法的主要区别&#xff1a; width: 100%; 定义&#xff1a;将元素的宽度设置为其包含块&#xff08;通常是父元素&#xff09;宽度的 100%。效果&#xff1a;元…

c++调用动态库LNK2019无法解析的外部符号LNK1120无法解析的外部命令

严重性 代码 说明 项目 文件 行 禁止显示状态 错误 LNK1120 6 个无法解析的外部命令 ConsoleApplication1 D:\vs_qt_project\ConsoleApplication1\x64\Debug\ConsoleApplication1.exe 1 严重性 代码 说明 项目 文件 行 …

Open vSwitch 数据包处理流程

一、Open vSwitch 数据包转发模式 Open vSwitch 根据不同的模块使用&#xff0c;主要分为两种数据包的转发模式&#xff1a;Datapath 模式和 DPDK 模式&#xff0c;这两种模式的主要区别在于&#xff1a; Datapath 模式&#xff1a; 使用内核空间的网络栈进行数据包的转发性能相…

M1Pro 使用跳板机

Mac (M1 Pro) 通过Iterm2 使用跳板机 1、由于堡垒机&#xff08;跳板机&#xff09;不能支持mac系统终端工具&#xff0c;只支持xshell等win生态。所以我们需要先安装iterm2 装iterms教程 这里头对rz、sz的配置不详细。我们可以这样配置&#xff1a; where iterm2-send-zmod…

WebGL技术的应用场景

WebGL&#xff08;Web Graphics Library&#xff09;是一种用于在网页上渲染交互式3D和2D图形的技术&#xff0c;而无需使用插件。它基于OpenGL ES 2.0 API&#xff0c;可以被JavaScript调用来绘制图形。WebGL的应用场景相当广泛&#xff0c;涵盖从游戏到教育&#xff0c;再到专…

Windows取证分析 | 如何最大程度提升分析效率

本文由安全研究人员Amr Ashraf发表于Cyber5w的官方博客&#xff0c;研究人员在本文中讨论了如何对可疑设备中的内存映像进行安全调查&#xff0c;并利用了Volatility 3和MemProcFS来最大程度提升Windows取证分析的工作效率。 介绍 内存取证是任何计算机取证分析人员的必备技能…

R语言探索与分析14-美国房价及其影响因素分析

一、选题背景 以多元线性回归统计模型为基础&#xff0c;用R语言对美国部分地区房价数据进行建模预测&#xff0c;进而探究提高多元回 归线性模型精度的方法。先对数据进行探索性预处理&#xff0c;随后设置虚拟变量并建模得出预测结果&#xff0c;再使用方差膨胀因子对 多重共…

磁盘怎么分区?3 款最佳免费磁盘分区软件

您可能已经注意到&#xff0c;大多数计算机至少有 2 个分区&#xff1a;一个安装 Windows 操作系统和程序&#xff08;C:&#xff09;&#xff0c;另一个安装其他文件&#xff08;D:&#xff09;。 默认情况下&#xff0c;计算机只有一个硬盘和一个分区。建议创建 2 个或更多分…

kafka-生产者监听器(SpringBoot整合Kafka)

文章目录 1、生产者监听器1.1、创建生产者监听器1.2、创建生产者拦截器1.3、发送消息测试1.4、使用Java代码创建主题分区副本1.5、application.yml配置----v1版1.6、屏蔽 kafka debug 日志 logback.xml1.7、引入spring-kafka依赖1.8、控制台日志 1、生产者监听器 1.1、创建生产…

计算机组成结构—IO方式

目录 一、程序查询方式 1. 程序查询基本流程 2. 接口电路 3. 接口工作过程 二、程序中断方式 1. 程序中断基本流程 2. 接口电路 3. I/O 中断处理过程 三、DMA 方式 1. DMA 的概念和特点 2. DMA 与 CPU 的访存冲突 3. DMA 接口的功能 4. DMA 接口的组成 5. DMA 的…

阿里 Qwen2 模型开源,教你如何将 Qwen2 扩展到百万级上下文

本次开源的 Qwen2 模型包括 5 个尺寸&#xff0c;分别是 0.5B、1.5B、7B、72B、57B&#xff0c;其中 57B 的属于 MoE 模型&#xff08;激活参数 14B&#xff09;&#xff0c;其余为 Dense 模型&#xff0c;本篇文章会快速介绍下各个尺寸模型的情况&#xff0c;然后重点介绍下如…