• 简述vue的双向绑定原理


    一、前言

      在vue的视图层与modal层进行数据交互的时,视图层的数据传入到modal层,modal层通过defineProperty来劫持每个元素,并绑定监听事件进行监听,一旦监听到数据变化,就通过defineProperty的set函数重新更新视图层。

    二、使用Object.defineProperty( )

      实现核心方法就是前文所说的Object.defineProperty( )。如果要对所有属性都进行监听的话,那么可以通过递归方法遍历所有属性值,并对其进行Object.defineProperty( )处理。

    // 1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者
    function Observer(data) {
        this.data = data;
        this.walk(data);
    }
    
    Observer.prototype = {
        walk: function(data) {
            var self = this;
            Object.keys(data).forEach(function(key) {
                self.defineReactive(data, key, data[key]);
            });
        },
        defineReactive: function(data, key, val) {
            var dep = new Dep();
            var childObj = observe(val);
            Object.defineProperty(data, key, {
                enumerable: true,
                configurable: true,
                get: function getter () {
                    if (Dep.target) {
                        dep.addSub(Dep.target);
                    }
                    return val;
                },
                set: function setter (newVal) {
                    if (newVal === val) {
                        return;
                    }
                    val = newVal;
                    dep.notify();
                }
            });
        }
    };
    
    function observe(value, vm) {
        if (!value || typeof value !== 'object') {
            return;
        }
        return new Observer(value);
    };
    
    function Dep () {
        this.subs = [];
    }
    Dep.prototype = {
        addSub: function(sub) {
            this.subs.push(sub);
        },
        notify: function() {
            this.subs.forEach(function(sub) {
                sub.update();
            });
        }
    };
    Dep.target = null;

    三、实现Watcher进行监听

      我们只要在订阅者Watcher初始化的时候出发对应的get函数去执行添加订阅者操作即可,只要获取对应的属性值就可以触发get函数,核心原因就是因为我们使用了Object.defineProperty( )进行数据监听。

    // 2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
    function Watcher(vm, exp, cb) {
        this.cb = cb;
        this.vm = vm;
        this.exp = exp;
        this.value = this.get();  // 将自己添加到订阅器的操作
    }
    
    Watcher.prototype = {
        update: function() {
            this.run();
        },
        run: function() {
            var value = this.vm.data[this.exp];
            var oldVal = this.value;
            if (value !== oldVal) {
                this.value = value;
                this.cb.call(this.vm, value, oldVal);
            }
        },
        get: function() {
            Dep.target = this;  // 缓存自己
            var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
            Dep.target = null;  // 释放自己
            return value;
        }
    };

    四、最后利用Compile解析和绑定dom节点

      解析器Compile实现步骤:

        1.解析模板指令,并替换模板数据,初始化视图;

        2.将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器;

       为了解析模板,首先需要获取到dom元素,然后对含有dom元素上含有指令的节点进行处理,因此这个环节需要对dom操作比较频繁,所有可以先建一个fragment片段,将需要解析的dom节点存入fragment片段里再进行处理:

    // 3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
    function Compile(el, vm) {
        this.vm = vm;
        this.el = document.querySelector(el);
        this.fragment = null;
        this.init();
    }
    
    Compile.prototype = {
        init: function () {
            if (this.el) {
                this.fragment = this.nodeToFragment(this.el);
                this.compileElement(this.fragment);
                this.el.appendChild(this.fragment);
            } else {
                console.log('Dom元素不存在');
            }
        },
        nodeToFragment: function (el) {
            var fragment = document.createDocumentFragment();
            var child = el.firstChild;
            while (child) {
                // 将Dom元素移入fragment中
                fragment.appendChild(child);
                child = el.firstChild
            }
            return fragment;
        },
        compileElement: function (el) {
            var childNodes = el.childNodes;
            var self = this;
            [].slice.call(childNodes).forEach(function(node) {
                var reg = /{{(.*)}}/;
                var text = node.textContent;
    
                if (self.isElementNode(node)) {  
                    self.compile(node);
                } else if (self.isTextNode(node) && reg.test(text)) {
                    self.compileText(node, reg.exec(text)[1]);
                }
    
                if (node.childNodes && node.childNodes.length) {
                    self.compileElement(node);
                }
            });
        },
        compile: function(node) {
            var nodeAttrs = node.attributes;
            var self = this;
            Array.prototype.forEach.call(nodeAttrs, function(attr) {
                var attrName = attr.name;
                if (self.isDirective(attrName)) {
                    var exp = attr.value;
                    var dir = attrName.substring(2);
                    if (self.isEventDirective(dir)) {  // 事件指令
                        self.compileEvent(node, self.vm, exp, dir);
                    } else {  // v-model 指令
                        self.compileModel(node, self.vm, exp, dir);
                    }
                    node.removeAttribute(attrName);
                }
            });
        },
        compileText: function(node, exp) {
            var self = this;
            var initText = this.vm[exp];
            this.updateText(node, initText);
            new Watcher(this.vm, exp, function (value) {
                self.updateText(node, value);
            });
        },
        compileEvent: function (node, vm, exp, dir) {
            var eventType = dir.split(':')[1];
            var cb = vm.methods && vm.methods[exp];
    
            if (eventType && cb) {
                node.addEventListener(eventType, cb.bind(vm), false);
            }
        },
        compileModel: function (node, vm, exp, dir) {
            var self = this;
            var val = this.vm[exp];
            this.modelUpdater(node, val);
            new Watcher(this.vm, exp, function (value) {
                self.modelUpdater(node, value);
            });
    
            node.addEventListener('input', function(e) {
                var newValue = e.target.value;
                if (val === newValue) {
                    return;
                }
                self.vm[exp] = newValue;
                val = newValue;
            });
        },
        updateText: function (node, value) {
            node.textContent = typeof value == 'undefined' ? '' : value;
        },
        modelUpdater: function(node, value, oldValue) {
            node.value = typeof value == 'undefined' ? '' : value;
        },
        isDirective: function(attr) {
            return attr.indexOf('v-') == 0;
        },
        isEventDirective: function(dir) {
            return dir.indexOf('on:') === 0;
        },
        isElementNode: function (node) {
            return node.nodeType == 1;
        },
        isTextNode: function(node) {
            return node.nodeType == 3;
        }
    }

      这样就大功告成了,此文章参考https://www.cnblogs.com/libin-1/p/6893712.html,完整源代码下载,请点击这里获取

  • 相关阅读:
    【原】相煎何太急——input的blur事件与button的click事件
    【原】jQuery与CSS自动生成验证码
    【转】HTML转义字符大全
    【原】来自于一位前端“布道者”的建议
    【原】如何在jQuery中实现闭包
    【转】Web前端研发工程师编程能力飞升之路
    【原】git如何删除本地和远程的仓库
    H5不同样式新闻垂直滚动效果
    mui防止软键盘弹起方法
    js显示对象所有属性和方法的函数
  • 原文地址:https://www.cnblogs.com/zxd66666/p/12984358.html
Copyright © 2020-2023  润新知