- 作者:老汪软件技巧
- 发表时间:2024-12-18 11:06
- 浏览量:
面试官的一番话让我印象很深,”如果你会忘记说不你并不真正理解,你要记住,你需要知道为什么需要“,我觉得还是非常有意义的话,至少对于我,促使我去思考为什么。什么是响应式?
今天的主题是Vue响应式,那么什么是响应式呢?
有一个简单的例子,在我们编写原生html代码时,我们都会下载一个Live Server插件,在编程中,响应式系统能够自动"感知"数据的变化,并及时更新与之相关的内容,无需手动干预,响应式系统会自动更新用户界面,确保显示的永远是最新的数据状态。
为什么需要使用响应式?简而言之,言而简之,当数据发生变化时,我们需要及时的去更新页面,也就是数据驱动视图。响应式的设计让数据的变化能够流畅地驱动页面的更新,为用户提供即时且无缝衔接的交互体验。
3. 实现一个简单的响应式系统
让我们从头开始,逐步构建一个简单的响应式系统。我们想想,如果我们需要实现一个响应式系统,我们需要什么?
首先,我需要一个工具,能够感知或捕获到数据变化,如果我们去寻求答案,更多的推荐可能会是Proxy 和 Object.defineProperty,那么我们就来看看怎么个事儿。
文档在此,有空可以看看和
通常,我们习惯地称为数据劫持和数据代理,它俩也正分别是Vue2.X和Vue3.X的核心。我们就先来看看使用数据劫持如何来实现响应式的。
3.1 Vue 2 的实现
简单讲讲,Object.defineProperty接受三个参数
要定义属性的对象。
一个字符串或,指定了要定义或修改的属性键。
要定义或修改的属性的描述符。
喔~第一个参数是对象,也难怪vue2中的data会是一个对象呢,那么现在我们可以尝试去“劫持”一个对象了:
const obj = {
name: 'kailin'
}
//let value = obj.name
Object.defineProperty(obj, 'name', {
get() {
console.log(`you are getting name`);
return obj.name
// return value
},
set(newVal) {
if(newVal !== value){
console.log(`you are setting name to ${newVal}`);
obj.name = newVal
//value = newVal
}
}
})
obj.name = 'kailin'//you are setting name to kailin
console.log(obj.name);//RangeError: Maximum call stack size exceeded
不会你也是这样想的吧,那么恭喜你,踩到第一个坑了。仔细想想,在get中,我们return和赋值的时候,是不是还需要读取一遍obj.name呢,这样就实现了一个自调用(死循环),自然要爆栈了。所以,我们可以在外面保存一下obj.name的值。
接下来,又有一个问题,data既然是一个对象,data所有的key都需要被劫持,所以,可以创建一个方法将所有的key进行数据劫持。
const observer = (obj) => {
walk (obj)
}
const walk = (obj) =>{
Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]))
}
const defineReactive = (obj, key, value) => {
Object.defineProperty(obj, key, {
//...
})
}
那么现在,我已经有了一个方法observer能够对一个对象中的所有属性进行劫持了,那么,真的是这样吗?我们可以试验一下:
const data = {
person: {
weight:140 ,
tall: 180
},
hobby: 'coding',
county: 'china',
schools: ['pku','tsinghua']
}
我发现了几个问题:
我们先来解决简单的问题,劫持多层对象或多维数组:在walk()方法中,可以进行判断是否是对象
const walk = (val) =>{
if (Array.isArray(val)) {
// 如果是数组,则调用 observeArray 递归处理
observeArray(val);
} else if (typeof val === 'object' && val !== null) {
// 如果是对象,则递归处理对象的属性
Object.keys(val).forEach(key => {
defineReactive(val, key, val[key]);
observer(val[key]);
});
}
}
通过这样的方法,我们就可以对多层的对象或数组进行劫持了。那么对数组的操作呢?在vue2中,尤大想了这样一个方法,重写了数组上的七种方法['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'],当然,并不是我们所想的那样。我们都知道,数组的方法都是挂载在Array的原型prototype上的,所以他想了一个办法:
传送门:好奇就点点我吧!
那么对于上面的过程,我们思考几个问题:
为什么只对push、unshift、splice三个方法的参数进行劫持呢?
那么,我们还剩下几个问题
无法感知通过索引修改数组和 delete删除对象属性直接修改数组的length属性无法监听对象的新增属性
其实,这也是Vue2的缺陷,当然,我们也可以使用别的方法来达到目的
操作方式原因解决方案示例代码
通过索引修改数组
Object.defineProperty 无法拦截数组索引操作
使用 Vue.set 或 splice 替代
Vue.set(arr, 3, 99)
向对象添加新的属性
属性新增时未定义 getter 和 setter
使用 Vue.set
Vue.set(obj, 'b', 2)
删除对象的属性
delete 不会触发依赖更新
使用 Vue.delete
Vue.delete(obj, 'a')
当然,到现在,我们也只完成了第一步,当然也是最核心的一步,除了对数据的监听,我们还需要及时地依赖(副作用)收集和触发、更新视图,如何去实现这一功能呢?。
在此处就不深入探讨了,大家可以看看站内相关的优秀文章和官方源码
下文:Vue3的响应式