千家信息网

Vue2响应式系统之怎么让数组生效

发表于:2025-02-04 作者:千家信息网编辑
千家信息网最后更新 2025年02月04日,这篇文章主要介绍了Vue2响应式系统之怎么让数组生效的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Vue2响应式系统之怎么让数组生效文章都会有所收获,下面我们一起来看看吧
千家信息网最后更新 2025年02月04日Vue2响应式系统之怎么让数组生效

这篇文章主要介绍了Vue2响应式系统之怎么让数组生效的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Vue2响应式系统之怎么让数组生效文章都会有所收获,下面我们一起来看看吧。

1、场景

import { observe } from "./reactive";import Watcher from "./watcher";const data = {    list: ["hello"],};observe(data);const updateComponent = () => {    for (const item of data.list) {        console.log(item);    }};new Watcher(updateComponent);data.list = ["hello", "liang"];

先可以一分钟思考下会输出什么。

虽然 的值是数组,但我们是对 进行整体赋值,所以依旧会触发 的 ,触发 进行重新执行,输出如下:listdata.listdata.listsetWatcher

2、场景 2

import { observe } from "./reactive";import Watcher from "./watcher";const data = {    list: ["hello"],};observe(data);const updateComponent = () => {    for (const item of data.list) {        console.log(item);    }};new Watcher(updateComponent);data.list.push("liang");

先可以一分钟思考下会输出什么。

这次是调用 方法,但我们对 方法什么都没做,因此就不会触发 了。pushpushWatcher

3、方案

为了让 还有数组的其他方法也生效,我们需要去重写它们,通过push代理模式 我们可以将数组的原方法先保存起来,然后执行,并且加上自己额外的操作。

/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype *//*export function def(obj, key, val, enumerable) {    Object.defineProperty(obj, key, {        value: val,        enumerable: !!enumerable,        writable: true,        configurable: true,    });}*/import { def } from "./util";const arrayProto = Array.prototype;export const arrayMethods = Object.create(arrayProto);const methodsToPatch = [    "push",    "pop",    "shift",    "unshift",    "splice",    "sort",    "reverse",];/** * Intercept mutating methods and emit events */methodsToPatch.forEach(function (method) {    // cache original method    const original = arrayProto[method];    def(arrayMethods, method, function mutator(...args) {        const result = original.apply(this, args);        /*****************这里相当于调用了对象 set 需要通知 watcher ************************/        // 待补充        /**************************************************************************** */        return result;    });});

当调用了数组的 或者其他方法,就相当于我们之前重写属性的 ,上边待补充的地方需要做的就是通知 中的 。pushsetdepWatcher

export function defineReactive(obj, key, val, shallow) {    const property = Object.getOwnPropertyDescriptor(obj, key);    // 读取用户可能自己定义了的 get、set    const getter = property && property.get;    const setter = property && property.set;    // val 没有传进来话进行手动赋值    if ((!getter || setter) && arguments.length === 2) {        val = obj[key];    }    const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher    let childOb = !shallow && observe(val);    Object.defineProperty(obj, key, {        enumerable: true,        configurable: true,        get: function reactiveGetter() {            const value = getter ? getter.call(obj) : val;            if (Dep.target) {                dep.depend();            }            return value;        },        set: function reactiveSetter(newVal) {            const value = getter ? getter.call(obj) : val;            if (setter) {                setter.call(obj, newVal);            } else {                val = newVal;            }            dep.notify();        },    });}

如上边的代码,之前的 是通过闭包,每一个属性都有一个各自的 ,负责收集 和通知 。depdepWatcherWatcher

那么对于数组的话,我们的 放到哪里比较简单呢?dep

回忆一下现在的结构。

const data = {    list: ["hello"],};observe(data);const updateComponent = () => {    for (const item of data.list) {        console.log(item);    }};new Watcher(updateComponent);

上边的代码执行过后会是下图的结构。

list 属性在闭包中拥有了 属性,通过 ,收集到了包含 函数的 。Depnew WatcherupdateCompnentWatcher

同时因为 的 是数组,也就是对象,通过上篇 listvalue["hello"]响应式系统之深度响应 (opens new window)我们知道,它也会去调用 函数。Observer

那么,我是不是在 中也加一个 就可以了。ObserverDep

这样当我们调用数组方法去修改 的值的时候,去通知 中的 就可以了。['hello']ObserverDep

3、收集依赖代码实现

按照上边的思路,完善一下 类。Observer

export class Observer {    constructor(value) {        /******新增 *************************/        this.dep = new Dep();        /************************************/          this.walk(value);    }    /**     * 遍历对象所有的属性,调用 defineReactive     * 拦截对象属性的 get 和 set 方法     */    walk(obj) {        const keys = Object.keys(obj);        for (let i = 0; i < keys.length; i++) {            defineReactive(obj, keys[i]);        }    }}

然后在 中,当前 中的 也去收集依赖。getOberverdep

export function defineReactive(obj, key, val, shallow) {    const property = Object.getOwnPropertyDescriptor(obj, key);    // 读取用户可能自己定义了的 get、set    const getter = property && property.get;    const setter = property && property.set;    // val 没有传进来话进行手动赋值    if ((!getter || setter) && arguments.length === 2) {        val = obj[key];    }    const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher    let childOb = !shallow && observe(val);    Object.defineProperty(obj, key, {        enumerable: true,        configurable: true,        get: function reactiveGetter() {            const value = getter ? getter.call(obj) : val;            if (Dep.target) {                dep.depend();                /******新增 *************************/                if (childOb) {                    // 当前 value 是数组,去收集依赖                    if (Array.isArray(value)) {                        childOb.dep.depend();                    }                }                /************************************/            }            return value;        },        set: function reactiveSetter(newVal) {            const value = getter ? getter.call(obj) : val;            if (setter) {                setter.call(obj, newVal);            } else {                val = newVal;            }            dep.notify();        },    });}

4、通知依赖代码实现

我们已经重写了 方法,但直接覆盖全局的 方法肯定是不好的,我们可以在 类中去操作,如果当前 是数组,就去拦截它的 方法。arrayarrrayObservervaluearray

这里就回到 的原型链上了,我们可以通过浏览器自带的 ,将当前对象的原型指向我们重写过的方法即可。js__proto__

考虑兼容性的问题,如果 不存在,我们直接将重写过的方法复制给当前对象即可。__proto__

import { arrayMethods } from './array' // 上边重写的所有数组方法/* export const hasProto = "__proto__" in {}; */export class Observer {    constructor(value) {        this.dep = new Dep();          /******新增 *************************/        if (Array.isArray(value)) {            if (hasProto) {                protoAugment(value, arrayMethods);            } else {                copyAugment(value, arrayMethods, arrayKeys);            }        /************************************/        } else {            this.walk(value);        }    }    /**     * 遍历对象所有的属性,调用 defineReactive     * 拦截对象属性的 get 和 set 方法     */    walk(obj) {        const keys = Object.keys(obj);        for (let i = 0; i < keys.length; i++) {            defineReactive(obj, keys[i]);        }    }}/** * Augment a target Object or Array by intercepting * the prototype chain using __proto__ */function protoAugment(target, src) {    /* eslint-disable no-proto */    target.__proto__ = src;    /* eslint-enable no-proto */}/** * Augment a target Object or Array by defining * hidden properties. *//* istanbul ignore next */function copyAugment(target, src, keys) {    for (let i = 0, l = keys.length; i < l; i++) {        const key = keys[i];        def(target, key, src[key]);    }}

还需要考虑一点,数组方法中我们只能拿到 值,那么怎么拿到 对应的 呢。valuevalueObserver

我们只需要在 类中,增加一个属性来指向自身即可。Observe

export class Observer {    constructor(value) {        this.dep = new Dep();        /******新增 *************************/        def(value, '__ob__', this)        /************************************/        if (Array.isArray(value)) {            if (hasProto) {                protoAugment(value, arrayMethods);            } else {                copyAugment(value, arrayMethods, arrayKeys);            }        } else {            this.walk(value);        }    }      ...}

回到最开始重写的 方法中,只需要从 中拿到 去通知 即可。array__ob__DepWatcher

/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */import { def } from "./util";const arrayProto = Array.prototype;export const arrayMethods = Object.create(arrayProto);const methodsToPatch = [    "push",    "pop",    "shift",    "unshift",    "splice",    "sort",    "reverse",];/** * Intercept mutating methods and emit events */methodsToPatch.forEach(function (method) {    // cache original method    const original = arrayProto[method];    def(arrayMethods, method, function mutator(...args) {        const result = original.apply(this, args);        /*****************这里相当于调用了对象 set 需要通知 watcher ************************/        const ob = this.__ob__;        // notify change        ob.dep.notify();        /**************************************************************************** */        return result;    });});

5、测试

import { observe } from "./reactive";import Watcher from "./watcher";const data = {    list: ["hello"],};observe(data);const updateComponent = () => {    for (const item of data.list) {        console.log(item);    }};new Watcher(updateComponent);data.list.push("liang");

这样当调用 方法的时候,就会触发相应的 来执行 函数了。pushWatcherupdateComponent

当前的依赖就变成了下边的样子:

关于"Vue2响应式系统之怎么让数组生效"这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对"Vue2响应式系统之怎么让数组生效"知识都有一定的了解,大家如果还想学习更多知识,欢迎关注行业资讯频道。

0