当前位置:首页 > 简单说明setter getter数据驱动原理 类似vue数据驱动

简单说明setter getter数据驱动原理 类似vue数据驱动

发布于 2018-04-18 阅读 525 次 Vue Javascript ES6

数据双向绑定小练习
首先遍历data 使用Object.defineProperty设置setter 和 getter
在这里我定义了一个observe函数来做这个工作
这样当我们获取data的值的时候就会getter 设置值的时候会触发setter
然后我们需要监听数据的变化,这样我们就需要一个Watcher,
创建一个观察者类Dep用来存储触发观察者Watcher,如何建立watcher和data之间的关系呢
数据变化的时候会触发setter所以我们可以在setter的时候通知观察者数据有变化。
观察者从哪里来呢,我们遍历data的时候在getter里面去添加观察者,我们只需要初始化的时候添加观察者,
所以我们需要有一个判断,我们可以判断Dep.target来区分
创建Wacher类,Watcher具有对比新旧值的能力,通过调用update方法来通知Compile来更新视图
Compile就是一个编译更新视图的类
我这里只是简单的实现了v-bind的绑定ElementNode的小功能,当然真正的双向绑定要复杂的多,
这里只是简单是阐述一下原理。仅供参考。欢迎大家拍砖指正。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div class="app">
        姓名:<span v-bind="name"></span><div></div>
        年龄:<span v-bind="age"></span>
    </div>
    <script>

    // 设置setter getter
    function observe(data) {
        if (!data || typeof data !== 'object') {
            return;
        }
        // 取出所有属性遍历
        Object.keys(data).forEach(function(key) {
            defineReactive(data, key, data[key]);
        });
    };

    function defineReactive(data, key, val) {
        var dep = new Dep();
        observe(val); // 监听子属性
        Object.defineProperty(data, key, {
            enumerable: true, // 可枚举
            configurable: false, // 不能再define
            get: function() {
                if(Dep.target){
                    // 添加订阅者
                    dep.addSub(Dep.target);
                    console.log('subs',dep.subs);
                }
                return val;
            },
            set: function(newVal) {
                console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal);
                val = newVal;
                // 通知订阅者
                dep.notify();
            }
        });
    }

    // 通过v-bind属性值获对应data里面属性的值
    var tools =  {
        _getVMVal: function(vm, exp) {
            var val = vm;
            exp = exp.split('.');
            exp.forEach(function(k) {
                val = val[k];
            });
            return val;
        }
    }
    // 观察者
    function Dep(){
        this.subs = []
    }

    Dep.prototype.addSub = function(watcher){
        this.subs.push(watcher)
    }

    // 每次值有所改变就要遍历所有Watcher
    Dep.prototype.notify = function(){
        this.subs.forEach(function(watcher){
            watcher.update();
        });
    }

    // 订阅者
    function Watcher (vm, exp, cb){
        this.vm = vm;
        this.exp = exp;
        this.cb = cb;
        this.value = tools._getVMVal(this.vm,this.exp);
    }
    Watcher.prototype.update = function(){
        var oldVal = this.value;
        var newVal = tools._getVMVal(this.vm,this.exp);
        this.cb(oldVal, newVal);
    }

    Watcher.prototype.get = function(){
        Dep.target = this;
        //这里会触发属性的getter,从而添加订阅者
        this.value = tools._getVMVal(this.vm, this.exp);
        Dep.target = null;
    }

    // 编译器用来更新视图
    function Compile(el, vm){
        this.$el = document.querySelector(el);
        this.$vm = vm;
        this.init();
    }

    Compile.prototype.init = function(){
        this.compileElement();
    }

    Compile.prototype.compileElement = function(){
        var nodes = this.$el.childNodes,
            self = this;
        [].slice.call(nodes).forEach(function(node){
            if (node.childNodes && node.childNodes.length) {
                me.compileElement(node);
            }
            if(node.nodeType == 1){
                self.compile(node);
            }

        })
    }
    Compile.prototype.compile = function(node){
        var nodeAttrs = node.attributes,
            self = this;
        [].slice.call(nodeAttrs).forEach(function(attr){
            if(attr.name.indexOf('v-bind') == 0){
                var exp = attr.value;
                var val = tools._getVMVal(self.$vm, exp);
                node.innerHTML = val;
                var w = new Watcher(self.$vm, exp, function(oldVal, newVal){
                    if(oldVal!==newVal){
                        node.innerHTML = newVal;
                    }
                });
                w.get();
            }
        });
    }


    // 初始化调用
    var data = {
        'name': '小幺鸡',
        'age': '12'
    };

    observe(data);
    new Compile('.app',data);

    data.name = 'XiaoYaoJi'; // 哈哈哈,监听到值变化了 小幺鸡  -->  XiaoYaoJi
    </script>
</body>
</html>