《深入浅出Vue.js》阅读笔记(一)Object的变化侦测

1 什么是变化侦测

在运行时,应用状态会不断的发生变化,页面需要反复渲染页面,如何确定其中的状态发生了哪些变化?

变化侦测就是用来解决该问题,变换侦测也是响应式系统的核心,没有它,就没有重新渲染。

2 如何追踪变化

js追踪变化的方法有两种,Object.defineProperty和Proxy,由于ES6在浏览器中的支持度并不理想,到目前为止,Vue.js还是使用Object.defineProperty。

Object.defineProperty是js中提供的一个方法,可以用于在对象中定义一个新属性或修改现有属性,并返回一个对象。当读取数据时,自动调用它里面的get方法,设置时,调用set方法。

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

defineReactive用来对Object.defineProperty进行封装,封装后只需要传递data、key和val。

当从data的key中读取数据时,get函数被触发,设置数据时,set函数被触发。

3 如何收集依赖

在getter中收集依赖,在setter中触发依赖

4 依赖收集在哪里

封装一个Dep类,用来管理依赖,可以收集依赖、删除依赖或者向依赖发送通知。

export default class Dep{
    constructor(){
        this.subs = [];
    }
    addSub(sub){         //收集
        this.subs.push(sub);
    }
    removeSub(sub){      //删除
        remove(this.subs,sub)
    }
    depend(){ 
        if(window.target){
            this.addSub(window.target);
        }
    }
    notify(){           //通知
        const subs = this.subs.slice();
        for(let i=0,l=subs.length;i<l;i++){
            subs[i].update();
        }
    }
    function remove(arr,item){
        if(arr.length){
            const index = arr.indexOf(item);
            if(index>-1){
                return arr.splice(index,1);
            }
        }
    }
}

改造下defineReaactive

function defineReactive(data,key,val){
    let dep = new Dep();//修改
    Object.defineProperty(data,key,{
        enumerable:true,
        get:function(){
            dep.depend()//修改
            return val
        }
        set:function(newVal){
            if(val==newVal){
                return;
            }
            val = newVal;
            dep.notify();//新增
    })
}

5 依赖是谁

知道状态发生变化不是最终结果,而是要通知使用了该状态的地方它们发生了变化。 

用到了该状态的地方就是我们要收集的,也就是依赖。

但使用这个数据的地方很多,而且类型还不一样,有可能是模板,也可能是用户写的一个watch,所以需要抽象出一个能集中处理这些情况的类,在依赖收集阶段,只收集这个封装好的类的实例进来,通知也只通知它一个。给它取了一个好听的名字,叫Watcher,即依赖。

6 什么是Watcher

Watcher是一个中介的角色,数据发生变化的时候通知它,然后它再去通知其他地方。

经典的使用方式:

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

把window.target赋一个this,再读一下值,触发getter,就可以将this主动添加到依赖数组的dep中。

之后,每当name的值发生变化,就会触发update方法,在其中执行参数中的回调函数。

(该部分来源https://blog.csdn.net/qq_16858683/article/details/101371154

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);
    }
}

到现在,已经实现了变化侦测的功能,但只能侦测数据中的某一个属性。

7 Observer类

为了侦测到数据的所有属性,包括子属性,封装一个Observer类。这个类的作用是将一个数据里的所有属性都转换成getter/setter的形式,然后追踪它们的变化。

(该部分来源https://blog.csdn.net/qq_16858683/article/details/101371154

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();
        }
    })
}

8 总结

Data通过Observer转换成getter/setter的形式来追踪变化。

当外界通过Watcher读取数据时,会触发getter将Watcher添加到依赖中。

当数据发生了变化,会触发setter,从而向Dep中的依赖(Watcher)发送通知。

Watcher接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新,也可能触发用户的某个回调函数。

注:

Vue.js通过Object.defineProperty来将对象的key转换成getter/setter的形式来追踪变化,但getter/setter只能追踪一个数据是否被修改,无法追踪新增属性和删除属性

但Vue提供了两个API解决这个问题

  • vm.$set
  • vm.$delete
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值