IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue Mastery - 响应式原理 -> 正文阅读

[JavaScript知识库]Vue Mastery - 响应式原理

Vue3 Reactivity

let price = 5
let quantity = 2
let total = 0

let dep = new Set()

let effect = () => {
    total = price * quantity
}

function track() {
    dep.add(effect)
}

function trigger() {
    dep.forEach(effect => effect())
}

track()
effect()
trigger()
  • track 依赖收集
  • trigger 更新依赖

通常我们有多个属性,如何监听多个属性的依赖呢?

使用一个图来保存属性和它相关的依赖函数。

const depsMap = new Map()

function track(key) {
    let dep = depsMap.get(key) // 得到属性相关的依赖集合
    if (!dep) {
        depsMap.set(key, (dep = new Set()))
    }

    dep.add(effect) // Set不会重复添加
}

function trigger(key) {
    let dep = depsMap.get(key)
    if (dep) {
        dep.forEach(effect => {
            effect()
        })
    }
}

let product = {
    price: 5,
    quantity: 2
}

let effect = () => {
    total = product.price * product.quantity
}

track('quantity')
effect() // 此时total为10
product.quantity = 3
trigger() // 此时total为15

如何建立一个响应式对象和其属性和其依赖项的关系呢?

使用WeakMap,将这个对象作为key值,而其value值,则是属性和依赖的图。

在这里插入图片描述

const targetMap = new WeakMap()
    function track(target, key) {
        let depsMap = targetMap.get(target)
        if (!depsMap) {
            targetMap.set(target, (depsMap = new Map())) // 注意等号返回的是 new Map()
        }
        let deps = depsMap.get(key)
        if (!deps) {
            depsMap.set(key, deps = new Set())
        }
        deps.add(effect)
    }

    function trigger(target, key) {
        const depsMap = targetMap.get(target)
        if (!depsMap) {
            return
        }

        const deps = depsMap.get(key)
        if (deps) {
            deps.forEach(effect => {
                effect()
            })
        }
    }

    let product = {
        price: 5,
        quantity: 2
    }
    let total = 0
    let effect = () => {
        total = product.price * product.quantity
    }

    track(product, 'quantity')
    effect()
    console.log(total) // 10
    product.quantity = 3
    trigger(product, 'quantity')
    console.log(total) // 15

但是,我们还无法让effect自动执行。还需要完善。

Proxy and Reflect

在这里插入图片描述
真正放弃IE

真正的响应式,当更改quantity or price 的时候,total自动变化。

要点:

  • get的时候进行 track
  • set的时候进行 trigger

Reflect的superPower?

let product = {
        price: 5,
        quantity: 2
    }
    let proxiedProduct = new Proxy(product, {
      get(target, key) {
        console.log('Get was called with key = ' + key);
        return target[key]
      }
    })

此时 Reflect要来临了。

我们会传入第三个参数,receiver,并且传入的reflect中。这个receiver保证了当我们的对象有继承自其他对象的值或函数时,this指针能正确的指向使用的对象。这将避免我们在vue2中有的响应式警告。

    let product = {
        price: 5,
        quantity: 2
    }
    let proxiedProduct = new Proxy(product, {
        get(target, key, receiver) {
            console.log('Get was called with key = ' + key);
            return Reflect.get(target, key, receiver)
        }
    })

    console.log(proxiedProduct.price);

同时,我们还需要拦截set方法。

let product = {
        price: 5,
        quantity: 2
    }
    let proxiedProduct = new Proxy(product, {
        get(target, key, receiver) {
            console.log('Get was called with key = ' + key);
            return Reflect.get(target, key, receiver)
        },
        set(target, key, value, receiver) {
            console.log('Set was called with key = ' + key + ' and value = ' + value)
            return Reflect.set(target, key, value, receiver)
        }
    })

    console.log(proxiedProduct.price);
    proxiedProduct.price = 7
    console.log(proxiedProduct.price);

Proxy的第二个参数是handler,现在改写一下,让他长得更像vue3源码。

   let product = {
        price: 5,
        quantity: 2
    }

    // composition API reactive
    function reactive(target) {
        const handler = {
            get(target, key, receiver) {
                console.log('Get was called with key = ' + key);
                return Reflect.get(target, key, receiver)
            },
            set(target, key, value, receiver) {
                console.log('Set was called with key = ' + key + ' and value = ' + value)
                return Reflect.set(target, key, value, receiver)
            }
        }
        return new Proxy(target, handler)
    }

    const proxiedProduct = reactive(product)
    console.log(proxiedProduct.price);
    proxiedProduct.price = 7
    console.log(proxiedProduct.price);

知道了如何通过Proxy拦截get和set之后,我们来试着将track函数和trigger函数加入到handler中,完成真正的响应式对象。

const targetMap = new WeakMap()

    function track(target, key) {
        let depsMap = targetMap.get(target)
        if (!depsMap) {
            targetMap.set(target, (depsMap = new Map())) // 注意等号返回的是 new Map()
        }
        let deps = depsMap.get(key)
        if (!deps) {
            depsMap.set(key, deps = new Set())
        }
        deps.add(effect)
    }

    function trigger(target, key) {
        const depsMap = targetMap.get(target)
        if (!depsMap) {
            return
        }

        const deps = depsMap.get(key)
        if (deps) {
            deps.forEach(effect => {
                effect()
            })
        }
    }

    let product = {
        price: 5,
        quantity: 2
    }

    // composition API reactive
    function reactive(target) {
        const handler = {
            get(target, key, receiver) {
                let result = Reflect.get(target, key, receiver)
                track(target, key)
                return result
            },
            set(target, key, value, receiver) {
                let oldValue = target[key] // 这里不用这个Reflect.get(target, key, receiver),因为get已经改写
                let result = Reflect.set(target, key, value, receiver) // result为true/false
                if (oldValue !== result) {
                    trigger(target, key) // 优化,不相等,触发依赖更新
                }
                return result
            }
        }
        return new Proxy(target, handler)
    }

    let product = reactive({
        price: 5,
        quantity: 2
    })
    let total = 0
    let effect = () => {
        total = product.price * product.quantity
    }

    //track(product, 'quantity')
    effect()
    console.log(total)
    product.quantity = 3
        //trigger(product, 'quantity')
    console.log(total)

我们通过将product设置成reactive对象。(其中将get和set重写了,让get的时候,能够收集依赖函数。让set的时候,如果新老值不同,那么出发trigger,更新依赖。)

此时,我们运行effect函数。(其中,用到了price的get,和quantity的get。由于product是reactive的,所以,将收集这个effect依赖函数。如何收集?外面有一个闭包,类型是WeakMap,存放着对象和其属性与其依赖函数的图。我们找到这个对象所对应的属性与其依赖的图。如果没有找到,新建一个。接着找这个图中,属性所对应的所有依赖。如果没有找到,新建立一个依赖集合。接着,就直接存进去。)

当我们把quantity设置为3。(其中,用到了quantity的set。由于product是reactive的,所以将触发更新。如何触发?和get相同,找到直接循环遍历触发即可。)
在这里插入图片描述
这里就是一个vue3的雏形了!!

但是,还有一个问题没有解决。因为此时的track和trigger只是定向收集effect。如何收集所有用到了这个对象属性的函数呢?

activeEffect & ref

在这里插入图片描述

追踪函数。

    const targetMap = new WeakMap()
    let activeEffect = null
    let effect = (eff) => {
        activeEffect = eff
        activeEffect()
        activeEffect = null
    }

    function track(target, key) {
        if (activeEffect) {
            let depsMap = targetMap.get(target)
            if (!depsMap) {
                targetMap.set(target, (depsMap = new Map())) // 注意等号返回的是 new Map()
            }
            let deps = depsMap.get(key)
            if (!deps) {
                depsMap.set(key, deps = new Set())
            }
            deps.add(activeEffect)
        }
    }

尝试使用一下

const targetMap = new WeakMap()
    let activeEffect = null
    let effect = (eff) => {
        activeEffect = eff
        activeEffect()
        activeEffect = null
    }

    function track(target, key) {
        if (activeEffect) {
            let depsMap = targetMap.get(target)
            if (!depsMap) {
                targetMap.set(target, (depsMap = new Map())) // 注意等号返回的是 new Map()
            }
            let deps = depsMap.get(key)
            if (!deps) {
                depsMap.set(key, deps = new Set())
            }
            deps.add(activeEffect)
        }
    }

    function trigger(target, key) {
        const depsMap = targetMap.get(target)
        if (!depsMap) {
            return
        }

        const deps = depsMap.get(key)
        if (deps) {
            deps.forEach(effect => {
                effect()
            })
        }
    }

    // composition API reactive
    function reactive(target) {
        const handler = {
            get(target, key, receiver) {
                let result = Reflect.get(target, key, receiver)
                track(target, key)
                return result
            },
            set(target, key, value, receiver) {
                let oldValue = target[key] // 这里不用这个Reflect.get(target, key, receiver),因为get已经改写
                let result = Reflect.set(target, key, value, receiver) // set返回新值
                if (oldValue !== result) {
                    trigger(target, key) // 优化,不相等,触发依赖更新
                }
                return result
            }
        }
        return new Proxy(target, handler)
    }

    let product = reactive({
        price: 5,
        quantity: 2
    })
    let salePrice = 0
    let total = 0

    effect(() => {
        total = product.price * product.quantity
    })

    effect(() => {
        salePrice = product.price * 0.9
    })
    console.log(total, salePrice) // 10, 4.5
    product.quantity = 3
    console.log(total, salePrice) // 15, 4.5
    product.price = 10
    console.log(total, salePrice) // 30, 9

没问题。但是,没有意义。
如果total = salePrice * quantity呢?由于salePrice不是响应式的,因此,它无法正常工作。

如何实现?ref
1.直接使用上面的reactive API。

function ref(initialValue){
	return reactive({value: initialValue })
}

2、使用Object Accessors(对象访问器)(aka. computed properties 即JS中的computed)

    let user = {
        firstName: 'Gregg',
        lastName: 'Pollack',

        get fullName() {
            return `${this.firstName} ${this.lastName}`
        },

        set fullName(value) {
            [this.firstName, this.lastName] = value.split(' ')
        }
    }

    console.log(`Name is ${user.fullName}`);
    user.fullName = 'Adam Jahr'
    console.log(`Name is ${user.fullName}`);

原来可以直接定义get和set,从而定义一个对象中属性的访问和设置!! amazing!!

那如何用这个操作来写ref呢?

function ref(raw) {
        const r = {
            get value() {
                track(r, 'value')
                return raw
            },

            set value(newValue) {
                raw = newValue
                trigger(r, 'value')
            }
        }
        return r
    }

function ref(raw) {
        const r = {
            get value() {
                track(r, 'value')
                return raw // 注意这里的返回
            },

            set value(newValue) {
                raw = newValue
                trigger(r, 'value')
            }
        }
        return r
    }

    let product = reactive({
        price: 5,
        quantity: 2
    })
    let salePrice = ref(0)
    let total = 0

    effect(() => {
        total = salePrice.value * product.quantity
    })

    effect(() => {
        salePrice.value = product.price * 0.9
    })
    console.log(total, salePrice) // 10, 4.5
    product.quantity = 3
    console.log(total, salePrice) // 13.5, 4.5
    product.price = 10
    console.log(total, salePrice) // 27, 9

在这里插入图片描述
ok,现在salePrice也是响应式的了。

完整

 const targetMap = new WeakMap()
    let activeEffect = null
    let effect = (eff) => {
        activeEffect = eff
        activeEffect()
        activeEffect = null
    }

    function track(target, key) {
        if (activeEffect) {
            let depsMap = targetMap.get(target)
            if (!depsMap) {
                targetMap.set(target, (depsMap = new Map())) // 注意等号返回的是 new Map()
            }
            let deps = depsMap.get(key)
            if (!deps) {
                depsMap.set(key, deps = new Set())
            }
            deps.add(activeEffect)
        }
    }

    function trigger(target, key) {
        const depsMap = targetMap.get(target)
        if (!depsMap) {
            return
        }

        const deps = depsMap.get(key)
        if (deps) {
            deps.forEach(effect => {
                effect()
            })
        }
    }

    // composition API reactive
    function reactive(target) {
        const handler = {
            get(target, key, receiver) {
                let result = Reflect.get(target, key, receiver)
                track(target, key)
                return result
            },
            set(target, key, value, receiver) {
                let oldValue = target[key] // 这里不用这个Reflect.get(target, key, receiver),因为get已经改写
                let result = Reflect.set(target, key, value, receiver) //set返回Boolean!!
                if (oldValue !== value) {
                    trigger(target, key) // 优化,不相等,触发依赖更新
                }
                return result
            }
        }
        return new Proxy(target, handler)
    }

    function ref(raw) {
        const r = {
            get value() {
                track(r, 'value')
                return raw
            },

            set value(newValue) {
                if (raw !== newValue) { // 不加判断会死循环,会无限赋值
                    raw = newValue
                    trigger(r, 'value')
                }
                return raw
            }
        }
        return r
    }

    let product = reactive({
        price: 5,
        quantity: 2
    })
    let salePrice = ref(0)
    let total = 0

    effect(() => {
        total = salePrice.value * product.quantity
    })

    effect(() => {
        salePrice.value = product.price * 0.9
    })

    console.log(total, salePrice.value) // 9, 4.5
    product.quantity = 3
    console.log(total, salePrice.value) // 13.5, 4.5
    product.price = 10
    console.log(total, salePrice.value) // 27, 9

Computed & Vue 3 Source

上节中,为什么销售价格和总价格,不能用computed?

如何定义computed呢?且computed也是响应式的

function ref(raw) {
        const r = {
            get value() {
                track(r, 'value')
                return raw
            },

            set value(newValue) {
                if (raw !== newValue) { // 不加判断会死循环,会无限赋值
                    raw = newValue
                    trigger(r, 'value')
                }
                return raw
            }
        }
        return r
    }

    function computed(getter) {
        let result = ref()

        effect(() => result.value = getter()) // 其实和之前的应用一样的,只是这个更抽象了

        return result
    }

    let product = reactive({
        price: 5,
        quantity: 2
    })
    let salePrice = computed(() => {
        return product.price * 0.9
    })
    let total = computed(() => {
        return salePrice.value * product.quantity
    })

    console.log(total.value, salePrice.value) // 9, 4.5
    product.quantity = 3
    console.log(total.value, salePrice.value) // 13.5, 4.5
    product.price = 10
    console.log(total.value, salePrice.value) // 27, 9

太妙了,太牛了。

即使是这段小小的代码,也已经超越vue2了。

因为在vue2中,新添加的属性,是没有响应式的!!而vue3中却有!为什么?

因为我们使用了Proxy,我们添加属性的时候,则这个属性会自动变成响应式!!

正式因为vue3中,我们是基于代理的响应式,当我们有一个使用代理的响应式对象时,我们就可以添加属性了!!

可以自己试试从vue中拉代码

在这里插入图片描述
那么vue响应式源码本身到底是什么样子的?

Q&A with Evan You

为什么ref不用reactive实现?
因为ref的本意就只是一个值,他不能像一个对象一样,添加其他属性。它仅仅只暴露一个value值,仅此而已。同时有一个isRef函数判断是不是ref。判断ref和reactive,很多情况下是必要的。最后还有考虑性能问题。

使用proxy有什么好处呢?
当使用proxy的时候,所谓的响应式转化会变懒加载。在vue2中,当我们进行转换的时候,我们必须尽快完成转换,因为当你将对象传递给Vue2的响应式的时候,我们必须遍历所有的键,并当场转换。所以以后,当他们被访问的时候,他们已经转换了。
但是对于vue3,当调用reactive的时候,对于一个对象,我们所做对的就是返回一个Proxy对象而已。仅仅在需要时转换嵌套对象,当你访问对象的时候。所以,默认情况下像懒加载。现在,这有一个明显的好处,如果你的应用程序有一个庞大的对象列表,但是对于分页,只是渲染页面的前10个,那么!!注意只有前10个对象必须经过响应式转化而已!!这就可以节省很多时间!!

Reading Source Code with Evan You

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-03 12:57:17  更:2021-12-03 12:57:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:48:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码