• vue双向绑定的原理


    什么是双向数据绑定?Vue是一个MVVM框架,数据绑定简单来说,就是当数据发生变化时,相应的视图会进行更新,当视图更新时,数据也会跟着变化。

    实现数据绑定的方式大致有以下几种:

    - 1、发布者-订阅者模式(backbone.js)
    - 2、脏值检查(angular.js)
    - 3、数据劫持(vue.js)

    发布者-订阅者模式

    一般通过sub, pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是 vm.set('property', value),有兴趣可参考这里

    我们更希望可以通过 vm.property = value 这种方式进行数据更新,同时自动更新视图。

    脏值检查

    angular是通过脏值检查方式来对比数据是否变化,来决定是否更新视图,最常见的方式是通过setInterval()来监测数据变化,当然,只会在某些指定事件触发时下才进行脏值检查。大致如下:

    - DOM事件,譬如用户输入文本,点击按钮等。( ng-click )
    - XHR响应事件 ( $http )
    - 浏览器Location变更事件 ( $location )
    - Timer事件( $timeout , $interval )
    - 执行 $digest() 或 $apply()

    数据劫持

    Vue.js则是通过数据劫持以及结合发布者-订阅者来实现的,数据劫持是利用ES5的Object.defineProperty(obj, key, val)来劫持各个属性的的setter以及getter,在数据变动时发布消息给订阅者,从而触发相应的回调来更新视图。

    一、实现最基础的数据绑定

    <input type="text" id="in"/>
        输入的值为:<span id="out"></span>
    
        <script>
            var int = document.getElementById('in');
            var out = document.getElementById('out');
            var obj = {};
    
            Object.defineProperty(obj, 'msg', {
                enumerable: true,
                configurable: true,
                set (newVal) {
                    out.innerHTML = newVal;
                }
            })
    
            int.addEventListener('input', function(e) {
                obj.msg = e.target.value;
            })
        </script>

    Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性:里面有三个值,分别如下:

    1. obj 需要定义属性的当前对象
    2. prop 当前需要定义的属性名
    3. desc 属性描述符
    • 一般通过为对象的属性赋值的情况下,对象的属性可以修改也可以删除,但是通过Object.defineProperty()定义属性,通过描述符的设置可以进行更精准的控制对象属性。

    有兴趣可以深入了解,这里就不多解释了=。=

    二、双向数据绑定实现(此处用MVue替代)

    上面的只是简单的使用了Object.defineProperty(),并不是我们最终想要的效果,最终想要的效果如下:

    <div id="app">
            <input type="text" v-model="text">
            输入的值为:{{text}}
            <div>
                <input type="text" v-model="text">
            </div>
        </div>
        <script>
            var vm = new MVue({
                el: '#app',
                data: {
                    text: 'hello world'
                }
            })
        </script>

    实现思路:
    1、输入框以及文本节点和data中的数据进行绑定
    2、输入框内容变化时,data中的对应数据同步变化,即 view => model
    3、data中数据变化时,对应的文本节点内容同步变化 即 model => view

    上述流程如图所示:

    1、实现一个数据监听器Obverser,对data中的数据进行监听,若有变化,通知相应的订阅者。
    2、实现一个指令解析器Compile,对于每个元素上的指令进行解析,根据指令替换数据,更新视图。
    3、实现一个Watcher,用来连接Obverser和Compile, 并为每个属性绑定相应的订阅者,当数据发生变化时,执行相应的回调函数,从而更新视图。
    4、构造函数 (new MVue({}))

    MVue构造函数

    在初始化MVue实例时,对data中每个属性劫持监听,同时进行模板编译,指令解析,最后挂载到相应的DOM中。

    function MVue (options) {
            this.$el = options.el;
            this.$data = options.data;
    
            // 初始化操作,后面会说
            // ...
        }

    1、实现 view => model

    DocumentFragment(文档片段)

    vue进行编译时,将挂载目标的所有子节点劫持到DocumentFragment中,经过一份解析等处理后,再将DocumentFragment整体挂载到目标节点上。

    function nodeToFragment (node, vm) {
            var flag = document.createDocumentFragment();
            var child;
            while (child = node.firstChild) {
                compile(child, vm);
                 if (child.firstChild) {
                    var dom = nodeToFragment(child, vm);
                    child.appendChild(dom);
                }
                flag.appendChild(child);
            }
            return flag;
        }

    模板编译(指令解析,事件绑定、初始化数据绑定)

    编译过程图

    代码如下:

    function compile (node, vm) {
            let reg = /{{(.*)}}/;
            // 元素节点
            if (node.nodeType === 1) {
                var attrs = node.attributes;
                for (let attr of attrs) {
                    if (attr.nodeName === 'v-model') {
                        // 获取v-model指令绑定的data属性
                        var name = attr.nodeValue;
                        // 绑定事件
                        node.addEventListener('input', function(e) {
                            vm.$data[name] = e.target.value;
                        })
                        // 初始化数据绑定
                        node.value = vm.$data[name];
                        // 移除v-model 属性
                        node.removeAttribute('v-model')
                    }
                }
            }
            
            // 文本节点
            if (node.nodeType === 3) {
                if (reg.test(node.nodeValue)) {
                    var name = RegExp.$1 && (RegExp.$1.trim());
                    // 绑定数据到文本节点中
                     node.nodeValue = node.nodeValue.replace(new RegExp('\{\{\s*(' + name + ')\s*\}\}'), vm.$data[name]);
                }
            }
        }

    现在,我们修改下MVue构造函数,增加模板编译,如下:

    function MVue (options) {
            this.$el = options.el;
            this.$data = options.data;
    
            // 模板编译
            let elem = document.querySelector(this.$el);
            elem.appendChild(nodeToFragment(elem, this))
        }

    那么,我们的view => model 已经实现了,包括初始化绑定默认值,只要修改了input中的值,data中对应的值相应变化,并触发了setter, 更新属性值等(可以自行在set方法中打印看效果,或者在控制台手动输入vm.$data.text也会看到效果)。
    2、实现 model => view
    上面可以看出,虽然我们实现了初始化数据绑定,以及输入框变化时,data中text也会变化,但是文本节点仍然没有任何变化,那么如果做到文本节点也同步变化呢,这里用的是发布者-订阅者模式。

  • 相关阅读:
    被 5 整除的数
    天才的主意
    天才的主意
    谷歌浏览器 —— 快捷键(vimium:像使用 vim 一样操作当前页面)
    谷歌浏览器 —— 快捷键(vimium:像使用 vim 一样操作当前页面)
    膨胀和腐蚀操作中的不同结构元效果对比
    水果的辨异
    水果的辨异
    推理集 —— 特殊与差异
    推理集 —— 特殊与差异
  • 原文地址:https://www.cnblogs.com/LWWTT/p/11111874.html
Copyright © 2020-2023  润新知