zoukankan      html  css  js  c++  java
  • VueJS 数据驱动和依赖追踪分析

    之前关于 Vue 数据绑定原理的一点分析,最近需要回顾,就顺便发到随笔上了

    在之前实现一个自己的Mvvm中,用 setter 来观测model,将界面上所有的 viewModel 绑定到 model 上。 当model改变,更新所有的viewModel,将新值渲染到界面上 。同时监听界面上通过v-model 绑定的所有 input,并通过 addEventListener 事件将新值更新到 model 上,以此来完成双向绑定 。

    但是那段程序除了用来理解 defineProperty,其它一文不值。

    • 没有编译节点 。
    • 没有处理表达式依赖 。

    这里我将解决表达式依赖这个问题,vue 模板的编译我会在下一节介绍 。

    为数据定义 getter & setter

    class Observer {
        constructor(data) {
            this._data = data;
            this.walk(this._data);
        }
    
        walk(data) {
            Object.keys(data).forEach((key) => { this.defineRective(data, key, data[key]) })
        };
        defineRective(vm, key, value) {
            var self = this;
            if (value && typeof value === "object") {
                this.walk(value);
            }
            Object.defineProperty(vm, key, {
                get: function() {
                    return value;
                },
                set: function(newVal) {
                    if (value != newVal) {
                        if (newVal && typeof newVal === "object") {
                            self.walk(newVal);
                        }
                        value = newVal;
                    }
                }
            })
        }
    }
    
    module.exports = Observer;
    

    这样,就为每个属性添加了 gettersetter ,当属性是一个对象,那么就递归添加。
    一旦获取属性值或者为属性赋值就会触发 getset ,当触发了 set,即model变化,就可以发布一个消息,通知所有viewModel 更新。

    defineRective(vm, key, value) {
        // 将这个属性的依赖表达式存储在闭包中。
        var dep = new Dep();
        var self = this;
        if (value && typeof value === "object") {
            this.walk(value);
        }
        Object.defineProperty(vm, key, {
            get: function() {
                return value;
            },
            set: function(newVal) {
                if (value != newVal) {
                    if (newVal && typeof newVal === "object") {
                        self.walk(newVal);
                    }
                    value = newVal;
                    // 通知所有的 viewModel 更新
                    dep.notify();
                }
            }
        })
    }
    

    那么怎么定义 Dep 呢??

    class Dep {
        constructor() {
            // 依赖列表
            this.dependences = [];
        }
        // 添加依赖
        addDep(watcher) {
            if (watcher) {
                this.dependences.push(watcher);
            }
        }
        // 通知所有依赖更新
        notify() {
            this.dependences.forEach((watcher) => {
                watcher.update();
            })
        }
    }
    
    module.exports = Dep;
    

    这里的每个依赖就是一个Watcher
    看看如何定义 Watcher
    这里每一个 Watcher 都会有一个唯一的id号,它拥有一个表达式和一个回调函数 。
    比如 表达式 a +b ; 会在get 计算时 访问 ab , 由于 JavaScript是单线程,任一时刻只有一处JavaScript代码在执行, 用Dep.target 作为一个全局变量来表示当前 Watcher 的表达式,然后通过 compute 访问 ab ,触发 a 与b 的getter,在 getter 里面将 Dep.target 添加为依赖 。
    一旦 a 与 b 的set 触发,调用 update 函数,更新依赖的值 。

    var uid = 0;
    class Watcher {
        constructor(viewModel, exp, callback) {
            this.viewModel = viewModel;
            this.id = uid++;
            this.exp = exp;
            this.callback = callback;
            this.oldValue = "";
            this.update();
        }
    
        get() {
            Dep.target = this;
            var res = this.compute(this.viewModel, this.exp);
            Dep.target = null;
            return res;
        }
    
        update() {
            var newValue = this.get();
            if (this.oldValue === newValue) {
                return;
            }
            // callback 里进行Dom 的更新操作
            this.callback(newValue, this.oldValue);
            this.oldValue = newValue;
        }
    
        compute(viewModel, exp) {
            var res = replaceWith(viewModel, exp);
            return res;
        }
    }
    
    module.exports = Watcher;
    
    

    由于当前表达式需要在 当前的model下面执行,所以 采用replaceWith 函数来代替 with ,具体可以查看另一篇随笔 javascript 中 with 的替代语法

    通过get 添加依赖

    Object.defineProperty(vm, key, {
        get: function() {
            var watcher = Dep.target;
            if (watcher && !dep.dependences[watcher.id]) {
                dep.addDep(watcher);
            }
            return value;
        },
        set: function(newVal) {
            if (value != newVal) {
                if (newVal && typeof newVal === "object") {
                    self.walk(newVal);
                }
                value = newVal;
                dep.notify();
            }
        }
    })
    

    这种添加依赖的方式实在太巧妙了 。
    这里我画了一个图来描述

    最后通过一段代码简单测试一下

    const Observer = require('./Observer.js');
    const Watcher = require('./watcher.js');
    var data = {
        a: 10,
        b: {
            c: 5,
            d: {
                e: 20,
            }
        }
    }
    
    var observe = new Observer(data);
    
    var watcher = new Watcher(data, "a+b.c", function(newValue, oldValue) {
        console.log("new value is  " + newValue);
        console.log("oldValue is  " + oldValue);
    });
    console.log("
    ");
    console.log("a has changed to 50,then the expr should has value 55");
    data.a = 50;
    
    console.log("
    ");
    console.log("b.c has changed to 50,then the expr should has value 122");
    data.b.c = 72;;
    
    console.log("
    ");
    console.log("b.c has reseted an object,then the expr should has value 80");
    data.b = { c: 30 }
    
    

    OK 大功告成

  • 相关阅读:
    [语言基础] 我只想导入目标包中的一个模块,没想到目标包的其他非模块代码也被执行了。。
    [vscode] os.getcwd(),调试和命令行运行的结果不一致
    234. 回文链表
    不生成新数组的迭代器方法:forEach()&every()&some()&reduce()&reduceRight()
    合并数组并对数组排序
    为数组排序:sort()&reverse()
    从数组中间位置添加元素:unshift()方法的有一种运用
    从数组中删除元素:pop()&unshift()方法
    为数组添加元素:push()&unshift()方法
    由已有数组创建新数组:concat()&splice()方法
  • 原文地址:https://www.cnblogs.com/likeFlyingFish/p/6744212.html
Copyright © 2011-2022 走看看