• 作者:老汪软件技巧
  • 发表时间: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, {
       //...
    })
}

响应式原理vue__响应式js

那么现在,我已经有了一个方法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')

当然,到现在,我们也只完成了第一步,当然也是最核心的一步,除了对数据的监听,我们还需要及时地依赖(副作用)收集和触发、更新视图,如何去实现这一功能呢?。

响应式js_响应式原理vue_

在此处就不深入探讨了,大家可以看看站内相关的优秀文章和官方源码

下文:Vue3的响应式


上一条查看详情 +前端必须掌握的 before/after 伪元素小技巧
下一条 查看详情 +没有了