学习笔记

92 阅读2分钟

Vue2响应式原理

1.数据劫持

> Object.defineProperty

Vue官网原话: 你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter

// 数据劫持
const data = {
    name: "",
    message: ""
}
// 通过 keys 获取 data 多个键,实现对 data 多个键值变化的监听
Object.keys(data).forEach(key => {
    let value = data[key]
    // 该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
    Object.defineProperty(data, key, {
        // 设定某属性的值的方法
        set(newValue) {
            value = newValue
            console.log("监听到了变化" + newValue);
        },
        // 获取某个特定属性的值的方法
        get() {
            return value
        }
    })
})

2.设计模式

我刚开始理解这些观察者模式和订阅/发布者模式时也是非常的混乱,完全不懂在讲什么,在网上搜索了别人写的博客也才慢慢了解一点,推荐看一下这个博客,看完会对理解这两种模式有帮助。

另外推荐一下coderwhy老师的响应式原理视频

2.1观察者模式

参考了小浪努力学前端-手写一个简易vue响应式带你了解响应式原理-的文章

// 观察者模式
// 目标类
class Subject {
    constructor() {
        // 用来储存观察者的列表
        this.observerLists = []
    }
    // 添加观察者
    addObs(obs) {
        // 判断是否存在观察者和观察者是否有 update 方法
        if (obs && obs.update) {
            this.observerLists.push(obs)
        }
    }
    // 通知观察者
    notify() {
        this.observerLists.forEach(obs => {
            obs.update()
        })
    }
    // 清空观察者
    clearObs() {
        this.observerLists = []
    }
}
// 观察者类
class Observer {
    constructor(name) {
        this.name = name
    }
    // 观察更新
    update() {
        console.log(this.name + "更新了");
    }
}
// 实例化一个目标对象
const sub = new Subject()
// 实例化两个观察者对象
const obs1 = new Observer('张三')
const obs2 = new Observer('李四')
// 给目标对象添加观察者
sub.addObs(obs1)
sub.addObs(obs2)
// 通知观察者
sub.notify()

在控制台调用目标对象的 notify(),会对张三和李四进行通知:

image.png

1.将上边数据劫持代码改为如下:

Object.defineProperty(data, key, {
    // 设定某属性的值的方法
    set(newValue) {
        value = newValue
        console.log(`监听到了 ${key} 变化`);
        // 数据劫持监听到 data 某个键值改变时,通知观察者更新
        // ↓
        sub.notify()
    },
    // 获取某个特定属性的值的方法
    get() {
        return value
    }
})

2.控制台修改 data 键值时:

image.png

Vue实例执行的具体过程可以看一下coderwhy老师画的图解,图片需要结合老师的讲解更容易理解:

image.png

2.2订阅/发布者模式

class Event {
    constructor() {
        // 事件中心,即调度中心
        this.subs = {}
    }
    $on(eventType, fn) {
        // 参数为事件名称和回调函数
        // 判断事件总线是否存在该事件
        if(!this.subs[eventType]) {
            this.subs[eventType] = []
        }
        // 把事件回调函数保存到事件名称列表中
        this.subs[eventType].push(fn)
    }
    $emit(eventType, payload) {
        // 参数为事件名称和有效负载
        // 判断事件总线里是否有该事件
        if(this.subs[eventType]) {
            // 遍历该事件的回调函数,并执行
            this.subs[eventType].forEach((fn) => {
                fn(payload)
            })
        }
    }
}
const e = new Event()
// 注册事件
e.$on('click', () => {
    console.log("clike1");
})
e.$on('click', () => {
    console.log("clike2");
})
e.$on('sum', (numlist) => {
    const total = numlist.reduce((x, y) => x + y, 0)
    console.log(total);
})
// e.$emit('click')
// click1
// click2

// e.$emit('sum', [1,2,3,4,5])
// 15