手把手实现vue响应式原理 -(六)

1.什么是响应式

let m = 20

console.log(m)
console.log(m*2)

m = 40; 数据发生改变
  • m有一个初始化的值,有一段代码使用了这个值
  • 那么在 m 有一个新值的时候,这段代码可以自动重新执行
  • 这样一种可以自动响应数据变量的代码机制,我们就称之为是响应式的

2.响应式函数的封装

// 对象的响应式
const obj = {
    name: 'deyang',
    age: 24
}


console.log('hello world')
console.log(obj.name)

obj.name = "superman"

对象的属性发生改变,我们希望涉及到该属性的代码能够重新执行,因此最好放到一个函数里面,

function foo() {
    const newName = obj.name
    console.log('hello world')
    console.log(obj.name)
}

 现在的问题就是当我的name属性发生改变的时候,foo函数能够重新执行。如果此时有另外一个函数 bar,但是这个函数我们不需要有响应式。

function bar() {
    console.log('普通的其他函数')
    console.log('这个函数不需要有任何的响应式');
}

所以我们的思路是封装一个响应式函数,将需要响应式的函数传递进去变成响应式。哪些函数需要响应式呢?产生了依赖的函数需要变成响应式。按照这个思路我们来完成响应式原理

// 封装一个响应式函数
let reactiveFns = [] // 定义存储响应式函数的数组
function watchFn(fn) {
    reactiveFns.push(fn) // 将产生依赖的函数存入响应式
}

// 响应式对象
const obj = {
    name: 'deyang',
    age: 24
}

// 将依赖产生的函数变成响应式
watchFn(function foo() {
    const newName = obj.name
    console.log('hello world')
    console.log(obj.name)
})

watchFn(function demo() {
    console.log(obj.name, 'demo-------------')
})

function bar() {
    console.log('普通的其他函数')
    console.log('这个函数不需要有任何的响应式');
}

obj.name = "chendeyang" // 对象的属性发生改变,触发执行响应式函数
reactiveFns.forEach(fn => {
    fn()
})

 目前响应式第一步已经实现,当对象 name 发生改变,循环执行响应式数组内的函数并执行

手把手实现vue响应式原理 -(六)

但是目前用一个数组收集的只是 name 一个属性的依赖,但是obj 还有 age 属性的依赖呢,开发中又有其他的类,因此需要用一个有状态的工具类来收集依赖,当数据发生改变通过这个类来触发依赖

3.依赖收集类的封装 

封装一个 Depend 的类,来收集和管理依赖,通过 addDepend 添加依赖,当依赖值发生改变的时候,通过 notify 来触发依赖

class Depend {
    constructor() {
        this.reactiveFns = []
    }

    addDepend(reactiveFn) {
        this.reactiveFns.push(reactiveFn)
    }

    notify() {
        this.reactiveFns.forEach(fn => {
            fn()
        })
    }
}

// 封装一个响应式函数
const depend = new Depend();
function watch(fn) {
    depend.addDepend(fn)
}

watchFn(function demo() {
    console.log(obj.name, 'demo-------------')
})

function bar() {
    console.log('普通的其他函数')
    console.log('这个函数不需要有任何的响应式');
}

obj.name = "chendeyang"
reactiveFns.forEach(fn => {
    fn()
})

我们目前的代码还是手动调用 depend.notify() 来触发依赖

4.自动监听对象变化

我们想要当对象属性发生变化的时候自动触发依赖,就要监听对象属性的改变,前面的几篇讲了两个方法 proxy / Obejct.defineProperty,先实现vue3的响应式,当我们用 proxy 代理的时候,后续就不要对原来的 obj 对象进行操作了,而是通过代理对象。

// 对象的响应式
const obj = {
    name: 'deyang', // depend对象
    age: 24 // depend 对象
}

// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const objProxy = new Proxy(obj, {
    get(target, key, receiver) {
        return Reflect.get(target, key, receiver)
    },
    set(target, key, newValue, receiver) {
        Reflect.set(target, key, newValue, receiver)
    }
})

watchFn(function foo() {
    const newName = objProxy.name
    console.log('欢迎来到卢本伟广场')
    console.log(objProxy.name)
})

objProxy.name = "superman"

当我们对 代理对象的name进行赋值的时候,就会进入到 set捕获器,我们就可以在 set 中进行依赖的触发

class Depend {
    constructor() {
        this.reactiveFns = []
    }

    addDepend(reactiveFn) {
        this.reactiveFns.push(reactiveFn)
    }

    notify() {
        this.reactiveFns.forEach(fn => {
            fn()
        })
    }
}

// 封装一个响应式函数
const depend = new Depend()
function watchFn(fn) {
    depend.reactiveFns.push(fn)
}

// 对象的响应式
const obj = {
    name: 'deyang', // depend对象
    age: 24 // depend 对象
}

// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const objProxy = new Proxy(obj, {
    get(target, key, receiver) {
        return Reflect.get(target, key, receiver)
    },
    set(target, key, newValue, receiver) {
        Reflect.set(target, key, newValue, receiver)
        depend.notify()
    }
})

watchFn(function foo() {
    const newName = objProxy.name
    console.log('hello world')
    console.log(objProxy.name)
})

objProxy.name = "super"
objProxy.name = "man"
objProxy.name = "卢本伟"

原来name 发生改变,我们需要手动触发三次,现在就完成了自动触发

手把手实现vue响应式原理 -(六)

5.依赖收集的管理

针对上面的代码,如果我们产生了 age属性的依赖

watchFn(function () {
    console.log(obj.age, '监听age的变化...1')
})

watchFn(function () {
    console.log(obj.age, '监听age的变化...2')
})

objProxy.age = 111

在 age 属性发生变化的时候,它自动将 name 的依赖也自动触发了,这显然不是我们想看到的,如果另外还有一个 info 对象,以及info 对象的依赖,当一个依赖属性发生改变了,所有的依赖都会出发。因此我们需要对依赖进行管理。

正确的思路应该是:我们将依赖按照对象划分为 obj 和 info, 然后又根据属性划分 obj 和 info 下面的依赖

                      手把手实现vue响应式原理 -(六)

 我们可以用到 ES6 的Map结构,为每一个响应式对象创建一个 map

const objMap = new Map()
objMap.set("name", "nameDepend")
objMap.set("age", "ageDepend")

const infoMap = new Map()
infoMap.set("address", "上海市")

 然后我们将创建的map对象用 WeakMap 保存起来

const targetMap = new WeakMap()
targetMap.set(obj, objMap)
targetMap.set(info, infoMap)

这样做的好处是,如果 obj.name 发生改变了就可以通过 targetMap.get(obj) 获取 obj对象的 objMap,然后通过objMap.get(name) 获取存储的 name依赖,然后触发依赖

// obj.name 发生改变
const depend = targetMap.get(obj).get(name);
depend.notify()

按照上面的思路封装一个获取 depend 的函数

// 封装一个获取 depend 的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
    // 根据 target 对象获取 map 的过程
    let map = targetMap.get(target)
    if (!map) {
        map = new Map()
        targetMap.set(target, map)
    }
    // 根据 key 获取 depend 对象
    let depend = map.get(key)
    if (!depend) {
        depend = new Depend()
        map.set(key, depend)
    }
    return depend
}

// 对象的响应式
const obj = {
    name: 'why', // depend对象
    age: 24 // depend 对象
}

// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const objProxy = new Proxy(obj, {
    get(target, key, receiver) {
        return Reflect.get(target, key, receiver)
    },
    set(target, key, newValue, receiver) {
        Reflect.set(target, key, newValue, receiver)
        // depend.notify()
        const depend = getDepend(target, key)
        console.log(depend.reactiveFns) // 此时打印四次 []
        depend.notify()
    }
})

watchFn(function foo() {
    const newName = objProxy.name
    console.log('hello world')
    console.log(objProxy.name)
})

objProxy.name = "chen"
objProxy.name = "de"
objProxy.name = "yang"
objProxy.age = 111

 但是此时触发依赖前,输出一下获取的依赖,其实都是空的,因为我们还没有正确的收集依赖手把手实现vue响应式原理 -(六)

6.正确收集依赖

我们之前将传入的 watchFn() 的函数都放在了一个depend中,这显然是不对的,应该根据不同对象划分不同的depend,传入不同的depend

// 对象的响应式
const obj = {
    name: 'why', // depend对象
    age: 24 // depend 对象
}

const info = {
    address: '上海' // depend
}

.....

// 用到了obj的属性就放到 obj 的 depend
watchFn(function() {
    console.log('欢迎来到卢本伟广场')
    console.log(objProxy.name)
})

// 用到了info的属性就放到 obj 的 depend
watchFn(function() {
    console.log(infoProxy.address)
})

手把手实现vue响应式原理 -(六)

 上面代码当运行到74行的时候,会走到 objProxy的get方法,那么我们就可以在这个捕获器里面获取depend,然后收集依赖

class Depend {
    constructor() {
        this.reactiveFns = []
    }

    addDepend(reactiveFn) {
        this.reactiveFns.push(reactiveFn)
    }

    notify() {
        this.reactiveFns.forEach(fn => {
            fn()
        })
    }
}

// 封装一个响应式函数
let activeReactiveFn = null
function watchFn(fn) {
    activeReactiveFn = fn
    fn()
    activeReactiveFn = null
}

// 封装一个获取 depend 的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
    // 根据 target 对象获取 map 的过程
    let map = targetMap.get(target)
    if (!map) {
        map = new Map()
        targetMap.set(target, map)
    }
    // 根据 key 获取 depend 对象
    let depend = map.get(key)
    if (!depend) {
        depend = new Depend()
        map.set(key, depend)
    }
    return depend
}

// 对象的响应式
const obj = {
    name: 'deyang', // depend对象
    age: 24 // depend 对象
}

// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const objProxy = new Proxy(obj, {
    get(target, key, receiver) {
        // 根据对应target获取对应depend
        const depend = getDepend(target, key)
        // depend中添加响应式函数
        depend.addDepend(activeReactiveFn)
        return Reflect.get(target, key, receiver)
    },
    set(target, key, newValue, receiver) {
        Reflect.set(target, key, newValue, receiver)
        // depend.notify()
        const depend = getDepend(target, key)
        depend.notify()
    }
})

watchFn(function foo() {
    console.log(objProxy.name, 'name depend 1')
})
watchFn(function foo() {
    console.log(objProxy.name, 'name depend 2')
})
watchFn(function () {
    console.log(objProxy.age, 'age depend 1')
})
watchFn(function () {
    console.log(objProxy.age, 'age depend 2')
})


console.log('上面是默认执行的代码-------------------------')
objProxy.name = '李银河'
// objProxy.age = 10

 目前就可以正确的收集依赖触发依赖,只有对应属性发生改变的时候才会触发依赖,手把手实现vue响应式原理 -(六)

手把手实现vue响应式原理 -(六)

 7.重构 Depend类

// 保存当前需要收集的响应式函数
let activeReactiveFn = null

/**
 * Depend优化:
 *  1.depend方法
 *  2.使用Set来保存依赖函数,而不是使用数组[]
 */
class Depend {
    constructor() {
        // this.reactiveFns = []
        this.reactiveFns = new Set()
    }

    depend() {
        if (activeReactiveFn) {
            this.reactiveFns.add(activeReactiveFn)
        }
    }

    notify() {
        this.reactiveFns.forEach(fn => {
            fn()
        })
    }
}

// 封装一个响应式函数
function watchFn(fn) {
    activeReactiveFn = fn
    fn()
    activeReactiveFn = null
}

// 封装一个获取 depend 的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
    // 根据 target 对象获取 map 的过程
    let map = targetMap.get(target)
    if (!map) {
        map = new Map()
        targetMap.set(target, map)
    }
    // 根据 key 获取 depend 对象
    let depend = map.get(key)
    if (!depend) {
        depend = new Depend()
        map.set(key, depend)
    }
    return depend
}

// 对象的响应式
const obj = {
    name: 'deyang', // depend对象
    age: 24 // depend 对象
}

// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const objProxy = new Proxy(obj, {
    get(target, key, receiver) {
        // 根绝对应target获取对应depend
        const depend = getDepend(target, key)
        // depend中添加响应式函数
        // depend.addDepend(activeReactiveFn)
        depend.depend()

        return Reflect.get(target, key, receiver)
    },
    set(target, key, newValue, receiver) {
        Reflect.set(target, key, newValue, receiver)
        // depend.notify()
        const depend = getDepend(target, key)
        depend.notify()
    }
})

watchFn(() => {
    console.log(objProxy.name, '--------')
    console.log(objProxy.name, '++++++++')
    console.log(objProxy.name, '++++++++')
})

objProxy.name = 'haha'

8 最终版 vue3 / vue2

proxy

// 保存当前需要收集的响应式函数
let activeReactiveFn = null
class Depend {
    constructor() {
        // this.reactiveFns = []
        this.reactiveFns = new Set()
    }

    depend() {
        if (activeReactiveFn) {
            this.reactiveFns.add(activeReactiveFn)
        }
    }

    notify() {
        this.reactiveFns.forEach(fn => {
            fn()
        })
    }
}

// 封装一个响应式函数
function watchFn(fn) {
    activeReactiveFn = fn
    fn()
    activeReactiveFn = null
}

// 封装一个获取 depend 的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
    // 根据 target 对象获取 map 的过程
    let map = targetMap.get(target)
    if (!map) {
        map = new Map()
        targetMap.set(target, map)
    }
    // 根据 key 获取 depend 对象
    let depend = map.get(key)
    if (!depend) {
        depend = new Depend()
        map.set(key, depend)
    }
    return depend
}

function defineReactive(obj) {
    return new Proxy(obj, {
        get(target, key, receiver) {
            // 根绝对应target获取对应depend
            const depend = getDepend(target, key)
            depend.depend()

            return Reflect.get(target, key, receiver)
        },
        set(target, key, newValue, receiver) {
            Reflect.set(target, key, newValue, receiver)
            // depend.notify()
            const depend = getDepend(target, key)
            depend.notify()
        }
    })
}

// 对象的响应式
// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const objProxy = defineReactive({
    name: 'chendeyang', // depend对象
    age: 24 // depend 对象
})

const infoProxy = defineReactive({
    address: '上海市',
    height: 1.88
})

watchFn(() => {
    console.log(infoProxy.address)
})

infoProxy.address = '武汉市'

Object.defineProperty

// 保存当前需要收集的响应式函数
let activeReactiveFn = null
class Depend {
    constructor() {
        // this.reactiveFns = []
        this.reactiveFns = new Set()
    }

    depend() {
        if (activeReactiveFn) {
            this.reactiveFns.add(activeReactiveFn)
        }
    }

    notify() {
        this.reactiveFns.forEach(fn => {
            fn()
        })
    }
}

// 封装一个响应式函数
function watchFn(fn) {
    activeReactiveFn = fn
    fn()
    activeReactiveFn = null
}

// 封装一个获取 depend 的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
    // 根据 target 对象获取 map 的过程
    let map = targetMap.get(target)
    if (!map) {
        map = new Map()
        targetMap.set(target, map)
    }
    // 根据 key 获取 depend 对象
    let depend = map.get(key)
    if (!depend) {
        depend = new Depend()
        map.set(key, depend)
    }
    return depend
}

function defineReactive(obj) {
    Object.keys(obj).forEach(key => {
        let value = obj[key]
        Object.defineProperty(obj, key, {
            get() {
                const depend = getDepend(obj, key)
                depend.depend()
                return value
            },
            set(newValue) {
                value = newValue
                const depend = getDepend(obj, key)
                depend.notify()
            }
        })
    })
    return obj
}

// 对象的响应式
// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const obj = defineReactive({
    name: 'chendeyang', // depend对象
    age: 24 // depend 对象
})

const info = defineReactive({
    address: '上海市',
    height: 1.88
})

watchFn(() => {
    console.log(info.address)
})

info.address = '武汉市'

上一篇:nGrinder中快速编写groovy脚本02-解读最基本的GET请求脚本


下一篇:设计原则之依赖倒置原则