美文网首页前端开发那些事儿
三、数据响应式原理

三、数据响应式原理

作者: 强某某 | 来源:发表于2021-04-14 16:47 被阅读0次

    知识点铺垫

    var obj = {};
    //getter和setter需要变量中转才能使用,所以不合适
    var temp;
    Object.defineProperty(obj, 'a', {
        //getter
        get() {
            return temp;
        },
        //setter
        set(val) {
            temp = val;
        }
        // value: 3,//value和get不能同时存在
        // writable:true,
        // enumerable:true
    });
    Object.defineProperty(obj, 'b', {
        value: 5,
        //是否可被枚举
        // enumerable:false
    });
    // obj.a++;
    // for (var item in obj) {
    //     console.log(item);//只输出a
    // }
    obj.a = 10;
    console.log(obj);
    
    • 闭包形式演化

    避免单独的temp变量

    var obj = {};
    //通过闭包,避免单独变量temp
    function defindReactive(data, key, val) {
        Object.defineProperty(data, key, {
            enumerable:true,
            configurable:true,//可配置,比如能被外部delete
            get() {
                return val;
            },
            set(newval) {
                if (newval === val) {
                    return;
                }
                val = newval;
            }
        });
    }
    defindReactive(obj,'a',10);
    obj.a++;
    console.log(obj);
    

    殊理

    • 实际上整体殊理逻辑要分门别类:例如,arr和object分开讨论;
      同时整体看起来好像是单向,但看对象的话实际上observe->Observer-> defineReactive,如果有set操作则又进入observe

    • 其中如果defineReactive如果知道对象有子对象,会继续从observe开始循环;即每次都是处理一层而已

    • 至于Dep(依赖收集)和Watcher是收集依赖和触发更新的,殊理逻辑可以最后再看

    1.png

    什么是依赖

    • 需要用的数据的地方,称为依赖
    • Vue2.x,中等依赖,用到数据的组件是依赖
    • 在getter中收集依赖,在setter中触发依赖

    Dep类和Watcher类

    • 依赖就是Watcher,只有在Watcher触发getter才会收集依赖,哪个Watcher触发了getter,就把哪个Watcher收集到Dep中
    • Dep使用发布订阅模式,当数据发生变化时候,会循环依赖列表,把所有Watcher都通知一遍
    • 代码实现的巧妙之处:Watcher把自己设置到全局的一个指定位置,然后读取数据,因为读取了数据,所以会触发这个数据的getter。在getter中就能得到当前正在读取数据的Watcher,并把这个Watcher收集到Dep中。
    4.png

    代码

    • index.js
    import { observe } from './observe.js'
    import Watcher from './Watcher.js'
    var obj = {
        a: {
            m: {
                n: 5
            }
        },
        b: 10,
        g:[2,3,4]
    };
    
    observe(obj);
    // obj.b++;
    obj.g.push(5);
    console.log(obj);
    new Watcher(obj,'a.m.n',val=>{
        console.log(1111,val);
    })
    
    • observe.js
    import Observer from './Observer.js'
    //创建observer函数,起辅助判别的作用:看obj身上有没有__ob__
    export const observe = function (value) {
        //如果value不是对象,什么也不做;因为直接数值时候没有必要
        if (typeof value !== 'object') {
            return
        }
        var ob;
        if (typeof value.__ob__ !== 'undefined') {
            ob = value.__ob__;//不希望和常见属性重名
        } else {
            ob = new Observer(value);
        }
        return ob;
    }
    
    • Observer.js
    import { def } from './utils.js';
    import defineReactive from './defineReactive.js'
    import { arrayMethods } from './arr.js'
    import { observe } from './observe.js'
    import Dep from './Dep.js'
    
    //Observer  将一个正常的object转换为每个层级的属性都是响应式(可以被侦测)的object
    export default class Observer {
        constructor(value) {
            //每个Observer的实例,成员中都有一个Dep的实例
            this.dep = new Dep();
            def(value, '__ob__', this, false);
            //检查它是数组还是对象
            if (Array.isArray(value)) {
                //如果是数组,将这个数组的原型,指向arrayMethods
                Object.setPrototypeOf(value, arrayMethods);
                //让这个数组变得observe
                this.observeArray(value);
            } else {
                this.walk(value);
            }
        }
        //遍历
        walk(value) {
            for (let key in value) {
                defineReactive(value, key);
            }
        }
        //数组特殊遍历
        observeArray(arr) {
            for (let i = 0; i < arr.length; i++) {
                observe(arr[i]);
            }
        }
    };
    
    • defineReactive.js
    import { observe } from './observe.js'
    import Dep from './Dep.js'
    export default function defindReactive(data, key, val) {
        //此处dep是闭包中的
        const dep = new Dep();
        if (arguments.length == 2) {
            val = data[key];
        }
        //子元素要进行observe,至此形成了递归;这个递归不是函数调用自己,是多个函数形成循环调用
        let childOb = observe(val);
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,
            get() {
                //如果现在处于依赖收集阶段
                if (Dep.target) {
                    dep.depend();
                    if (childOb) {
                        childOb.dep.depend();
                    }
                }
                return val;
            },
            set(newval) {
                if (newval === val) {
                    return;
                }
                val = newval;
                //当设置了新值,新值也要被observe,不然不是响应式的
                childOb = observe(newval);
                //发布订阅模式,通知dep
                dep.notify();
            }
        });
    }
    
    • arr.js
    import { def } from './utils.js';
    const arrayPrototype = Array.prototype;
    //以Array.prototype为原型,创建arrayMethods对象
    // 针对数组形成响应式,需要改写数组的七种方法
    // push
    // pop
    // shift
    // unshift
    // splice
    // sort
    // reverse
    export const arrayMethods = Object.create(arrayPrototype);
    
    // Object.setPrototypeOf(o,arrayMethods);
    // o.__proto__=arrayMethods;
    
    const methodsNeedChange = [
        'push',
        'pop',
        'shift',
        'unshift',
        'splice',
        'sort',
        'reverse'];
    methodsNeedChange.forEach(methodName => {
        //备份原来的方法
        const original = arrayPrototype[methodName];
        //定义新的方法
        def(arrayMethods, methodName, function () {
            //把类数组对象变为数组,不然下面的slice无法调用
            const args=[...arguments];
            //把这个数组身上的__ob__取出来,__ob__已经被添加了,为什么已经被添加了?因为数组肯定不是最高层
            //比如obj.g属性是数组,obj不能是数组,第一次遍历obj这个对象的第一层的时候,已经给g属性,添加了__ob__属性
            const ob = this.__ob__;
    
            // 'push',
            // 'unshift',
            // 'splice',
            //这三者涉及插入新项,所以也需要变成observe
            let inserted = [];
            switch (methodName) {
                case 'push':
                case 'unshift':
                    inserted = args;
                    break;
                case 'splice':
                    inserted = args.slice(2);
                    break;
            }
            //判断有没有需要插入新项,让新项变成响应的
            if (inserted) {
                ob.observeArray(inserted);
            }
            //此处this是被数组打点调用的,而且不能用箭头函数
            original.apply(this, arguments);
            ob.dep.notify();
        }, false);
    })
    
    • Dep.js
    var uid = 0;
    export default class Dep {
        constructor() {
            this.id = uid++;
            //用数组存储自己的订阅者.放的是watcher的实例
            this.subs = [];
        }
        addSub(sub) {
            this.subs.push(sub);
        }
        //添加依赖
        depend() {
            //Dep.target:就是自己指定的全局的位置而已,只要是全局唯一即可
            if (Dep.target) {
                this.addSub(Dep.target);
            }
        }
        notify() {
            const subs = this.subs.slice();//浅克隆一份
            for (let i = 0; i < subs.length; i++) {
                subs[i].update();
            }
        }
    }
    
    • Watcher.js
    import Dep from "./Dep";
    
    var uid = 0;
    export default class Watcher {
        constructor(target, expression, callback) {
            this.id = uid++;
            this.target = target;
            this.gettr = parsePath(expression);//'a.b.c.d'
            this.callback = callback;
            this.value = this.get();
        }
        update() {
            this.getAndInvoke(this.callback);
        }
        get() {
            //进入依赖收集阶段,让全局的Dep,target设置为watcher本身,那么就是进入依赖收集阶段
            Dep.target = this;
            const obj = this.target;
            var value;
            try {
                value = this.gettr(obj);
            } finally {
                //收集完毕,退出,其他watcher需要
                Dep.target = null;
            }
            return value;
        }
        //获取数据并更新
        getAndInvoke(cb) {
            const value = this.get();
            if (value !== this.value || typeof value == 'object') {
                const oldValue = this.value;
                this.value = value;
                cb.call(this.target, value, oldValue);
            }
        }
    };
    function parsePath(str) {
        //作用是:把'a.b.c.d'这种转换成a{b:{c:{d:{}}}}
        var segments = str.split('.');
        return (obj) => {
            for (let i = 0; i < segments.length; i++) {
                if (!obj) {
                    return;
                }
                obj = obj[segments[i]];
            }
            return obj;
        }
    }
    

    相关文章

      网友评论

        本文标题:三、数据响应式原理

        本文链接:https://www.haomeiwen.com/subject/nrbmkltx.html