Vue数据响应式原理,我来了

402 阅读3分钟

学习笔记

不知不觉使用Vue.js已经两年了,响应式原理的理解却停留在官方文档的解释🤦‍♀️。最近终于下定决心把它吃透,下面是我的学习笔记😎

实现原理

  • Vue2.x 中使用的是Object.defineProperty()方法中的 getset 这两个方法对数据进行劫持
function reactive(data, key, value) {
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get() {
            console.log('你试图访问' + key + '属性')
            return value
        },
        set(newValue) {
            if (value === newValue) return
            console.log('你试图改变' + key + '属性');
            value = newValue
        }
    })
}

let person = {
    name: '小明',
    age: '24'
}

// 单个属性响应式
reactive(person, 'name', '小明')

// 测试用例
person.name
person.name = '小红'
person.age // 无响应

---------------------------

// 多个属性响应式
for (const key in person) {
    if (Object.hasOwnProperty.call(person, key)) {
        const element = person[key]
        reactive(person, key, element)
    }
}

// 测试用例
person.name
person.name = '小红'
person.age
person.age = '66'

  • Vue3.x 中使用的是Proxy()来代理整个对象,来实现数据劫持
let person = {
    name: '小明',
    age: '24'
}

function reactive(data) {
    return new Proxy(data, {
        get(obj, prop) {
            console.log('你试图访问' + prop + '属性')
            return Reflect.get(...arguments)
        },
        set(obj, prop, value) {
            console.log('你试图改变' + prop + '属性')
            return Reflect.set(...arguments)
        }
    })
}

person = reactive(person)

// 测试用例
person.name
person.name = '小红'
person.age
person.age = '25'
  • 两者的区别:
    • Object.defineProperty()一次只能劫持目标对象中的一个属性
    • Proxy()可以劫持整个目标对象,Object.defineProperty()要劫持整个目标对象,需要循环遍历整个对象才能实现
    • Object.defineProperty()无法检测到对象属性的添加和删除,数组的 API 方法也无法监听
    • Proxy()在性能上更优秀,配合Reflect使用,能让我们快速地执行this绑定

响应式类型

vue (非侵入式)

this.vm.a++;

react (侵入式)

this.setState({
  a: this.state.a + 1,
});

Observer 类--实现响应式的关键

Observer类.jpg

数组响应式--重写 7 个数组操作方法

重写数组的7个方法.jpg

依赖收集

什么是依赖?

数据响应式原理中的依赖,不是我们平常使用的 npm 包的依赖,而指的是 Vue 中需要用到数据的地方

  • Vue1.x,细粒度依赖,用到数据的DOM都是依赖
  • Vue2.x,中等粒度依赖,用到数据的组件是依赖
  • getter 中收集依赖,在 setter 中触发依赖

Dep 类和 Watch 类

  • 把依赖收集的代码封装成一个 Dep 类,它专门用来管理用来,每个 Observer 类的实例,都内置了一个 Dep 类的实例
  • Watch 是一个中介,数据发生变化时,通过 Watch 中转,通知组件
  • 工作过程简单描述
    • 有一个已经被响应式处理的 A 对象(也就是 Observe 类的实例)
    • 使用 Watch 类的实例 B 去监测数据变化,对 A 对象添加监测的那一刻触发了 getter
    • 触发了 getter,A 对象中 Dep 类实例就会进行依赖收集工作,把当前的实例 B 收集起来
    • 当 A 对象的数据变化时,触发 setter
    • 触发 setter,这时候 A 对象中 Dep 类实例通知实例 B,实例 B 就触发响应回调

数据监测工作原理.jpg

手写响应式原理

代码结构

|-- study_reactivity
    |-- .gitignore
    |-- package-lock.json
    |-- package.json
    |-- readme.md
    |-- webpack.config.js
    |-- src
    |   |-- array.js
    |   |-- defineReactive.js // 数据响应式的主要实现
    |   |-- Dep.js // Dep类
    |   |-- index.js // 主入口
    |   |-- observe.js
    |   |-- Observer.js // Observer类
    |   |-- utils.js
    |   |-- Watch.js // Watch类型
    |-- www
        |-- index.html

源码仓库

github.com/AFine970/st…

最后

好好学习不会差,我是 970,大家一起进步!