精华内容
下载资源
问答
  • 主要为大家详细介绍了Vue 3.0双向绑定原理的实现方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • vue数据双向绑定原理

    2021-02-03 14:11:00
    vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的 var vm = new Vue({ data: { obj: { a: 1 } }, created: function () { console.log(this.obj); } }); 结果 我们可以看到属性a有两...

    先来整个图

     

     

    vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的

    var vm = new Vue({
        data: {
            obj: {
                a: 1
            }
        },
        created: function () {
            console.log(this.obj);
        }
    });

    结果

    我们可以看到属性a有两个相对应的get和set方法,为什么会多出这两个方法呢?因为vue是通过Object.defineProperty()来实现数据劫持的。

    Object.defineProperty( )是用来做什么的?它可以来控制一个对象属性的一些特有操作,比如读写权、是否可以枚举,这里我们主要先来研究下它对应的两个描述属性get和set

    var Book = {
      name: 'vue权威指南'
    };
    console.log(Book.name);  // vue权威指南

    如果想要在执行console.log(book.name)的同时,直接给书名加个书名号,那要怎么处理呢?

    或者说要通过什么监听对象 Book 的属性值。这时候Object.defineProperty( )就派上用场了,代码如下:

    var Book = {}
    var name = '';
    Object.defineProperty(Book, 'name', {
      set: function (value) {
        name = value;
        console.log('你取了一个书名叫做' + value);
      },
      get: function () {
        return '《' + name + '》'
      }
    })
     
    Book.name = 'vue权威指南';  // 你取了一个书名叫做vue权威指南
    console.log(Book.name);  // 《vue权威指南》

     

    我们通过Object.defineProperty( )设置了对象Book的name属性

    对其get和set进行重写操作,

    顾名思义,get就是在读取name属性这个值触发的函数,set就是在设置name属性这个值触发的函数,

    所以当执行 Book.name = 'vue权威指南' 这个语句时,

    控制台会打印出 "你取了一个书名叫做vue权威指南",紧接着,当读取这个属性时,就会输出 "《vue权威指南》",

    因为我们在get函数里面对该值做了加工了。如果这个时候我们执行下下面的语句,控制台会输出什么?

    结果如看下:

    紧接着就是思路分析:

    实现mvvm包含两个方面、数据变化更新视图、视图变化更新数据

    关键点在于data如何更新view,因为view更新data其实可以通过事件监听即可,比如input标签监听 'input' 事件就可以实现了,所以我们着重来分析下,当数据改变,如何更新视图的。

    数据更新视图的重点是如何知道数据变化,只要知道数据变化了,那么接下来的事情都好处理了,

    如何知道数据变了?其实在上文我们已经给出答案了,

    就是通过object.defineProperty(对象的定义属性)对属性设置一个set函数,当数据改变了就会触发这个函数,所以我们只要将一些需要更新的方法放在这里面就可以实现data更新view了

    实现过程:

    已经知道了实现数据的双向绑定,首先就要对数据进行劫持监听,所以我们需要设置一个监听器observer,用来监听所有属性,如果属性发生变化了,就需要告诉订阅者Watcher看是否需要更新,

    因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。

    接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:

    1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

    2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

    3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

    4.Dep :消息订阅器,内部维护了一个数组,用来收集订阅者(Watcher),数据变动触发notify 函数,再调用订阅者的 update 方法

     

    1.实现一个Observer

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

    function defineReactive(data, key, val) {
        observe(val); // 递归遍历所有子属性
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,
            get: function() {
                return val;
            },
            set: function(newVal) {
                val = newVal;
                console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
            }
        });
    }
     
    function observe(data) {
        if (!data || typeof data !== 'object') {
            return;
        }
        Object.keys(data).forEach(function(key) {
            defineReactive(data, key, data[key]);
        });
    };
     
    var library = {
        book1: {
            name: ''
        },
        book2: ''
    };
    observe(library);
    library.book1.name = 'vue权威指南'; // 属性name已经被监听了,现在值为:“vue权威指南”
    library.book2 = '没有此书籍';  // 属性book2已经被监听了,现在值为:“没有此书籍”

    从代码上看,我们将订阅器Dep添加一个订阅者设计在getter里面,这是为了让Watcher初始化进行触发,因此需要判断是否要添加订阅者,至于具体设计方案,下文会详细说明的。在setter函数里面,如果数据变化,就会去通知所有订阅者,订阅者们就会去执行对应的更新的函数。到此为止,一个比较完整Observer已经实现了,接下来我们开始设计Watcher。

    2.实现Watcher

    订阅者Watcher在初始化的时候需要将自己添加进订阅器Dep中,那该如何添加呢?我们已经知道监听器Observer是在get函数执行了添加订阅者Wather的操作的,所以我们只要在订阅者Watcher初始化的时候触发对应的get函数去执行添加订阅者操作即可,那要如何触发get的函数,再简单不过了,只要获取对应的属性值就可以触发了,核心原因就是因为我们使用了Object.defineProperty( )进行数据监听。这里还有一个细节点需要处理,我们只要在订阅者Watcher初始化的时候才需要添加订阅者,所以需要做一个判断操作,因此可以在订阅器上做一下手脚:在Dep.target上缓存下订阅者,添加成功后再将其去掉就可以了。订阅者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;
        }
    };
    

    这时候,我们需要对监听器Observer也做个稍微调整,主要是对应Watcher类原型上的get函数。需要调整地方在于defineReactive函数:

    function defineReactive(data, key, val) {
        observe(val); // 递归遍历所有子属性
        var dep = new Dep(); 
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,
            get: function() {
                if (Dep.target) {.  // 判断是否需要添加订阅者
                    dep.addSub(Dep.target); // 在这里添加一个订阅者
                }
                return val;
            },
            set: function(newVal) {
                if (val === newVal) {
                    return;
                }
                val = newVal;
                console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
                dep.notify(); // 如果数据变化,通知所有订阅者
            }
        });
    }
    Dep.target = null;
    

    到此为止,简单版的Watcher设计完毕,这时候我们只要将Observer和Watcher关联起来,就可以实现一个简单的双向绑定数据了。因为这里没有还没有设计解析器Compile,所以对于模板数据我们都进行写死处理,假设模板上又一个节点,且id号为'name',并且双向绑定的绑定的变量也为'name',且是通过两个大双括号包起来(这里只是为了演示,暂时没什么用处),模板如下:

    <body>
        <h1 id="name">{{name}}</h1>
    </body>

    这时候我们需要将Observer和Watcher关联起来:

    function SelfVue (data, el, exp) {
        this.data = data;
        observe(data);
        el.innerHTML = this.data[exp];  // 初始化模板数据的值
        new Watcher(this, exp, function (value) {
            el.innerHTML = value;
        });
        return this;
    }

    然后在页面上new以下SelfVue类,就可以实现数据的双向绑定了:

    <body>
        <h1 id="name">{{name}}</h1>
    </body>
    <script src="js/observer.js"></script>
    <script src="js/watcher.js"></script>
    <script src="js/index.js"></script>
    <script type="text/javascript">
        var ele = document.querySelector('#name');
        var selfVue = new SelfVue({
            name: 'hello world'
        }, ele, 'name');
     
        window.setTimeout(function () {
            console.log('name值改变了');
            selfVue.data.name = 'canfoo';
        }, 2000);
     
    </script>

    这时候打开页面,可以看到页面刚开始显示了是'hello world',过了2s后就变成'canfoo'了。到这里,总算大功告成一半了,但是还有一个细节问题,我们在赋值的时候是这样的形式 '  selfVue.data.name = 'canfoo'  ' 而我们理想的形式是'  selfVue.name = 'canfoo'  '为了实现这样的形式,我们需要在new SelfVue的时候做一个代理处理,让访问selfVue的属性代理为访问selfVue.data的属性,实现原理还是使用Object.defineProperty( )对属性值再包一层:

    function SelfVue (data, el, exp) {
        var self = this;
        this.data = data;
     
        Object.keys(data).forEach(function(key) {
            self.proxyKeys(key);  // 绑定代理属性
        });
     
        observe(data);
        el.innerHTML = this.data[exp];  // 初始化模板数据的值
        new Watcher(this, exp, function (value) {
            el.innerHTML = value;
        });
        return this;
    }
     
    SelfVue.prototype = {
        proxyKeys: function (key) {
            var self = this;
            Object.defineProperty(this, key, {
                enumerable: false,
                configurable: true,
                get: function proxyGetter() {
                    return self.data[key];
                },
                set: function proxySetter(newVal) {
                    self.data[key] = newVal;
                }
            });
        }
    }

    这下我们就可以直接通过'  selfVue.name = 'canfoo'  '的形式来进行改变模板数据了。

    3.实现Compile

    虽然上面已经实现了一个双向数据绑定的例子,但是整个过程都没有去解析dom节点,而是直接固定某个节点进行替换数据的,所以接下来需要实现一个解析器Compile来做解析和绑定工作。解析器Compile实现步骤:

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

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

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

    function nodeToFragment (el) {
        var fragment = document.createDocumentFragment();
        var child = el.firstChild;
        while (child) {
            // 将Dom元素移入fragment中
            fragment.appendChild(child);
            child = el.firstChild
        }
        return fragment;
    }

    接下来需要遍历各个节点,对含有相关指定的节点进行特殊处理,这里咱们先处理最简单的情况,只对带有 '{{变量}}' 这种形式的指令进行处理,先简道难嘛,后面再考虑更多指令情况:

    function compileElement (el) {
        var childNodes = el.childNodes;
        var self = this;
        [].slice.call(childNodes).forEach(function(node) {
            var reg = /\{\{(.*)\}\}/;
            var text = node.textContent;
     
            if (self.isTextNode(node) && reg.test(text)) {  // 判断是否是符合这种形式{{}}的指令
                self.compileText(node, reg.exec(text)[1]);
            }
     
            if (node.childNodes && node.childNodes.length) {
                self.compileElement(node);  // 继续递归遍历子节点
            }
        });
    },
    function compileText (node, exp) {
        var self = this;
        var initText = this.vm[exp];
        updateText(node, initText);  // 将初始化的数据初始化到视图中
        new Watcher(this.vm, exp, function (value) {  // 生成订阅器并绑定更新函数
            self.updateText(node, value);
        });
    },
    function updateText (node, value) {
        node.textContent = typeof value == 'undefined' ? '' : value;
    }

    获取到最外层节点后,调用compileElement函数,对所有子节点进行判断,如果节点是文本节点且匹配{{}}这种形式指令的节点就开始进行编译处理,编译处理首先需要初始化视图数据,对应上面所说的步骤1,接下去需要生成一个并绑定更新函数的订阅器,对应上面所说的步骤2。这样就完成指令的解析、初始化、编译三个过程,一个解析器Compile也就可以正常的工作了。为了将解析器Compile与监听器Observer和订阅者Watcher关联起来,我们需要再修改一下类SelfVue函数:

    function SelfVue (options) {
        var self = this;
        this.vm = this;
        this.data = options;
     
        Object.keys(this.data).forEach(function(key) {
            self.proxyKeys(key);
        });
     
        observe(this.data);
        new Compile(options, this.vm);
        return this;
    }
    

    更改后,我们就不要像之前通过传入固定的元素值进行双向绑定了,可以随便命名各种变量进行双向绑定了:

    <body>
        <div id="app">
            <h2>{{title}}</h2>
            <h1>{{name}}</h1>
        </div>
    </body>
    <script src="js/observer.js"></script>
    <script src="js/watcher.js"></script>
    <script src="js/compile.js"></script>
    <script src="js/index.js"></script>
    <script type="text/javascript">
     
        var selfVue = new SelfVue({
            el: '#app',
            data: {
                title: 'hello world',
                name: ''
            }
        });
     
        window.setTimeout(function () {
            selfVue.title = '你好';
        }, 2000);
     
        window.setTimeout(function () {
            selfVue.name = 'canfoo';
        }, 2500);
     
    </script>

    如上代码,在页面上可观察到,刚开始titile和name分别被初始化为 'hello world' 和空,2s后title被替换成 '你好' 3s后name被替换成 'canfoo' 了。

    到这里,一个数据双向绑定功能已经基本完成了,接下去就是需要完善更多指令的解析编译,在哪里进行更多指令的处理呢?答案很明显,只要在上文说的compileElement函数加上对其他指令节点进行判断,然后遍历其所有属性,看是否有匹配的指令的属性,如果有的话,就对其进行解析编译。这里我们再添加一个v-model指令和事件指令的解析编译,对于这些节点我们使用函数compile进行解析处理:

    function compile (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);
            }
        });
    }

    上面的compile函数是挂载Compile原型上的,它首先遍历所有节点属性,然后再判断属性是否是指令属性,

    最后我们在稍微改造下类SelfVue,使它更像vue的用法:

    function SelfVue (options) {
        var self = this;
        this.data = options.data;
        this.methods = options.methods;
     
        Object.keys(this.data).forEach(function(key) {
            self.proxyKeys(key);
        });
     
        observe(this.data);
        new Compile(options.el, this);
        options.mounted.call(this); // 所有事情处理好后执行mounted函数
    }

     

     

    展开全文
  • 理解VUE双向数据绑定原理和实现

    万次阅读 多人点赞 2021-02-26 12:04:02
    1.vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变; 2.核心:关于VUE双向数据绑定,其核心是 Object....

    原文链接:  https://www.jianshu.com/p/e7ebb1500613 

    一、原理:

    1.vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;

    2.核心:关于VUE双向数据绑定,其核心是 Object.defineProperty()方法;

    3.介绍一下Object.defineProperty()方法
    (1)Object.defineProperty(obj, prop, descriptor) ,这个语法内有三个参数,分别为 obj (要定义其上属性的对象) prop (要定义或修改的属性) descriptor (具体的改变方法)
    (2)简单地说,就是用这个方法来定义一个值。当调用时我们使用了它里面的get方法,当我们给这个属性赋值时,又用到了它里面的set方法;

    set,get方法初步了解

    二、先简单的实现一个js的双向数据绑定来熟悉一下这个方法:

     

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    
    <body>
        <div id="app">
            <input type="text" id="a">
            <span id="b"></span>
        </div>
    </body>
    <script>
        var obj = {};  //定义一个空对象
    
        var val = 'zhao';  //赋予初始值
    
        Object.defineProperty(obj, 'val', {//定义要修改对象的属性
    
            get: function () {
    
                return val;
            },
    
            set: function (newVal) { 
    
                 val = newVal;//定义val等于修改后的内容
    
                document.getElementById('a').value = val;//让文本框的内容等于val
                
                document.getElementById('b').innerHTML = val;//让span的内容等于val
    
            }
    
        });
        document.addEventListener('keyup', function (e) {//当在文本框输入内容时让对象里你定义的val等于文本框的值
    
            obj.val = e.target.value;
    
        })
    
    </script>
    
    </html>
    

    这样我们就能实现js的双向数据绑定,也对这个方法有初步的了解
    这个例子实现的效果是:随着文本框输入文字的变化,span中会同步显示相同的文字内容;这样就实现了 model => view 以及 view => model 的双向绑定。
    通过添加事件监听keyup来触发set方法,而set再修改了访问器属性的同时,也修改了dom样式,改变了span标签内的文本。

    三、实现一个真正的双向绑定的原理

    1.实现效果
    先来看一下vue双向数据绑定是如何进行的,以便我们确定好思考方向

    image

     

    image

    2.任务拆分

    拆分任务可以让我们的思路更加清晰:
    (1)将vue中的data中的内容绑定到输入文本框和文本节点中
    (2)当文本框的内容改变时,vue实例中的data也同时发生改变
    (3)当data中的内容发生改变时,输入框及文本节点的内容也发生变化

    3.开始任务1——绑定内容
    我们先了解一下 DocuemntFragment(碎片化文档)这个概念,你可以把他认为一个dom节点收容器,当你创造了10个节点,当每个节点都插入到文档当中都会引发一次浏览器的回流,也就是说浏览器要回流10次,十分消耗资源。
    而使用碎片化文档,也就是说我把10个节点都先放入到一个容器当中,最后我再把容器直接插入到文档就可以了!浏览器只回流了1次。
    注意:还有一个很重要的特性是,如果使用appendChid方法将原dom树中的节点添加到DocumentFragment中时,会删除原来的节点。

    举个例子:
    可以看到,我的app中有两个子节点,一个元素节点,一个文本节点
    但是,当我通过DocumentFragment 劫持数据一下后

    image

    image

    image

    注意:我的碎片化文档是将子节点都劫持了过来,而我的id为app的div内已经没有内容了。
    同时要主要我while的判断条件。判断是否有子节点,因为我每次appendChild都把node中的第一个子节点劫持走了,node中就会少一个,直到没有的时候,child也就变成了undefined,也就终止了循环。

    来实现内容绑定
    我们要考虑两个问题,一个是如何绑定要input上,另一个是如何绑定要文本节点中。
    这样思路就来了,我们已经获取到了div的所以子节点了,就在DocumentFragment里面,然后对每一个节点进行处理,看是不是有跟vm实例中有关联的内容,如果有,修改这个节点的内容。然后重新添加入DocumentFragment中。

    首先,我们写一个处理每一个节点的函数,如果有input绑定v-model属性或者有{{ xxx }}的文本节点出现,就进行内容替换,替换为vm实例中的data中的内容

    image

    然后,在向碎片化文档中添加节点时,每个节点都处理一下。

    image

    创建Vue的实例化函数

    image

    效果图如下:

    image

    我们成功将内容都绑定到了输入框与文本节点上!

    4、实现任务2——【view => model
    对于此任务,我们从输入框考虑,输入框的问题,输入框如何改变data。我们通过事件监听器keyup,input等,来获取到最新的value,然后通过Object.defineProperty将获取的最新的value,赋值给实例vm的text,我们把vm实例中的data下的text通过Object.defineProperty设置为访问器属性,这样给vm.text赋值,就触发了set。set函数的作用一个是更新data中的text,另一个等到任务三再说。

    首先实现一个响应式监听属性的函数。一旦有赋新值就发生变化

    image

    然后,实现一个观察者,对于一个实例 每一个属性值都进行观察。

    image

    改写编译函数,注意由于改成了访问器属性,访问的方法也产生变化,同时添加了事件监听器,把实例的text值随时更新

    image

    实例函数中,观察data中的所有属性值,注意增添了observe

    image

    最终我们改变input中的内容能改变data中的数据,单页面却没有刷新

    image

    image

    4、实现任务3——【model => view】
    通过修改vm实例的属性 该改变输入框的内容 与 文本节点的内容。
    这里涉及到一个问题 需要我们注意,当我们修改输入框,改变了vm实例的属性,这是1对1的。
    但是,我们可能在页面中多处用到 data中的属性,这是1对多的。也就是说,改变1个model的值可以改变多个view中的值。
    这就需要我们引入一个新的知识点:

    订阅/发布者模式
    订阅发布模式(又称观察者模式)定义了一种一对多的关系,让多个观察者同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察者对象。

    发布者发出通知 => 主题对象收到通知并推送给订阅者 => 订阅者执行相应操作
    1
    举个例子:

    image

    之前提到的set函数的第二个作用 就是来提醒订阅者 进行noticy操作,告诉他们:“我的text变了!” 文本节点变成了订阅者,接到消息后,立马进行update操作

    回顾一下,每当 new 一个 Vue,主要做了两件事:第一个是监听数据:observe(data),第二个是编译 HTML:nodeToFragement(id)。
    在监听数据的过程中,我们会为 data 中的每一个属性生成一个主题对象 dep。

    在编译 HTML 的过程中,会为每个与数据绑定相关的节点生成一个订阅者 watcher,watcher 会将自己添加到相应属性的 dep 容器中。

    我们已经实现:修改输入框内容 => 在事件回调函数中修改属性值 => 触发属性的 set 方法。

    接下来我们要实现的是:发出通知 dep.notify() => 触发订阅者的 update 方法 => 更新视图。
    这里的关键逻辑是:如何将 watcher 添加到关联属性的 dep 中。

    注意: 我把直接赋值的操作改为了 添加一个 Watcher 订阅者

    image

    那么,Watcher又该做些什么呢?

    image

    首先,将自己赋给了一个全局变量 Dep.target;

    其次,执行了 update 方法,进而执行了 get 方法,get 的方法读取了 vm 的访问器属性,从而触发了访问器属性的 get 方法,get 方法中将该 watcher 添加到了对应访问器属性的 dep 中;

    再次,获取属性的值,然后更新视图。

    最后,将 Dep.target 设为空。因为它是全局变量,也是 watcher 与 dep 关联的唯一桥梁,任何时刻都必须保证 Dep.target 只有一个值。

    image

    image

    最终我们就实现了这个双向数据绑定功能,虽然很繁琐,但我相信,你多打几遍,一定会对你有所帮助,加油吧!!

    最后小编给大家附上源码:

     

    <!DOCTYPE html>
    <html>
    
        <head>
            <meta charset="UTF-8">
            <title></title>
        </head>
    
        <body>
    
            <div id="app">
    
                <input type="text" v-model="text" /> {{text}}
    
            </div>
    
        </body>
    
        <script type="text/javascript">
            //          编译函数
            function compile(node, vm) {
    
                var reg = /\{\{(.*)\}\}/; // 来匹配{{xxx}}中的xxx
    
                //如果是元素节点
                if(node.nodeType === 1) {
    
                    var attr = node.attributes;
    
                    //解析元素节点的所有属性
    
                    for(let i = 0; i < attr.length; i++) {
    
                        if(attr[i].nodeName == 'v-model') {
    
                            var name = attr[i].nodeValue //看看是与哪一个数据相关
    
                            node.addEventListener('input', function(e) { //将与其相关的数据改为最新值
                                vm[name] = e.target.value
                            })
    
                            node.value = vm.data[name]; //将data中的值赋予给该node
    
                            node.removeAttribute('v-model')
    
                        }
    
                    }
    
                }
    
                //如果是文本节点
    
                if(node.nodeType === 3) {
    
                    if(reg.test(node.nodeValue)) {
    
                        var name = RegExp.$1; //获取到匹配的字符串
    
                        name = name.trim();
    
                        //                  node.nodeValue = vm[name]; //将data中的值赋予给该node
    
                        new Watcher(vm, node, name) //绑定一个订阅者
                    }
    
                }
    
            }
    
            //          在向碎片化文档中添加节点时,每个节点都处理一下
    
            function nodeToFragment(node, vm) {
    
                var fragment = document.createDocumentFragment();
    
                var child;
    
                while(child = node.firstChild) {
    
                    compile(child, vm);
    
                    fragment.appendChild(child);
    
                }
    
                return fragment
    
            }
    
            //          Vue构造函数     
            //      观察data中的所有属性值,注意增添了observe
    
            function Vue(options) {
    
                this.data = options.data;
    
                observe(this.data, this)
    
                var id = options.el;
    
                var dom = nodeToFragment(document.getElementById(id), this)
    
                //处理完所有节点后,重新把内容添加回去
                document.getElementById(id).appendChild(dom)
    
            }
    
            //      实现一个响应式监听属性的函数。一旦有赋新值就发生变化 
    
            function defineReactive(obj, key, val) {
    
                var dep = new Dep();
    
                Object.defineProperty(obj, key, {
    
                    get: function() {
    
                        if(Dep.target) {
    
                            dep.addSub(Dep.target)
    
                        }
    
                        return val
    
                    },
                    set: function(newVal) {
    
                        if(newVal === val) {
    
                            return
    
                        }
    
                        val = newVal;
    
                        console.log('新值' + val);
    
                        //一旦更新立马通知
    
                        dep.notify();
    
                    }
    
                })
    
            }
    
            //      实现一个观察者,对于一个实例 每一个属性值都进行观察。
    
            function observe(obj, vm) {
    
                for(let key of Object.keys(obj)) {
    
                    defineReactive(vm, key, obj[key]);
    
                }
    
            }
    
            //      Watcher监听者
    
            function Watcher(vm, node, name) {
    
                Dep.target = this;
    
                this.vm = vm;
                this.node = node;
                this.name = name;
    
                this.update();
    
                Dep.target = null;
    
            }
    
            Watcher.prototype = {
    
                update() {
                    this.get();
                    this.node.nodeValue = this.value //更改节点内容的关键
                },
                get() {
                    this.value = this.vm[this.name] //触发相应的get
                }
    
            }
    
            //      dep构造函数
    
            function Dep() {
                this.subs = []
            }
            Dep.prototype = {
                addSub(sub) {
                    this.subs.push(sub)
                },
                notify() {
                    this.subs.forEach(function(sub) {
                        sub.update();
                    })
                }
            }
    
            var vm = new Vue({
    
                el: 'app',
    
                data: {
                    text: '赵刚'
                }
    
            })
        </script>
    
    </html>
    展开全文
  • 到这里我们的整个的模板编译也已经完成,不过这里我们并没有实现过多的指令,我们只是简单的实现了 v-model 指令,本意是通过这篇文章让大家熟悉与认识 Vue双向绑定原理,并不是去创造一个新的 MVVM 实例。...

    在线使用-线上测试-源码

    //代码:
    <div id="app">
        <input v-model="name" type="text">
        <h1>{{ name }}</h1>
    </div>
    <script src="./js/observer.js"></script>
    <script src="./js/watcher.js"></script>
    <script src="./js/compile.js"></script>
    <script src="./js/index.js"></script>
    <script>
    const vm = new Mvue({
        el: "#app",
    	data: {
    		name: "我是摩登"
    	}
    });
    </script>

    数据绑定

    在正式开始之前我们先来说说数据绑定的事情,数据绑定我的理解就是让数据M(model)展示到 视图V(view)上。我们常见的架构模式有 MVC、MVP、MVVM模式,目前前端框架基本上都是采用 MVVM 模式实现双向绑定,Vue 自然也不例外。但是各个框架实现双向绑定的方法略有所不同,目前大概有三种实现方式。

    • 发布订阅模式
    • Angular 的脏查机制
    • 数据劫持

    而 Vue2.0 则采用的是数据劫持与发布订阅相结合的方式实现双向绑定,数据劫持主要通过 Object.defineProperty 来实现。

    Object.defineProperty

    这篇文章我们不详细讨论 Object.defineProperty 的用法,我们主要看看它的存储属性 get 与 set。我们来看看通过它设置的对象属性之后有何变化。

    var people = {
        name: "Modeng",
        age: 18
    }
    people.age; //18
    people.age = 20;

    上述代码就是普通的获取/设置对象的属性,看不到什么奇怪的变化。

    var modeng = {}
    var age;
    Object.defineProperty(modeng, 'age', {
      get: function () {
        console.log("获取年龄");
        return age;
      },
      set: function (newVal) {
        console.log("设置年龄");
        age = newVal;
      }
    });
    modeng.age = 18;
    console.log(modeng.age);

    你会发现通过上述操作之后,我们访问 age 属性时会自动执行 get 函数,设置 age 属性时,会自动执行 set 函数,这就给我们的双向绑定提供了非常大的方便。

    剖析

    我们知道 MVVM 模式在于数据与视图的保持同步,意思是说数据改变时会自动更新视图,视图发生变化时会更新数据。

    所以我们需要做的就是如何检测到数据的变化然后通知我们去更新视图,如何检测到视图的变化然后去更新数据。检测视图这个比较简单,无非就是我们利用事件的监听即可。

    那么如何才能知道数据属性发生变化呢?这个就是利用我们上面说到的 Object.defineProperty 当我们的属性发生变化时,它会自动触发 set 函数从而能够通知我们去更新视图。

    实现

    通过上面的描述与分析我们知道 Vue 是通过数据劫持结合发布订阅模式来实现双向绑定的。我们也知道数据劫持是通过 Object.defineProperty 方法,当我们知道这些之后,我们就需要一个监听器 Observer 来监听属性的变化。得知属性发生变化之后我们需要一个 Watcher 订阅者来更新视图,我们还需要一个 compile 指令解析器,用于解析我们的节点元素的指令与初始化视图。所以我们需要如下:

    • Observer 监听器:用来监听属性的变化通知订阅者
    • Watcher 订阅者:收到属性的变化,然后更新视图
    • Compile 解析器:解析指令,初始化模版,绑定订阅者

    监听器 Observer

    监听器的作用就是去监听数据的每一个属性,我们上面也说了使用 Object.defineProperty 方法,当我们监听到属性发生变化之后我们需要通知 Watcher 订阅者执行更新函数去更新视图,在这个过程中我们可能会有很多个订阅者 Watcher 所以我们要创建一个容器 Dep 去做一个统一的管理。

    function defineReactive(data, key, value) {
      //递归调用,监听所有属性
      observer(value);
      var dep = new Dep();
      Object.defineProperty(data, key, {
        get: function () {
          if (Dep.target) {
            dep.addSub(Dep.target);
          }
          return value;
        },
        set: function (newVal) {
          if (value !== newVal) {
            value = newVal;
            dep.notify(); //通知订阅器
          }
        }
      });
    }
    
    function observer(data) {
      if (!data || typeof data !== "object") {
        return;
      }
      Object.keys(data).forEach(key => {
        defineReactive(data, key, data[key]);
      });
    }
    
    function Dep() {
      this.subs = [];
    }
    Dep.prototype.addSub = function (sub) {
      this.subs.push(sub);
    }
    Dep.prototype.notify = function () {
      console.log('属性变化通知 Watcher 执行更新视图函数');
      this.subs.forEach(sub => {
        sub.update();
      })
    }
    Dep.target = null;

    以上我们就创建了一个监听器 Observer,我们现在可以尝试一下给一个对象添加监听然后改变属性会有何变化。

    var modeng = {
      age: 18
    }
    observer(modeng);
    modeng.age = 20;

    我们可以看到浏览器控制台打印出 “属性变化通知 Watcher 执行更新视图函数” 说明我们实现的监听器没毛病,既然监听器有了,我们就可以通知属性变化了,那肯定是需要 Watcher 的时候了。

    订阅者 Watcher

    Watcher 主要是接受属性变化的通知,然后去执行更新函数去更新视图,所以我们做的主要是有两步:

    1. 把 Watcher 添加到 Dep 容器中,这里我们用到了 监听器的 get 函数
    2. 接收到通知,执行更新函数。
    function Watcher(vm, prop, callback) {
      this.vm = vm;
      this.prop = prop;
      this.callback = callback;
      this.value = this.get();
    }
    Watcher.prototype = {
      update: function () {
        const value = this.vm.$data[this.prop];
        const oldVal = this.value;
        if (value !== oldVal) {
          this.value = value;
          this.callback(value);
        }
      },
      get: function () {
        Dep.target = this; //储存订阅器
        const value = this.vm.$data[this.prop]; //因为属性被监听,这一步会执行监听器里的 get方法
        Dep.target = null;
        return value;
      }
    }

    这一步我们把 Watcher 也给弄了出来,到这一步我们已经实现了一个简单的双向绑定了,我们可以尝试把两者结合起来看下效果。

    function Mvue(options, prop) {
    	this.$options = options;
    	this.$data = options.data;
    	this.$prop = prop;
    	this.$el = document.querySelector(options.el);
    	this.init();
    }
    Mvue.prototype.init = function () {
        observer(this.$data);
        this.$el.textContent = this.$data[this.$prop];
        new Watcher(this, this.$prop, value => {
    	    this.$el.textContent = value;
    	});
    }

    这里我们尝试利用一个实例来把数据与需要监听的属性传递进来,通过监听器监听数据,然后添加属性订阅,绑定更新函数。

    <div id="app">{{ name }}</div>
    const vm = new Mvue({
    	el: "#app",
    	data: {
    		name: "我是摩登"
    	}
    }, "name");

    我们可以看到数据已经正常的显示在页面上,那么我们在通过控制台去修改数据,发生变化后视图也会跟着修改。

    到这一步我们我们基本上已经实现了一个简单的双向绑定,但是不难发现我们这里的属性都是写死的,也没有指令模板的解析,所以下一步我们来实现一个模板解析器。

    Compile 解析器

    Compile 的主要作用一个是用来解析指令初始化模板,一个是用来添加添加订阅者,绑定更新函数。

    因为在解析 DOM 节点的过程中我们会频繁的操作 DOM, 所以我们利用文档片段(DocumentFragment)来帮助我们去解析 DOM 优化性能。

    function Compile(vm) {
      this.vm = vm;
      this.el = vm.$el;
      this.fragment = null;
      this.init();
    }
    Compile.prototype = {
      init: function () {
        this.fragment = this.nodeFragment(this.el);
      },
      nodeFragment: function (el) {
        const fragment = document.createDocumentFragment();
        let child = el.firstChild;
        //将子节点,全部移动文档片段里
        while (child) {
          fragment.appendChild(child);
          child = el.firstChild;
        }
        return fragment;
      }
    }

    然后我们就需要对整个节点和指令进行处理编译,根据不同的节点去调用不同的渲染函数,绑定更新函数,编译完成之后,再把 DOM 片段添加到页面中。

    Compile.prototype = {
      compileNode: function (fragment) {
        let childNodes = fragment.childNodes;
        [...childNodes].forEach(node => {
          let reg = /\{\{(.*)\}\}/;
          let text = node.textContent;
          if (this.isElementNode(node)) {
            this.compile(node); //渲染指令模板
          } else if (this.isTextNode(node) && reg.test(text)) {
            let prop = RegExp.$1;
            this.compileText(node, prop); //渲染 {{ }}模板
          }
    
          //递归编译子节点
          if (node.childNodes && node.childNodes.length) {
            this.compileNode(node);
          }
        });
      },
      compile: function (node) {
        let nodeAttrs = node.attributes;
        [...nodeAttrs].forEach(attr => {
          let name = attr.name;
          if (this.isDirective(name)) {
            let value = attr.value;
            if (name === "v-model") {
              this.compileModel(node, value);
            }
            node.removeAttribute(name);
          }
        });
      },
      //略
    }

    因为代码比较长如果全部贴出来会影响阅读,我们主要是讲整个过程实现的思路,文章结束我会把源码发出来,有兴趣的可以去查看全部代码

    到这里我们的整个的模板编译也已经完成,不过这里我们并没有实现过多的指令,我们只是简单的实现了 v-model 指令,本意是通过这篇文章让大家熟悉与认识 Vue 的双向绑定原理,并不是去创造一个新的 MVVM 实例。所以并没有考虑很多细节与设计。

    现在我们实现了 Observer、Watcher、Compile,接下来就是把三者给组织起来,成为一个完整的 MVVM。

    创建 Mvue

    这里我们创建一个 Mvue 的类(构造函数)用来承载 Observer、Watcher、Compile 三者。

    function Mvue(options) {
      this.$options = options;
      this.$data = options.data;
      this.$el = document.querySelector(options.el);
      this.init();
    }
    Mvue.prototype.init = function () {
      observer(this.$data);
      new Compile(this);
    }

    然后我们就去测试一下结果,看看我们实现的 Mvue 是不是真的可以运行。

    <div id="app">
        <h1>{{ name }}</h1>
    </div>
    <script src="./js/observer.js"></script>
    <script src="./js/watcher.js"></script>
    <script src="./js/compile.js"></script>
    <script src="./js/index.js"></script>
    <script>
    	const vm = new Mvue({
    		el: "#app",
    		data: {
    			name: "完全没问题,看起来是不是很酷!"
    		}
    	});
    </script>

    我们尝试去修改数据,也完全没问题,但是有个问题就是我们修改数据时时通过 vm.$data.name 去修改数据,而不是想 Vue 中直接用 vm.name 就可以去修改,那这个是怎么做到的呢?其实很简单,Vue 做了一步数据代理操作。

    数据代理

    我们来改造下 Mvue 添加数据代理功能,我们也是利用 Object.defineProperty 方法进行一步中间的转换操作,间接的去访问。

    function Mvue(options) {
      this.$options = options;
      this.$data = options.data;
      this.$el = document.querySelector(options.el);
      //数据代理
      Object.keys(this.$data).forEach(key => {
        this.proxyData(key);
      });
    
      this.init();
    }
    Mvue.prototype.init = function () {
      observer(this.$data);
      new Compile(this);
    }
    Mvue.prototype.proxyData = function (key) {
      Object.defineProperty(this, key, {
        get: function () {
          return this.$data[key]
        },
        set: function (value) {
          this.$data[key] = value;
        }
      });
    }

    到这里我们就可以像 Vue 一样去修改我们的属性了,非常完美

     

    展开全文
  • 原理Vue2.x): Vue.js它是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter;在数据变动时发布消息给订阅者,触发相应的监听回调。 我们知道,实现MVVM...

    原理(Vue2.x):

    Vue.js它是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter;在数据变动时发布消息给订阅者,触发相应的监听回调。

     我们知道,实现MVVM主要包括两个方面:1.视图变化更新数据;2.数据变化更新视图。

      

    • 视图变化更新数据

    对于视图变化更新数据,其实可以通过事件监听即可,比如input标签监听‘input’事件就可以实现。

    • 数据变化更新视图

    对于数据变化更新视图,其实我们上面就讲到了,就是通过Object.defineProperty()来实现,设置一个set函数,当数据发生改变就会触发这个函数,所以我们只需要将一些需要更新的方法放在这个set函数里面就可以实现数据变化更新视图了。

    我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:

    实现步骤:

    (1)第一个就是首先我们需要对数据进行劫持监听,所以我们需要先设置实现一个监听器Observer,用来劫持并监听所有的属性,一旦属性发生变化,就通知订阅者。

    (2)第二个就是我们需要设置实现一个订阅者Watcher,用来接收属性变化的一个通知并执行相应的函数,从而更新视图。

    (3)第三个就是最后设置实现一个解析器Compile,用来可以扫描和解析每个节点的相关指令,然后初始化相应的模板数据以及订阅者。

    代码示例:

    Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

    Object.defineProperty()方法接收3个参数:

    第一个参数:需要定义属性的当前对象;

    第二个:当前需要定义的属性名;

    第三个:属性描述符;

    vue3双向数据绑定使用的是代理,与Vue2相比的好处在于不消耗性能,因为Vue2使用Object.defineProperty()方法嵌套深层的话需要使用递归比较耗性能,而Vue3使用代理则不用。

     

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <title>vuejs-iview-admin</title>
      </head>
      <body>
      <input type="text">
      <span id="text"></span>
      <script>
        var text = document.getElementById('text');
        var data = {};
        var value = '';//全局变量用于set设置和get取值
        Object.defineProperty(data, "name", {//属性描述
          //监听数据发生变化设置值,setter
          set(val) {//监听属性name的变化,val其实就是属性name
            text.innerHTML = val;
            value = val;
          },
          //获取值getter
          get() {
            return value;
          }
        })
        //监听input事件
        window.addEventListener('input', function (e) {
          data.name = e.target.value;
          console.log(data.name)
        });
      </script>
        <div id="app"></div>
        <!-- built files will be auto injected -->
      </body>
    </html>
    
    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==
    展开全文
  • 什么是数据双向绑定? vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化,数据也会跟着同步变化。这也算是vue的精髓之处了。单项数据绑定是使用状态管理工具(如redux...
  • vue双向数据绑定原理

    2021-04-29 19:09:20
    vue双向数据绑定原理 一、原理 vue双向数据绑定是通过数据劫持结合发布订阅者模式的方式来实现的,也就是说数据和视图同步,数据发生变化,试图跟着变化,数据也随之发生改变 核心:关于vue数据绑定,其核心是 ...
  • vue.js采用的是数据劫持结合发布和-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。那么Object我们知道是一个对象,...
  • 原文地址:Vue数据双向绑定原理(vue2向vue3的过渡)_Vam的金豆之路-CSDN博客众所周知,Vue的两大重要概念:数据驱动组件系统接下来我们浅析数据双向绑定的原理一、vue21、认识definePropertyvue2中的双向绑定是基于...
  • vue-双向绑定原理

    2021-03-28 20:55:16
    1.双向绑定 [1]定义 在视图上的数据发生了变化,data中的数据也要对应改变;...在vue2.x中数据双向绑定的核心是使用 Object.defineProperty(对象,key,{get(),set()})方法来给对象的属性添加get/s
  • vue双向绑定原理 vue.js采用数据劫持结合发布者-订阅者模式的方式实现双向绑定,通过Object.defineProperty()来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。 其中最核心的...
  • 双向数据绑定:保留了 angular 的特点,在数据操作方面更为简单; 组件化:保留了 react 的优点,实现了 html 的封装和重用,在构建单页面应用方面有着独特的优势; 视图,数据,结构分离:使数据的更改更为简单,不...
  • vue双向数据绑定原理

    万次阅读 2017-09-04 19:30:02
    有关双向数据绑定的原理 最近两次面试的时候,被问到了vue中双向数据绑定的原理,因为初学不精,只是使用而没有深入研究,所以... 文章链接:vue双向绑定原理及实现 Mozilla 开发者服务:Object.defineProperty...
  • vue3双向绑定实现原理

    2021-07-15 21:48:59
    一、vue3为什么要用proxy实现双向绑定? 1.object.defineProperty的缺点: 因为es5的object.defineProperty无法监听对象属性的删除和添加 不能监听数组的变化,除了push/pop/shift/unshift/splice/spObject.definert...
  • vue双向绑定原理

    2021-06-10 16:34:20
    当前台显示的view发生变化了,它会实时反应到viewModel上,如果有需要,viewModel 会通过ajax等方法将改变的数据 传递给后台model同时从后台model获取过来的数据,通过vm将值响应到前台UI上双向绑定原理v...
  • Vue数据双向绑定原理

    2021-04-15 10:22:39
    vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript...
  • vue2与vue3的双向绑定写法不同 vue使用Object.defineProperty来劫持对象属性 而vue3使用Proxy代理来实现双向绑定vue2: <div class="data"></div> <label>vue2:</label> <input ...
  • 数据响应式: 数据模型是JavaScript 的普通对象,当我们修改数据时,视图会进行更新,...直接看案例 vue2.0 双向绑定原理 <!-- body --> <div id="app">app</div> <script> let data={ msg
  • vue双向绑定原理什么?(2020/01/12) 前台UI数据改变实时反应在ViewModel上,后会通过ajax传到后台model上 后台改变的也会通过ViewModel响应到前台UI上 当data 有变化的时候它通过Object.defineProperty...
  • vue源码之数据双向绑定原理一.双向绑定的原理介绍二.代码简单实现 这里什么说是vue2.0的双向绑定原理是因为和vue3.0的实现方式是有区别的。 一.双向绑定的原理介绍 vue的双向绑定是数据和视图的同步变化,即当数据...
  • Vue数据双向绑定原理是通过数据劫持结合“发布者-订阅者”模式的方式来实现的,首先是对数据进行监听,然后当监听的属性发生变化时则告诉订阅者是否要更新,若更新就会执行对应的更新函数从而更新视图。Vue数据双向...
  • 目录原理介绍DEMO实现双向绑定第一部分是Observer:第二部分是Watcher:第三部分是Dep:Vue中的双向绑定Vue中的Observer:Vue中的Dep:Vue中的Watcher:总结一下 原理介绍 Vue.js是通过数据劫持以及结合发布者-订阅...
  • [vue] 什么是双向绑定原理是什么?

    千次阅读 多人点赞 2021-02-28 18:51:37
    [vue] 什么是双向绑定原理是什么? 双向数据绑定个人理解就是存在data→view,view→data两条数据流的模式。其实可以简单的理解为change和bind的结合。目前双向数据绑定都是基于Object.defineProperty()重新定义get...
  • vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,那么vue是如果进行数据劫持的,我们可以先来看一下通过控制台输出一个定义在vue初始化数据上的对象是个什么东西。 var vm = new Vue({ data:...
  • Vue的响应式原理,也可以叫双向绑定原理,MVVM模式原理。 一、MVVM 理解 MVVM分为Model、View、ViewModel三者。 Model 代表数据模型,数据和业务逻辑都在Model层中定义; View 代表UI视图,负责数据的展示; ...
  • 双向数据绑定机制: 官方:vue是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调。 第一步: 需要...
  • vue 数据双向绑定原理

    千次阅读 2021-04-14 15:06:44
    一、首先是2.0的双向绑定原理,基于Object.defineProperty实现 et obj = { name:'' } let virtualDom = JSON.parse(JSON.stringify(obj)) Object.defineProperty(obj,"name",{ get(){ return virtualDom.name ...
  • vue数据双向绑定

    2021-04-27 10:35:12
    现在世面上好用的前端框架琳琅满目,其核心内容基本都是:原型链、作用域链、异步非阻塞、闭包 等(个人感觉) ...前面啰嗦结束(其实感觉前面的才是最主要的),下面讲一下vue双向数据绑定的实现, 实.

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,681
精华内容 6,272
关键字:

vue数据双向绑定的原理是什么

vue 订阅