vue2 原理【详解】MVVM、响应式、模板编译、虚拟节点 vDom、diff 算法

简介: vue2 原理【详解】MVVM、响应式、模板编译、虚拟节点 vDom、diff 算法

vue 的设计模式 —— MVVM

  • M —— Model 模型,即数据
  • V —— View 视图,即DOM渲染
  • VM —— ViewModel 视图模型,用于实现Model和View的通信,即数据改变驱动视图渲染,监听视图事件修改数据

初次渲染

  1. 将模板编译为 render 函数 ( webpack 中使用的 vue-loader 插件在开发环境启动项目时会完成编译)
  2. 触发响应式,监听 data 属性触发 getter 和 setter 方法 (主要是getter 方法)
  3. 执行 render 函数,生成 vnode ,执行 patch(elem, vnode) 完成 DOM 渲染

更新过程【需会画和讲解图】

  1. 修改 data,触发 setter 方法
  2. 重新执行 render 函数,生成 newVnode
  3. 执行 patch(vnode,newVnode) 更新发生变化的 DOM 节点

【重点】异步渲染

vue 的更新过程,是一种异步渲染,即并不是每一点 data 的改变都会立马触发视图更新, 而是会汇总 data 的修改,再一次性更新视图,这样可以减少 DOM 的操作次数,提高性能。

vue 原理的三大核心

一、响应式

vue 的响应式机制是在vue 实例初始化时建立的,即 data 函数中定义的变量,在页面初始化后,都具有响应式对于vue 实例初始化之后新增的属性,不具有响应式,解决方案是改用 $set 的方式新增属性。

监听 data 的变化

【核心】 API-Object.defineProperty

此时只能监听到对象的第一层属性,而无法实现更深层次属性变化的监听。

实现深度监听

  • 深层对象属性的监听,通过递归遍历深层对象的属性实现
  • 深层数组的监听,通过变更数组原型(为所有改变数组的 api 中加入更新视图),再递归遍历深层数组实现。
// 触发更新视图
function updateView() {
  console.log("视图更新");
}

// 重新定义数组原型
const oldArrayProperty = Array.prototype;
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
["push", "pop", "shift", "unshift", "splice"].forEach((methodName) => {
  arrProto[methodName] = function () {
    updateView(); // 触发视图更新
    oldArrayProperty[methodName].call(this, ...arguments);
  };
});

// 重新定义属性,监听起来
function defineReactive(target, key, value) {
  // 深度监听
  observer(value);

  // 核心 API
  Object.defineProperty(target, key, {
    get() {
      return value;
    },
    set(newValue) {
      if (newValue !== value) {
        // 深度监听
        observer(newValue);

        // 设置新值
        // 注意,value 一直在闭包中,此处设置完之后,再 get 时会获取最新的值
        value = newValue;

        // 触发更新视图
        updateView();
      }
    },
  });
}

// 监听对象属性
function observer(target) {
  // 不是对象或数组,无需深度监听
  if (typeof target !== "object" || target === null) {
    return target;
  }

  // 若是数组,则修改为自定义的添加了视图刷新的数组原型
  if (Array.isArray(target)) {
    target.__proto__ = arrProto;
  }

  // 重新定义各个属性(for in 也可以遍历数组)
  for (let key in target) {
    defineReactive(target, key, target[key]);
  }
}

// 准备数据
const data = {
  name: "张三",
  age: 20,
  info: {
    address: "北京", // 需要深度监听
  },
  nums: [10, 20, 30], // 需要深度监听
};

// 监听数据
observer(data);

// 测试
data.name = "李四"; // 无需深度监听
data.age = 21; // 无需深度监听
data.x = "100"; // 新增属性,监听不到 —— 需用 Vue.set
delete data.name; // 删除属性,监听不到 —— 需用 Vue.delete
data.info.address = "上海"; // 对象属性的属性,需要深度监听
data.nums.push(4); // 数组需要深度监听

Object.defineProperty 的缺点

  • 深度监听,需要递归到底,一次性计算量大
  • 无法监听属性的新增和删除,这会导致以下操作无响应式:
  • 对象新增属性
  • 对象删除属性
  • 通过数组下标修改数组元素的值
  • 修改数组的长度

为了弥补以上操作无响应式的缺陷,vue 补充了 set 和 delete 方法。

Vue.set() 和 this.$set() 这两个api的实现原理基本一模一样,都是使用了set函数

$set 的响应式原理

this.$set(this.arr, "3", 7)

对于数组,$set 的参数为数组、数组下标、新的值,通过调用被 vue 改造过的添加了视图更新的 splice 方法实现响应式,相关vue 源码如下:

// 判断操作目标是否是数组,传入的数组下标是否规范
if (Array.isArray(target) && isValidArrayIndex(key)) {
  // 若传入的数组下标超过数组长度,则将数组长度增长为传入的下标,以防后续调用splice方法时因下标超出数组长度而报错。
  target.length = Math.max(target.length, key)
  // 使用添加了视图更新的 splice 方法实现响应式
  target.splice(key, 1, val)
  return val
 }
this.$set(this.obj, "新的属性", "新增的属性的值");
 
  • 对于对象,$set 的参数为对象、新的属性、新增的属性的值,通过对新增属性添加深度监听实现响应式,相关vue 源码如下:
// 判断如果key本来就是对象中的一个属性,并且key不是Object原型上的属性, 则此属性已添加过响应式,直接修改值即可。
 if (key in target && !(key in Object.prototype)) {
  target[key] = val
  return val
 }
 // 获取 target对象的 __ob__ 属性
 const ob = (target: any).__ob__
 // 若 target对象是vue实例对象或者是根数据对象,则抛出错误警告。
 if (target._isVue || (ob && ob.vmCount)) {
  process.env.NODE_ENV !== 'production' && warn(
   'Avoid adding reactive properties to a Vue instance or its root $data ' +
   'at runtime - declare it upfront in the data option.'
  )
  return val
 }
 // 若 target 对象的 __ob__ 属性不存在,则 target 不是响应式对象,无需添加响应式监听,直接新增属性赋值即可。( vue给响应式对象都加了 __ob__ 属性,如果一个对象有 __ob__ 属性,则说明这个对象是响应式对象)
 if (!ob) {
  target[key] = val
  return val
 }
 // 给新属性添加响应式监听
 defineReactive(ob.value, key, val)
 // 触发视图更新
 ob.dep.notify()
 return val

二、模板编译

vue 文件中支持指令、插值、JS 表达式,还能实现判断、循环,大大便捷了开发,但无法在浏览器中渲染,需要先将其转换成 JS 代码才行,这个转换的过程,即模板编译。

编译过程

  1. 借助插件 vue-template-compiler 将vue 文件编译成 render 函数
  2. 执行 render 函数,返回 vnode
  3. 基于 vnode 执行 patch 和 diff ,完成 DOM 渲染

演示代码

const compiler = require('vue-template-compiler')
const template = `<p>{{message}}</p>`
const res = compiler.compile(template)
console.log(res.render)

得到函数

with(this){return _c('p',[_v(_s(message))])}
  • _c 对应插件内定义的函数 createElement
  • _v 对应插件内定义的函数 createTextVNode
  • _s 对应插件内定义的函数 toString

即实现了模板向 JS 的转换。

with 语法

  • 改变 {} 内自由变量的查找规则,将其当做 obj 属性来查找
  • 如果找不到匹配的 obj 属性,就会报错
  • with 要慎用,它打破了作用域规则,易读性变差

编译形式

  • 在 webpack 中使用的 vue-loader 插件,在开发环境启动项目时,就完成了模板的编译(提升了渲染效率)
  • vue 组件可以用 template 写法,也可以直接用 render 函数(react 中全是 render 函数 )

三、虚拟节点 vDom

数据变化驱动视图更新,就需要执行DOM 操作重新渲染视图,但DOM 操作非常耗费性能,怎样提升性能呢?

解决思路:使用虚拟节点 vDom,即用 JS 模拟 DOM 结构,计算出最小的变更,更新 DOM。

因为 JS 的执行速度比DOM 操作快得多!

通过 h 函数生成 vnode

  • 初次渲染【增】
patch(container,vnode);

在目标容器中,渲染节点

  • 更新视图【改】
patch(vnode,newVnode);

用新节点,替代旧节点

只会重新渲染新旧节点中有差异的部分,不会重新渲染整个节点。

  • 销毁视图【删】
patch(newVnode,null);

用 null 替代目标节点

【核心】diff 算法

用于计算出 vDom 的最小变更(即比较出新旧 DOM 树的差异)

树 diff 的时间复杂度为 O(n^3)

  1. 遍历 tree1
  2. 遍历 tree2
  3. 排序

1000 个节点,要计算1亿次,算法不可用!

改用 diff 算法将时间复杂度降为 O(n)

  • 只比较同一层级,不跨级比较

  • tag 不相同,则直接删掉重建,不再深度比较

  • tag 和 key,两者都相同,则认为是相同节点,不再深度比较

  • 不使用key,则所有元素会先移除,再添加
    若使用key,则若存在未改变的元素,只需进行移动即可。
    若key 使用 index,则 key 的值为 0,1,2,3,4……,则若元素的顺序发生改变时,会出现问题。

相关的重要函数

  • patchVnode
  • addVnodes
  • removeVnodes
  • updatèChildren
目录
相关文章
|
8天前
|
NoSQL 算法 安全
分布式锁—1.原理算法和使用建议
本文主要探讨了Redis分布式锁的八大问题,包括非原子操作、忘记释放锁、释放其他线程的锁、加锁失败处理、锁重入问题、锁竞争问题、锁超时失效及主从复制问题,并提供了相应的优化措施。接着分析了Redis的RedLock算法,讨论其优缺点以及分布式专家Martin对其的质疑。此外,文章对比了基于Redis和Zookeeper(zk)的分布式锁实现原理,包括获取与释放锁的具体流程。最后总结了两种分布式锁的适用场景及使用建议,指出Redis分布式锁虽有性能优势但模型不够健壮,而zk分布式锁更稳定但部署成本较高。实际应用中需根据业务需求权衡选择。
|
2月前
|
机器学习/深度学习 数据采集 算法
短视频到底如何推荐的?深度剖析视频算法推送原理详细且专业的解读-优雅草卓伊凡-【01】短视频算法推荐之数据收集
短视频到底如何推荐的?深度剖析视频算法推送原理详细且专业的解读-优雅草卓伊凡-【01】短视频算法推荐之数据收集
145 12
短视频到底如何推荐的?深度剖析视频算法推送原理详细且专业的解读-优雅草卓伊凡-【01】短视频算法推荐之数据收集
|
2月前
|
算法 安全 搜索推荐
套用算法模板备案审核问题增多的原因及解决建议
随着算法备案要求的完善,企业常因使用网上廉价模板而遭遇审核通过率低、问题增多的困境。本文分析了审核不通过的原因,包括模板缺乏针对性、审核标准严格、审核人员主观差异及企业准备不足等,并提出建议:深入了解备案要求、准备详尽材料、避免通用模板、寻求专业帮助。备案后还需持续合规管理,确保算法服务安全运行。
|
3月前
|
传感器 算法 物联网
基于粒子群算法的网络最优节点部署优化matlab仿真
本项目基于粒子群优化(PSO)算法,实现WSN网络节点的最优部署,以最大化节点覆盖范围。使用MATLAB2022A进行开发与测试,展示了优化后的节点分布及其覆盖范围。核心代码通过定义目标函数和约束条件,利用PSO算法迭代搜索最佳节点位置,并绘制优化结果图。PSO算法灵感源于鸟群觅食行为,适用于连续和离散空间的优化问题,在通信网络、物联网等领域有广泛应用。该算法通过模拟粒子群体智慧,高效逼近最优解,提升网络性能。
191 16
|
3月前
|
运维 NoSQL 算法
【📕分布式锁通关指南 04】redis分布式锁的细节问题以及RedLock算法原理
本文深入探讨了基于Redis实现分布式锁时遇到的细节问题及解决方案。首先,针对锁续期问题,提出了通过独立服务、获取锁进程自己续期和异步线程三种方式,并详细介绍了如何利用Lua脚本和守护线程实现自动续期。接着,解决了锁阻塞问题,引入了带超时时间的`tryLock`机制,确保在高并发场景下不会无限等待锁。最后,作为知识扩展,讲解了RedLock算法原理及其在实际业务中的局限性。文章强调,在并发量不高的场景中手写分布式锁可行,但推荐使用更成熟的Redisson框架来实现分布式锁,以保证系统的稳定性和可靠性。
108 0
【📕分布式锁通关指南 04】redis分布式锁的细节问题以及RedLock算法原理
|
4月前
|
机器学习/深度学习 算法 PyTorch
深度强化学习中SAC算法:数学原理、网络架构及其PyTorch实现
软演员-评论家算法(Soft Actor-Critic, SAC)是深度强化学习领域的重要进展,基于最大熵框架优化策略,在探索与利用之间实现动态平衡。SAC通过双Q网络设计和自适应温度参数,提升了训练稳定性和样本效率。本文详细解析了SAC的数学原理、网络架构及PyTorch实现,涵盖演员网络的动作采样与对数概率计算、评论家网络的Q值估计及其损失函数,并介绍了完整的SAC智能体实现流程。SAC在连续动作空间中表现出色,具有高样本效率和稳定的训练过程,适合实际应用场景。
944 7
深度强化学习中SAC算法:数学原理、网络架构及其PyTorch实现
|
5月前
|
算法 Java 数据库
理解CAS算法原理
CAS(Compare and Swap,比较并交换)是一种无锁算法,用于实现多线程环境下的原子操作。它通过比较内存中的值与预期值是否相同来决定是否进行更新。JDK 5引入了基于CAS的乐观锁机制,替代了传统的synchronized独占锁,提升了并发性能。然而,CAS存在ABA问题、循环时间长开销大和只能保证单个共享变量原子性等缺点。为解决这些问题,可以使用版本号机制、合并多个变量或引入pause指令优化CPU执行效率。CAS广泛应用于JDK的原子类中,如AtomicInteger.incrementAndGet(),利用底层Unsafe库实现高效的无锁自增操作。
199 0
理解CAS算法原理
|
5月前
|
存储 人工智能 缓存
【AI系统】布局转换原理与算法
数据布局转换技术通过优化内存中数据的排布,提升程序执行效率,特别是对于缓存性能的影响显著。本文介绍了数据在内存中的排布方式,包括内存对齐、大小端存储等概念,并详细探讨了张量数据在内存中的排布,如行优先与列优先排布,以及在深度学习中常见的NCHW与NHWC两种数据布局方式。这些布局方式的选择直接影响到程序的性能,尤其是在GPU和CPU上的表现。此外,还讨论了连续与非连续张量的概念及其对性能的影响。
186 3
|
4月前
|
传感器 算法
基于GA遗传优化的WSN网络最优节点部署算法matlab仿真
本项目基于遗传算法(GA)优化无线传感器网络(WSN)的节点部署,旨在通过最少的节点数量实现最大覆盖。使用MATLAB2022A进行仿真,展示了不同初始节点数量(15、25、40)下的优化结果。核心程序实现了最佳解获取、节点部署绘制及适应度变化曲线展示。遗传算法通过初始化、选择、交叉和变异步骤,逐步优化节点位置配置,最终达到最优覆盖率。
|
9天前
|
算法 数据安全/隐私保护
基于GA遗传算法的悬索桥静载试验车辆最优布载matlab仿真
本程序基于遗传算法(GA)实现悬索桥静载试验车辆最优布载的MATLAB仿真(2022A版)。目标是自动化确定车辆位置,使加载效率ηq满足0.95≤ηq≤1.05且尽量接近1,同时减少车辆数量与布载时间。核心原理通过优化模型平衡最小车辆使用与ηq接近1的目标,并考虑桥梁载荷、车辆间距等约束条件。测试结果展示布载方案的有效性,适用于悬索桥承载能力评估及性能检测场景。

热门文章

最新文章