深入浅出vuejs读书笔记----------1、object的变化侦测

(1)变化侦测

变化侦测一般分为两种类型:拉(pull)和推(push)

拉:状态发生变化时,并不知道是哪个状态变了,只是知道状态可能发生变化了,然后发送信号给框架,框架收到信号后,通过暴力对比找出哪些DOM需要重写渲染(Angular:脏检查流程,react:虚拟DOM)
在这里插入图片描述
推:当状态发生变化时,他立刻就知道了,而且一定程度上知道哪些状态发生了变化。(vue)
在这里插入图片描述
通过两种变化侦测类型可以看出,‘推’知道的信息更多,那么也就可以进行更细粒度的更新

更细粒度的更新:如果一个状态绑定多个依赖,每个依赖表示一个具体的DOM节点,当这个状态发生改变时,就会想这个状态所绑定的依赖发送通知,让他们进行DOM的更新操作。其中,拉的粒度最粗。

vue.js 2.0引入了虚拟DOM,将粒度调整为中粒度,即一个状态所绑定的不是状态而是组件。在状态发生变化时,会通知到它所绑定的组件,组件内部在使用虚拟DOM进行对比。这降低了依赖数量和内存消耗。

(2)如何追踪变化

js中侦测对象变化的方法:Object.definePropertyes6的Proxy

vuejs 2.0使用​Object.defineProperty来侦测变化

通过defineProperty定义数据,每次读取该数据,get函数就会被触发,每次设置新的值,set函数就会被触发。

Object.defineProperty (data, key, {
    enumerable: true,
    configurable: true,
    value: "",
    get: function () {
        return value;
    },
    set: function (newVal) {
        if (value === newVal) return;
        value = newVal;
    }
})

(3)如何收集依赖

当然,知道状态发生变化不是结果,而是要通知那些使用了该状态的地方它们发生了变化。在getter中收集依赖,在setter中触发依赖。

vue封装了一个Dep类,用于收集储存依赖,并在其封装了一些收集、删除依赖与向依赖发送通知的发放;

export default class Dep {
    constructor() {
        this.subs = []
    }addSub(sub) {
        this.subs.push(sub)
    }// remove...// 收集依赖的方法,若存在一个依赖,则将该依赖push到subs数组中
    depend() {
        if (window.target) this.addSub(window.target);
    }// 循环通知每一个依赖
    notify() {
        // 数组深拷贝,也可用concat()
        const subs = this.subs.silce();
        for (let i = 0, len = subs.length; i < len; i ++) {
            // update()为依赖 更新自己数据所定义的触发方法,后面会写
            subs[i].update();
        }
    }
}

vue封装了defineProperty函数,来将依赖Dep类与变化侦测相结合:

function defineReactive(data, key, val) {let dep = new Dep();
​
    Object.defineProperty (data, key, {
        enumerable: true,
        configurable: true,
        get: function () {// 在getter中收集依赖
            dep.depend();return val;
        },
        set: function (newVal) {
            if (val === newVal) return;
            val = newVal;// 在setter中通知依赖
            dep.notify();
        }
    })
}

在vue中,依赖可能是template,也可能是用户手动写的watch;因此,vue抽象了一个能集中处理的类:​Watcher。

Watcher是一个中介,数据变化是通知它,然后它再通知其他地方。在setter中执行watcher.udate方法。

一个常见的例子:

vm.$watch("name", function(newVal, oldVal) {
    // something
});

我们在新建一个watch时,把这个watcher实例添加到name的dep中,当name改变触发setter时,遍历依赖数组dep,到该watcher时,触发其update方法,在update()中再执行我们定义的回调函数。
so:

export default class Watcher {
    constructor(vm, expOrFn, cb) {
        // vm为vue实例
        // expOrFn为表达式或函数(这个函数可能访问了多个数据,就会创建多个依赖),在上面的例子里,expOrFn为"name"
        // cb为回调函数,callback的缩写
        this.vm = vm;
        // 解析expOfFn的路径,读取它的值(parsePath函数是一个闭包,返回的是一个函数,该函数参数应该传这个值所在对象,其实就是vm)
        // 所以这里getter只是一个函数
        this.getter = parsePath(expOfFn);
        this.cb = cb;
        // 每实例化一个Watcher,都自动触发get(),触发收集依赖的逻辑
        this.value = this.get();
    }
    get() {
        window.target = this;
        // 读取expOrFn的值,从而触发了"name"的getter,从而触发了收集依赖:dep.depend();
        // depend() {
        //     if (window.target) this.addSubs(window.target);
        // }
        // 而depend()中是将window.target存入依赖,此时windo.target指向了Watcher的this!,正好把这个Watcher存入依赖,妙啊
        let value = this.getter.call(this.vm, this.vm);
        // 用完window.target就置为undefined,备胎...
        window.target = undefined;
        return value;
    }
    // "name"数据改变时,触发它自己的setter,然后遍历依赖。触发了该watcher的update方法。
    // 该方法调用回调函数
    update() {
        // 此时watcher中的value是老的值
        const oldValue = this.value;
        // 通过get()获取"name"最新的值,get中会判断这个watcher是否被加入到依赖中,防止重复添加依赖
        this.value = this.get();
        // callback中的this指向vm,然后传入newVal,oldVal,执行callback
        this.cb.call(this.vm, this.value, oldValue);
    }
}

前面都是手动侦测一个属性,在实际中,一个对象可能有很多属性。因此,vue封装了一个Observer类,将一个数据中的所有属性,包括子属性都转换为 getter/setter 的形式,这样就不会漏掉所有的值变化。

export class Observer {
    constructor(value) {
        this.value = value;
        // 数组和对象要分开处理
        if (!Array.isArray(value)) {
            this.walk(value);
        }
    }
    // 循环为每个属性添加侦测
    walk(obj) {
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i], obj[keys[i]]);
        }
    }
}
function defineReactive(data, key, val) {
    // 如果子属性还是个对象,则递归为这个对象的子属性添加侦测
    if (type val === "object") new Observer;
    let dep = new Dep();
    Object.defineProperty (data, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            // 在getter中收集依赖
            dep.depend();
            return val;
        },
        set: function (newVal) {
            if (val === newVal) return;
            val = newVal;
            // 在setter中通知依赖
            dep.notify();
        }
    })
}

下图为Data, Observer, Dep和Watcher之间的关系
在这里插入图片描述

(4)object存在的问题

在一些方法里,我们手动向已响应的对象中新增一个属性或删除一个属性:

this.obj.name = "lotus";
delete this.obj.name;

由于未通过Observer去添加侦测,所有这些新增属性的变化就侦测不到。因此,vue通过vm.$set和vm.$delete这两个API来解决这个问题。在vue3.0中,由于使用ES6的​Proxy,故不存在这个问题。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值