海边的小溪鱼

vuePress-theme-reco 海边的小溪鱼    2017 - 2023
海边的小溪鱼 海边的小溪鱼

Choose mode

  • dark
  • auto
  • light
首页
分类
  • CSS
  • HTTP
  • Axios
  • jQuery
  • NodeJS
  • JavaScript
  • Vue2
  • Server
  • Vue3
标签
时间轴
更多
  • Gitee (opens new window)
Getee (opens new window)
author-avatar

海边的小溪鱼

28

文章

14

标签

首页
分类
  • CSS
  • HTTP
  • Axios
  • jQuery
  • NodeJS
  • JavaScript
  • Vue2
  • Server
  • Vue3
标签
时间轴
更多
  • Gitee (opens new window)
Getee (opens new window)

Vue3的响应式原理(Proxy)

vuePress-theme-reco 海边的小溪鱼    2017 - 2023

Vue3的响应式原理(Proxy)

海边的小溪鱼 2017-07-14 Vue3

vue3中的响应式原理、手写Vue3响应式

您可能需要了解(Vue2的响应式原理)

Vue3响应式实现原理:使用了 Proxy 代理对象, 和 Reflect 反射对象

Vue3 通过 ES6 的代理对象 Proxy 进行响应式,代理 data 对象里面所有的属性及数组,访问属性时触发 get(),改变属性值时触发 set(),然后发布消息给订阅者,重新渲染页面。

    1. 通过 Proxy (代理):拦截对象中任意属性的变化(对属性的增删改查都可以被监听得到)
    • 作用:监听某个对象属性的变化(监听对属性的增删改查操作)
    1. 通过 Reflect (反射):对被代理对象的属性进行操作(对属性的增删改查操作都是用的它)
    • 作用:操作源对象的数据(增删改查)
// 源对象
let person = { name: "张三", age: 18 }

// 格式:new Proxy("代理哪个对象", 配置对象)
const p = new Proxy(person, {

    // 通过 Proxy 中的 get,拦截读取属性值(监听读取操作)
    get(target, propName) {
        console.log(target == person); // true
        return Reflect.get(target, propName) // 通过 Reflect 读取属性值
    },

    // 通过 Proxy 中的 set,拦截修改或添加属性(监听增改操作)
    set(target, propName, value) {
        Reflect.set(target, propName, value) // 通过 Reflect 增改属性
    },
    
    // 通过 Proxy 中的 deleteProperty,拦截删除属性(监听删除操作)
    deleteProperty(target, propName) {
        return Reflect.deleteProperty(target, propName) // 通过 Reflect 删除属性
    }

    // target 参数:源对象
    // propName 参数:监听对象的哪个属性
    // value 参数:最新修改的值
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 具体实现(模拟 Vue3 响应式)

1. Proxy(第一个参数)

new Proxy() 构造函数:第一个参数(需要代理哪个对象)

作用:将 源对象 交给 Proxy 代理,得到一个被 Proxy 所代理的对象(引用对象)

// 源对象
let person = { name: "张三", age: 18 }

// 将 源对象(person) 交给 Proxy 代理
const p = new Proxy(person, {}) // 参数1:需要代理哪个对象,参数2:配置项
console.log(p); // Proxy {name: "张三", age: 18},此时 p 就是被 Proxy 所代理的对象(引用对象)

// 此时,当我们操作 person 源对象里面的属性时,p对象也会发生相应的变化;反之同理
person.name = "李四" // 修改 name 属性
person.sex = "男" // 添加 sex 属性
delete person.age // 删除 age 属性
console.log("我是源对象person", person);
console.log("我是代理对象p", p);
1
2
3
4
5
6
7
8
9
10
11
12
13

注意:此时不是响应式数据,数据虽然发生变化,但页面还没更新(只是完成了代理的操作,数据的变化并没有被监听到)
想要实现响应式,还需要捕获数据的修改,此时用到第二个参数(配置项)

2. Proxy(第二个参数)

new Proxy() 构造函数:第二个参数(配置项,是一个对象)

(1). 通过配置项中的 get()、set()、deleteProperty() 来监听某个对象中任意属性的变化(属性的增删改查都能被监听到) (2). 通过 Reflect (反射) 来操作源对象的数据(对属性的,增、删、改、查,操作都是用的它)

let person = { name: "张三", age: 18 }

const p = new Proxy(person, {

    // 1. get() 方法:读取属性时会调用
    get(target, propName) {
        console.log(`有人读取了 p 身上的 ${propName} 属性`);
        // return target[propName]
        return Reflect.get(target, propName) // 通过 Reflect(反射对象) 读取属性值
    },

    // 2. set() 方法:修改和添加属性时会调用
    set(target, propName, value) {
        console.log(`有人修改 p 身上的 ${propName} 属性的值,新值为${value}`);
        // target[propName] = value
        Reflect.set(target, propName, value) // 通过 Reflect(反射对象) 添加/修改属性值
    },

    // 3. deleteProperty() 方法:删除属性时会调用
    deleteProperty(target, propName) {
        console.log(`有人删除了 p 身上的 ${propName} 属性`)
        // return delete target[propName]
        return Reflect.deleteProperty(target, propName) // 通过 Reflect(反射对象) 删除属性值
    }

    // 参数说明:target(源对象person)、propName(被读取的属性)、value(最新修改的值)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Reflect 对象(Object的继承者)

Reflect 对象 与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 Api

目前正在尝试将 Object 身上一些有用的 api 转移到 Reflect 身上(很可能会成为 Object 的继承者)
注意:相对于封装框架来说,用 Reflect 比较好,所以 Vue3 响应式对数据的操作是用了 Reflect

1. Reflect 可以操作对象

const obj = { name: "张三", age: 18 }

// Reflect.get(对象, "属性名"):可以读取某个对象身上的某个属性
console.log(Reflect.get(obj, "name")); // 张三

// Reflect.set(对象, "属性名", 值):可以修改某个对象身上的某个属性的值
Reflect.set(obj, "age", 20)

// Reflect.deleteProperty(对象, "属性名"):可以删除某个对象身上的某个属性
Reflect.deleteProperty(obj, "name")
1
2
3
4
5
6
7
8
9
10

2. Reflect 身上的 defineProperty(Reflect.defineProperty)

const person = { a: 1, b: 2 }

/* 1. 通过 Object.defineProperty
      监视两个同样的属性会直接报错(后面的代码不能继续执行),需要 try catch 来解决(不能更好的捕获错误)
*/
try {
    Object.defineProperty(person, "c", {
        get() {
            console.log("有人读取了c 属性");
            return 3
        },
    })
    Object.defineProperty(person, "c", {
        get() {
            console.log("有人读取了c 属性");
            return 4
        },
    })
} catch (error) {
    console.log(error);
}

/* 2. 通过 Reflect.defineProperty
      监视两个同样的属性,不报错,且返回一个布尔值来表示成功/失败(可以做更多的操作)
*/
const c1 = Reflect.defineProperty(person, "c", {
    get() {
        console.log("有人读取了c 属性");
        return 3
    },
})
console.log(c1); // true
const c2 = Reflect.defineProperty(person, "c", {
    get() {
        console.log("有人读取了c 属性");
        return 4
    },
})
console.log(c2); // false
if (c2) {
    console.log("某某某操作成功了");
} else {
    console.log("某某某操作失败了");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
帮助我们改善此页面! (opens new window)