Vue2、3响应式对比
- Vue2: Object.defineProperty 重写getter setter不能监听数组长度的变化
- Vue3: Proxy
- Vue 2 使用的是 Object.defineProperty 来实现响应式系统,对于每个属性都需要重新定义 getter 和 setter,这在大量数据或深层次的嵌套对象中可能会引入额外的性能开销
- Vue 3 引入了基于 Proxy 的响应式系统,它提供了更高效的依赖收集和触发更新的机制,减少了 Vue 2 中存在的重复依赖问题
this.$set
Vue.set(target, key, value)
- target:要修改的数据源
- key:要修改、要添加的属性名
- value: 对应key的值
实现逻辑:
- 如果target时是undefined、null、基础类型,直接报错
- 对于数组,[1,2] => [3,2] // [1,2].splice(0,1,3)
js
// 数组的处理
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(tanget.length, key)
target.splice(key, 1, val)
return val
}
- 对于对象,如果key存在直接更新走响应式逻辑;如果不是响应式对象就重新收集
两种响应式的实现
js
const initData = { value: 1 }
const data = {}
Object.keys(initData).forEach(key => {
Object.defineProperty(data, key, {
get() {
console.log("访问了", key)
return initData[key]
},
set(v) {
console.log("修改了", key)
initData[key] = v
}
})
})
const initData1 = { value: 1 }
const proxy = new Proxy(initData1, {
get: function (target, key, receiver) {
console.log("访问了", key)
return Reflect.get(target, key, receiver)
},
set: function (target, key, value, receiver) {
console.log("修改了", key)
return Reflect.set(target, key, value, receiver)
}
})
Vue3新特性
- main.js可以支持多个实例的声明
- composition api 对比mixin
- mixin:
- 命名冲突
- 不清楚谁暴露出来的内容
- composition api:
- setup允许按照功能分布代码而不是按照类型
- mixin:
- 生命周期
- 异步组件
- build以后是独立文件
- 自定义hook
- teleport
- 将子组件渲染到不存在的父组件之外的dom上(dialog等实现原理)
setup
代替beforeCreate、created生命周期之前执行
Vue2/3 diff
Vue3.0 性能提升主要是通过哪几个方面体现的?
- ① 响应式系统提升
- vue2 在初始化的时候,对 data 中的每个属性使用 definepropery 调用getter 和setter 使之变为响应式对象。如果属性值为对象,还会递归调用 defineproperty 使之变为响应式对象。vue3 使用 proxy 对象重写响应式。proxy 的性能本来比 defineproperty 好,proxy 可以拦截属性的访问、赋值、删除等操作,不需要初始化的时候遍历所有属性,另外有多层属性嵌套的话,只有访问某个属性的时候,才会递归处理下一级的属性。
- 优势:
- 可以监听动态新增的属性;
- 可以监听删除的属性 ;
- 可以监听数组的索引和 length 属性;
- ② 编译优化
- 优化编译和重写虚拟 dom,让首次渲染和更新 dom 性能有更大的提升vue2 通过标记静态根节点,优化 diff 算法 vue3 标记和提升所有静态根节点,diff 的时候只比较动态节点内容Fragments, 模板里面不用创建唯一根节点,可以直接放同级标签和文本内容
- 静态提升
- patch flag, 跳过静态节点,直接对比动态节点,缓存事件处理函数
- ③ 源码体积的优化
- vue3 移除了一些不常用的 api,例如:inline-template、filter 等 使用tree-shaking
vue2 Diff 缺点
vue2的diff是全量Diff,数据发生变化会产生新的DOM,与之前的DOM比较找到不同,如果层级太深,很消耗内存。
vue3 Diff 最长递增子序列
- 静态标记 + 非全量Diff
- 使用最长递增子序列进行优化
上 最长递增子序列 的链接
算法
js
function lengthOfLIS(nums) {
const tails = []; // 严格递增
tails[0] = nums[0];
for (let i = 1; i < nums.length; i++) {
const num = nums[i]; // 当前值
if (tails.length && tails[tails.length - 1] < num) {
tails.push(num);
} else {
// 二分法插入当前值
// 为什么可以无脑插入呢
// 1.无脑插入不影响已有数组的长度,虽然数组不一定满足
// 2.因为当前数组有序,后续存在的值如果大于栈顶元素,能保证取值的正确性
// 省流:不断的去找尽可能每个值都尽可能小且尽可能长的严格递增子序列
let left = 0;
let right = tails.length;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
// 这里一定要把 = 的清空放在大于那边
if (tails[mid] < num) {
left = mid + 1;
} else {
right = mid - 1;
}
}
// 贪心算法覆盖
tails[left] = num;
}
}
return tails.length;
}
console.log(lengthOfLIS([3, 5, 6, 2, 5, 4, 19, 5, 6, 7, 12]))
上文中这个算法还是存在问题的,因为他能求出最大长度,但是子序列不一定正确,比如你用这个例子[102, 103, 101, 105, 106, 108, 107, 109, 104]
得到的是[101,103,104,106,107,109]
,要解决这个问题,很显然,需要记录每次tails
中移动的项目,或者记录每一项的前置节点,诚然都可以实现,但是后者更容易。
算法
js
function getLIS(nums) {
const len = nums.length;
const tails = [nums[0]]; // 严格递增
const preNode = new Array(len).fill(0);
for (let i = 1; i < len; i++) {
const cur = nums[i]; // 当前值
if (tails[tails.length - 1] < cur) {
// 先存储前置节点,再push
preNode[i] = tails[tails.length - 1];
tails.push(cur);
} else {
// 二分法插入当前值
// 为什么可以无脑插入呢
// 1.无脑插入不影响已有数组的长度,虽然数组不一定满足
// 2.因为当前数组有序,后续存在的值如果大于栈顶元素,能保证取值的正确性
// 省流:不断的去找尽可能每个值都尽可能小且尽可能长的严格递增子序列
let left = 0;
let right = tails.length;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
// 这里一定要把 = 的清空放在大于那边
if (tails[mid] < cur) {
left = mid + 1;
} else {
right = mid - 1;
}
}
// 贪心算法覆盖
tails[left] = cur;
preNode[i] = tails[left - 1];
}
}
// 首先要确定一点,tails的最后一项一定是正确的,所以可以从这里下手
// 定义一个从后往前的游标 last 2 first pointer
// 把跟当前值前置节点不对应的都替换
let l2fp = tails.length - 1;
for (let i = len - 1; i >= 0; i--) {
const cur = nums[i];
if (tails[l2fp] === cur) {
if (preNode[i] !== tails[l2fp - 1]) tails[l2fp - 1] = preNode[i];
l2fp--; //shou
if (l2fp === 0) break
}
}
return tails;
}
console.log(getLIS([102, 103, 101, 105, 106, 108, 107, 109, 104]))
// [102, 103, 105, 106, 107, 109]
上述代码还存在改进空间,如果tails中存储的是index的话,那么最后处理的时候可以直接循环tails,不过时间复杂度虽然优化了一丢丢,但是区别不大
具体Vue2/3
diff的区别可以看这个 掘金文章