object如何转list_深入浅出vue.js-object的变化侦测

在vue2中,实现监听的主要方法是 Object.defineProperty ,将一个对象的每一个属性,都变为getter/setter的方式。但是在数据和视图中间,还做了很多其他的工作。

1、如何实现object的变化侦测

想要实现数据侦测,首先要获取到两个重要的时机:数据赋值和数据读取。当数据读取时,我们来确定有哪些地方使用了数据;当数据赋值时,我们来通知这些地方,数据已经更新:

b4e41ee2e3a451b5ab891cce11e0e5a4.png

解释下这个图

data:源数据

observer :给数据源增加getter 和 setter

getter:获取数据,这时候要记录哪些地方,用到了数据,记录在 Dep 中

setter:设置数据,这时候,要通知用到了这些数据的地方,数据已经更新

watcher :绑定某个UI,或者其他的东西。代表了一个使用了数据的东西,就是getter中“记录哪些地方”的那个地方

dep:容器,存储watcher作用。

2、代码实现

首先实现 Observer 部分,这部分的作用,是将一个对象的每一个属性,都转为getter/setter。注意,Observer 只处理 对象,不处理数组

class Observer{
    constructor(data){
        this.value = data;

        // 区分 object 还是 array
        if(Array.isArray(this.value)){
            // 当传入的是 数组 时的处理逻辑
        }else {
            this.walk(this.value);
        }
    }

    /**
     * walk 方法,将对象的每一个属性,都转为 getter/setter
     * 只有数据类型是 object 时才调用
     * 所以,遍历调用 defineReactive,defineReactive是封装了Object.defineProperty的方法
     */
    walk(d){
        let keys = Object.keys(d);
        for(let k of keys){
            defineReactive(d,k,d[k]);
        }
    }
}

因为 Observer 中调用的 defineReactive ,我们继续实现这个函数:

function defineReactive(data,key,val){
    if(typeof val === 'object'){
        new Observer(val)
    }
    Object.defineProperty(data,key,{
        enumerable:true,
        configurable:true,
        get() {
            // 01
            // get 时,存储哪些地方使用了数据
            // 也就是将使用了数据的watcher,存储到 dep 中
            return val
        },
        set(v) {
            if(v !== val){
            // 02
            // set 时,通知使用了数据的地方
            // 也就是将dep中存储的watcher,都调用一遍
                val = v;
            }
        }
    })
}

我们首先实现一个Dep,用来存储watcher。这个dep应该具备如下功能:

  • 能存储watcher
  • 新增一个watcher进行存储
  • 当数据变化时,将新旧值,传入watcher
class Dep{
    constructor() {
        // let watchers = []
        this.watchers = []
    }

    /**
     * 添加 watcher
     */
    apend(w){
        this.watchers.push(w)
    }

    /**
     * 更新时,通知依赖
     * 将更新后的值,和原来的值,都传递给依赖
     * cb 依赖在值变化时的回调方法
     */
    update(newVal,oldVal){
        for(let k of this.watchers){
            k.cb(newVal,oldVal)
        }
    }
}

我们收集依赖时,不是直接收集使用 数据 的依赖,而是收集一个叫做watcher的东西。watcher绑定依赖。我们数据变化时,先通知到对应的watcher,watcher在去操作对应的依赖。所以,实现watcher如下:

/**
 * 实现 watcher 构造函数
 * */
class Watcher{
    /**
     * @param vm : 绑定的vm实例
     * @param expOrFn : 在这个vm 实例上,要操作的属性的路径
     * @param cb : 数据变化时的回调函数,接受到变化的数值
     */
    constructor(vm,expOrFn,cb){
        this.vm = vm;
        this.cb = cb;
        this.vPath = expOrFn;
    }
    /**
     * 根据传入的 属性路径,获取对象对应路径下的值
     * @param path
     */
    getObjVal(path){
        // 会对具体情况做一些容错
        let p_list = path.split('.');
        let v = this.vm['data'];
        for(let k of p_list){
            v = v[k]
        }
        return v;
    }
    /**
     * 将当前 watcher 实例,添加到 window.target 上
     * 然后 通过 读取属性,来触发依赖收集
     * 将当前 watcher 添加到依赖收集里
     */
    addDep(){
        window.target = this;
        this.value = this.getObjVal(this.vPath);
        window.target = undefined;
    }
}

这时候我们在修改下 defineReactive 函数,使其依赖收集规范化:

function defineReactive(data,key,val){
    if(typeof val === 'object'){
        new Observer(val)
    }
    let dep = new Dep();
    Object.defineProperty(data,key,{
        enumerable:true,
        configurable:true,
        get() {
            // 01
            // get 时,存储哪些地方使用了数据
            // 也就是将使用了数据的watcher,存储到 dep 中
            dep.apend(window.target)
            return val
        },
        set(v) {
            if(v !== val){
                // 02
                // set 时,通知使用了数据的地方
                // 也就是将dep中存储的watcher,都调用一遍
                val = v;
                dep.update(v,val)
            }
        }
    })
}
> vue-element-admin@4.4.0 dev > vue-cli-service serve --mode development ERROR Error loading F:\工作\his-erp-front\vue.config.js: ERROR Error: Cannot find module 'body-parser' Require stack: - F:\工作\his-erp-front\mock\mock-server.js - F:\工作\his-erp-front\vue.config.js - F:\工作\his-erp-front\node_modules\.store\@vue+cli-shared-utils@4.5.19\node_modules\@vue\cli-shared-utils\lib\module.js - F:\工作\his-erp-front\node_modules\.store\@vue+cli-shared-utils@4.5.19\node_modules\@vue\cli-shared-utils\index.js - F:\工作\his-erp-front\node_modules\.store\@vue+cli-service@4.4.4\node_modules\@vue\cli-service\bin\vue-cli-service.js Error: Cannot find module 'body-parser' Require stack: - F:\工作\his-erp-front\mock\mock-server.js - F:\工作\his-erp-front\vue.config.js - F:\工作\his-erp-front\node_modules\.store\@vue+cli-shared-utils@4.5.19\node_modules\@vue\cli-shared-utils\lib\module.js - F:\工作\his-erp-front\node_modules\.store\@vue+cli-shared-utils@4.5.19\node_modules\@vue\cli-shared-utils\index.js - F:\工作\his-erp-front\node_modules\.store\@vue+cli-service@4.4.4\node_modules\@vue\cli-service\bin\vue-cli-service.js at Function.Module._resolveFilename (internal/modules/cjs/loader.js:902:15) at Function.Module._load (internal/modules/cjs/loader.js:746:27) at Module.require (internal/modules/cjs/loader.js:974:19) at require (internal/modules/cjs/helpers.js:93:18) at Object.<anonymous> (F:\工作\his-erp-front\mock\mock-server.js:2:20) at Module._compile (internal/modules/cjs/loader.js:1085:14) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10) at Module.load (internal/modules/cjs/loader.js:950:32) at Function.Module._load (internal/modules/cjs/loader.js:790:12) at Module.require (internal/modules/cjs/loader.js:974:19)报错如何解决
07-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值