1. Vue数据响应原理
Vue在数据响应的过程中会进行数据劫持,通过比对的方式(就是判断要更新的数据是标签还是属性等等,然后我应该怎么去做)更新数据。下面的代码用传统 js 操作 Dom 的方式简单实现了这一原理。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h3 id="title"></h3>
<input type="text" id="username">
<script>
// 数据源
const data = {
name: '张三'
}
// 目标数据
const target = {}
// 响应数据 (数据劫持)
/*
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,
或者修改一个对象的现有属性,并返回此对象。
obj 要定义属性的对象。
prop 要定义或修改的属性的名称或 Symbol 。
descriptor 要定义或修改的属性描述符。
*/
Object.defineProperty(target, 'name', {
// 获取 相当于用Dom方式获取target.name
get() {
// return Reflect.get(data, 'name') 反射写法:这一种可以捕获异常
return data.name
},
set(newValue) {
// 视图改变导致数据改变
data.name = newValue
// 通知页面元素显示的数据改变
document.getElementById('title').textContent = newValue
}
})
// 获取dom中的元素,使得输入框和h3标题中的值都是数据源中的数据
const username = document.getElementById('username')
username.value = target.name
const title = document.getElementById('title')
title.textContent = target.name
// 页面改变导致数据改变
username.addEventListener('input', function () {
target.name = this.value
})
</script>
</body>
</html>
那么,Vue究竟是怎么实现数据响应的呢?
当把一个普通的JavaScript对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,使用Object.defineProperty
(vue2.x),vue3.x 中使用了 Proxy 类把这些属性全部转为 getter/setter (数据劫持)。在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。下图可以帮助理解。
Vue 的数据劫持手段:
- 对于一般属性数据进行劫持,通过键值对声明的方式
- 对于多层级中的数据,进行递归来完成劫持
- 对于数组中的数据,进行重写对应的相关方法来完成劫持
补充:
在 Vue 中如果你不想让一个数据变为响应性的,可以把它冻结起来
Object.freeze({id:1})
。这样可以让性能得到提升。检查当时对象是否冻结:
Object.isFrozen()
。
2. Diff比对
vue中当数据发生改变的时候,对应监听的set方法会执行,调用数据中的Dep.notify
方法通知所有的订阅者,订阅者就会通过patch函数对比新旧虚拟节点是否一样,如果用新的虚拟节点则整个替换老节点,如果不是使用新节点,则根据子节点情况来进行同层比较。
patch主要做4个判断
-
没有新节点,直接触发旧节点的destory钩子,进行销毁。
假如旧节点中存在 a 节点,而新节点中没有 a 节点,则直接将旧节点中的 a 节点销毁。
-
没有旧节点,此时根本不需要比较了,直接全部都是新建
-
旧节点和新节点一样时,直接调用 patchVnode 去处理这两个节点
-
旧节点和新节点自身不一样,当两个节点不一样的时候,直接创建新节点,删除旧节点
patch 做的是同层比对(同层比对复杂度较低,可以提升性能),同层比对后进行同 key 比对。当没有 key 时,就按顺序比。
没有key的比较:
比对时有key值标识:
key值的作用:在使用diff算法比较新旧dom树的时候,可以更准确更快得找到oldDom树中对应的节点。(利用key的唯一性生成map对象来获取对应节点,比遍历方式更快)
关于 Diff 比对的算法,这篇文章通俗易懂,很好理解,特此推荐:
https://blog.csdn.net/weixin_43638968/article/details/112686317