手搓实现简易版 Vue2 响应式系统

本文引用自作者博客:plankbevelen.cn

以 vue2 为基础,实现一个简易版的响应式系统。 首先了解 vue2 的响应式系统是如何实现的。首先举例(以下为伪代码):

javascript 复制代码
let val = 1;
val = 2;
<div>{{ val }}</div> => <div>1</div> => <div>2</div>

以上总的流程为:改变val值时,vue追踪到val的变化,然后通知所有依赖val的地方,最后更新视图。 vue官方说的是发布订阅模式,核心即:追踪变化,收集依赖,触发更新。

追踪变化 vue2 中是通过 Object.defineProperty 实现的。vue3 中是通过 Proxy 实现的,核心思路是不变的。

js 复制代码
function defineReactive(obj, key, val) {
    object.defineProperty(obj, key, {
        get() {
            return val;
        },
        set(newVal) {
            if (newVal !== val) {
                val = newVal;
            }
        }
    })
}

这个借助Object.defineProperty,为对象的属性添加了getter和setter,当属性被访问或修改时,会触发getter和setter,达到数据变化追踪的效果。 而这个只能为对象的单个属性,而且只能添加一次,所以可以进行递归遍历,为对象的每个属性都加上getter和setter.

js 复制代码
// 检测数据变化
class Observer {
    constructor(value) {
        this.value = value;
        if(value && typeof value === 'object') Array.isArray(value) ? obserbeArray(value) : walk(value);
    }
    walk(obj) {
        for(const key in obj) {
            defineReactive(obj, key, obj[key]);
        }
    }
    obserbeArray(obj) {
        for(let i = 0; i < obj.length; i++) {
            observe(obj[i]);
        }
    }
}

export function observe(val) {
    // 如果不是对象
    if(typeof val !== 'object') return 
    return new Observer(val); // 继续
}

Observer类用递归实现了一整个对象的所有属性的监听,如果数据有所变化的话,需要有一个类似于socket的东西去帮它传达更新消息,并执行其对应的业务逻辑。 vue 是使用了一个叫依赖的东西,当数据变化的时候,你所要通知的地方就是依赖。依赖收集的时期在getter时,等你在触发setter的时候就会去触发对应业务。到这儿整个流程大致会清晰一些,最后再做总结。所以中间先建立一个依赖收集器,只实现部分这儿用得到的功能:收集依赖、通知更新,就够了

js 复制代码
class Dep {
    constructor() {
        this.subs = [];
    }
    addSub(sub) {
        if(sub && sub.update && !this.subs.includes(sub)) this.subs.push(sub);
    }
    notify() {
        this.subs.foreach((sub) => sub.update());
    }
}
Dep.target = null;

(补一下收集依赖的部分)

js 复制代码
function defineReactive(obj, key, val) {
    object.defineProperty(obj, key, {
        get() {
            // 收集依赖
            this.addSub(Dep.target);
            return val;
        },
        set(newVal) {
            if (newVal !== val) {
                val = newVal;
                // 通知更新
                this.notify();
                // 监听新值
                observe(newVal);
            }
        }
    })
}

最后是实现,依赖所要通知的单位,这个单位呢有很多种,比如用户实现的watch,template中的数据等,为了方便管理起来,vue中把这个叫做watcher进行集中管理

js 复制代码
class Watcher {
    // vm是组件实例,expOrFn是监听对应的数据,cb是回调函数,也就是其对应的业务逻辑
    constructor(vm, expOrFn, cb) {
        this.vm = vm;
        this.cb = cb;
        if(typeof expOrFn === 'function') {
            this.expOrFn = expOrFn; // 直接执行
        } else {
            this.expOrFn = parseExp(exp);
        }
        this.value = this.get();
    }
    get() {
        Dep.target = this;
        const val = this.expOrFn(this.vm);
        Dep.target = null;
        return val;
    }
    update() {
        let oldValue = this.value;
        let newValue = this.get();
        if(oldValue !== newValue) {
            this.value = newValue;
            this.cb.call(this.vm, newValue);
        }
    }
    parseExp(exp) {
        if(/[^\w.$]/.test(exp)) throw new Error('表达式格式错误');
        const segments = exp.split('.');
        return function(obj) {
            let val = obj;
            segments.forEach((segment) => {
                val = val[segment];
            })
            return val;
        }
    }
}

总的结果就是Observer类负责监听数据变化,Dep类负责收集依赖和通知更新,Watcher类负责订阅数据变化并执行回调函数。

相关推荐
障碍的枫子3 小时前
Vue组件导出&渲染
前端·javascript·vue.js
开开心心就好3 小时前
视频播放器完美解码集成三款播放器切换使用
前端·人工智能·智能手机·电脑·音视频·virtualenv·pygame
Sterting3 小时前
组件通信:Props 与 Emit
前端·vue.js
东方老白3 小时前
iOS 面试题
前端
hunterandroid3 小时前
Compose LazyColumn 性能优化:从卡顿到流畅的大列表治理
前端
sakiko_3 小时前
Swift学习笔记39-实战注意事项
前端·笔记·学习·swift
海带紫菜菠萝汤3 小时前
MSE (Media Source Extensions) 实战:流媒体分块加载与自适应码率
前端·javascript·音视频
@小匠4 小时前
Spring Boot Nacos绑定 Map 时中文 key 导致启动失败:一次从复现到源码的排查实录
java·开发语言·前端
黄华SJ520it4 小时前
顶俏洗衣液模式制度开发介绍:S2B2C社交分销+多门店核销系统全解析
前端·数据库·小程序·零售·系统开发
晓说前端4 小时前
TypeScript 核心语法应用 —— Vue 3 中的使用(上)
前端·typescript