精华内容
下载资源
问答
  • 本篇文章主要介绍了详解Vue双向数据绑定原理解析 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 本文实例讲述了Vue双向绑定原理与用法。分享给大家供大家参考,具体如下: Vue 中需要输入什么内容的时候,自然会想到使用 <input v-model=xxx /> 的方式来实现双向绑定。下面是一个最简单的示例 <h2>...
  • vue双向绑定原理

    2021-03-19 09:03:19
    原理 view的变化能实时让model发生变化,而model的变化也能实时更新到view。 vue采用数据劫持&发布-订阅模式的方式,通过es5提供...要实现vue中的双向数据绑定,大致可以划分三个模块:Observer、Compile、W.
    1. 原理
      在这里插入图片描述
      view的变化能实时让model发生变化,而model的变化也能实时更新到view。

    vue采用数据劫持&发布-订阅模式的方式,通过es5提供Object.defineProperty()方法来劫持(监控)各属性的getter、setter,并在数据(对象)发生变动时通知订阅者,触发相应的监听回调。并且,由于是在不同的数据上触发同步,可以精确的将变更发送给绑定的视图,而不是对所有的数据都执行一次检测。要实现vue中的双向数据绑定,大致可以划分三个模块:Observer、Compile、Water
    在这里插入图片描述
    Observe 数据监听器,负责对数据对象的所有属性进行监听(数据劫持),监听到数据发生变化后通知订阅者
    Compiler 指令解析器,扫描模板,并对指令进行解析,然后绑定指定事件
    Watcher 订阅者,关联Observer和Compile,能够订阅并收到属性变动的通知,执行指令绑定的相应操作,更新视图。Update()是它自身的一个方法,用于执行Compile中绑定的回调,更新视图。

    模板渲染解析时watcher会对应绑定指令(一对一)

    此时会通过调用订阅者watcher初始化(watcher中get()方法)去触发对应属性在发布者observer里(object.defineProperty)的getter, observer会判断是不是通过watcher初始化调用的(Dep.target实例化之后会清空),中有是才会通过dep类依赖收集。

    observe通过depend通知Dep类收集(addDep方法,在watcher类中,会传入当前Dep实例调用自身)当前该订阅者(watcher)中的触发更新的方法,同时第一次初始化watcher.update()初始化视图。此后每次的数据更新都会通过observe中的setter去触发dep类中的回调update执行收集依赖的所有方法更新订阅者中的状态同时更新视图。

    observe在处理对象和数组的时候,如果是数组,并且调用的方法会改变数组长度,则会重新增加索引之后更新数组,进行重新监听。(因为调用数组原生API可能多次触发getter setter 且索引不会变),如果是对象则通过对象的getter获取值和setter更新值。

    1. 版本比较
      vue是基于依赖收集的双向绑定;
      3.0之前的版本使用 Ojbect.defineProperty, 3.0新版本使用Proxy
      1. 基于数据劫持/依赖收集的双向绑定的优点
        a.不需要显示的调用,vue利用数据劫持+发布订阅,可以直接通知变化并且驱动视图
        b.直接得到精确的变化数据,劫持了属性setter,当属性值改变我们可以精确的获取变化 的内容newVal,不需要额外的diff操作
        2)object.defineProperty的缺点
        a.不能监听数组;因为数组没有getter 和 setter ,因为数组长度不确定,如果太长性能负担太大。
        b.只能监听属性,而不是整个对象;需要遍历属性;
        c.只能监听属性变化,不能监听属性的删减
        3)Proxy好处
        a.可以监听数组;
        b.监听整个对象不是属性
        c.13种拦截方法,强大很多;
        d.返回新对象而不是直接修改原对象,更符合immutable
        4)proxy缺点
        a兼容性不好,且无法用polyfill磨平。
    展开全文
  • 主要为大家详细介绍了Vue 3.0双向绑定原理的实现方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Vue双向绑定原理简单总结Vue双向绑定原理 Vue双向绑定原理 总结 vue.js是采用数据劫持结合发布者-订阅模式的方式, 通过Object.defineProperty()来劫持各个属性的setter,getter, 在数据创建时,发布消息给订阅者...

    Vue双向绑定原理简单总结

    Vue双向绑定原理

    总结

    • vue.js是采用数据劫持结合发布者-订阅模式的方式,
    • 通过Object.defineProperty()来劫持各个属性的setter,getter,
    • 在数据创建时,发布消息给订阅者,触发相应的监听回调
    展开全文
  • 每当被问到Vue数据双向绑定原理的时候,大家可能都会脱口而出:Vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,即vue内部通过Object.defineProperty方法属性拦截的方式,把data对象里每个数据...

    1. 前言

    每当被问到Vue数据双向绑定原理的时候,大家可能都会脱口而出:Vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,即vue内部通过Object.defineProperty方法属性拦截的方式,把data对象里每个数据的读写转化成getter/setter,当数据变化时通知视图(view)更新。虽然一句话把大概原理概括了,但是其内部的实现方式还是值得深究的,本文就以通俗易懂的方式剖析Vue内部双向绑定原理的实现过程。

    2. 思路分析

    所谓MVVM数据双向绑定,即主要是:数据变化更新视图,视图变化更新数据。如下图:

    在这里插入图片描述
    也就是说:

    输入框内容变化时,data 中的数据同步变化。即 view => model 的变化。

    data 中的数据变化时,文本节点的内容同步变化。即 model => view 的变化。

    要实现这两个过程,关键点在于数据变化如何更新视图,因为视图变化更新数据我们可以通过事件监听的方式来实现。所以我们着重讨论数据变化如何更新视图。

    数据变化更新视图的关键点则在于我们如何知道数据发生了变化,只要知道数据在什么时候变了,那么问题就变得迎刃而解,我们只需在数据变化的时候去通知视图更新即可。

    3. 使数据对象变得“可观测”

    数据的每次读和写能够被我们看的见,即我们能够知道数据什么时候被读取了或数据什么时候被改写了,我们将其称为数据变的‘可观测’。

    要将数据变的‘可观测’,我们就要借助前言中提到的Object.defineProperty方法了,关于该方法,MDN上是这么介绍的:

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

    在本文中,我们就使用这个方法使数据变得“可观测”。

    首先,我们定义一个数据对象car:

    let car = {
            'brand':'BMW',
            'price':3000
        }
    

    我们定义了这个car的品牌brand是BMW,价格price是3000。现在我们可以通过car.brand和car.price直接读写这个car对应的属性值。但是,当这个car的属性被读取或修改时,我们并不知情。那么应该如何做才能够让car主动告诉我们,它的属性被修改了呢?

    接下来,我们使用Object.defineProperty()改写上面的例子:

        let car = {}
        let val = 3000
        Object.defineProperty(car, 'price', {
            get(){
                console.log('price属性被读取了')
                return val
            },
            set(newVal){
                console.log('price属性被修改了')
                val = newVal
            }
        })
    

    通过Object.defineProperty()方法给car定义了一个price属性,并把这个属性的读和写分别使用get()和set()进行拦截,每当该属性进行读或写操作的时候就会出发get()和set()。如下图:
    在这里插入图片描述

    可以看到,car已经可以主动告诉我们它的属性的读写情况了,这也意味着,这个car的数据对象已经是“可观测”的了。

    为了把car的所有属性都变得可观测,我们可以编写如下两个函数:

    /**
         * 把一个对象的每一项都转化成可观测对象
         * @param { Object } obj 对象
         */
        function observable (obj) {
            if (!obj || typeof obj !== 'object') {
                return;
            }
            let keys = Object.keys(obj);
            keys.forEach((key) =>{
                defineReactive(obj,key,obj[key])
            })
            return obj;
        }
        /**
         * 使一个对象转化成可观测对象
         * @param { Object } obj 对象
         * @param { String } key 对象的key
         * @param { Any } val 对象的某个key的值
         */
        function defineReactive (obj,key,val) {
            Object.defineProperty(obj, key, {
                get(){
                    console.log(`${key}属性被读取了`);
                    return val;
                },
                set(newVal){
                    console.log(`${key}属性被修改了`);
                    val = newVal;
                }
            })
        }
    

    现在,我们就可以这样定义car:

    let car = observable({
            'brand':'BMW',
            'price':3000
        })
    

    car的两个属性都变得可观测了。

    4. 依赖收集

    完成了数据的’可观测’,即我们知道了数据在什么时候被读或写了,那么,我们就可以在数据被读或写的时候通知那些依赖该数据的视图更新了,为了方便,我们需要先将所有依赖收集起来,一旦数据发生变化,就统一通知更新。其实,这就是典型的“发布订阅者”模式,数据变化为“发布者”,依赖对象为“订阅者”。

    现在,我们需要创建一个依赖收集容器,也就是消息订阅器Dep,用来容纳所有的“订阅者”。订阅器Dep主要负责收集订阅者,然后当数据变化的时候后执行对应订阅者的更新函数。

    创建消息订阅器Dep:

        class Dep {
            constructor(){
                this.subs = []
            },
            //增加订阅者
            addSub(sub){
                this.subs.push(sub);
            },
            //判断是否增加订阅者
            depend () {
                if (Dep.target) {
                    this.addSub(Dep.target)
                }
            },
    
            //通知订阅者更新
            notify(){
                this.subs.forEach((sub) =>{
                    sub.update()
                })
            }
        }
    Dep.target = null;
    

    有了订阅器,再将defineReactive函数进行改造一下,向其植入订阅器:

    function defineReactive (obj,key,val) {
            let dep = new Dep();
            Object.defineProperty(obj, key, {
                get(){
                    dep.depend();
                    console.log(`${key}属性被读取了`);
                    return val;
                },
                set(newVal){
                    val = newVal;
                    console.log(`${key}属性被修改了`);
                    dep.notify()                    //数据变化通知所有订阅者
                }
            })
        }
    

    从代码上看,我们设计了一个订阅器Dep类,该类里面定义了一些属性和方法,这里需要特别注意的是它有一个静态属性 target,这是一个全局唯一 的Watcher,这是一个非常巧妙的设计,因为在同一时间只能有一个全局的 Watcher 被计算,另外它的自身属性 subs 也是 Watcher 的数组。

    我们将订阅器Dep添加订阅者的操作设计在getter里面,这是为了让Watcher初始化时进行触发,因此需要判断是否要添加订阅者。在setter函数里面,如果数据变化,就会去通知所有订阅者,订阅者们就会去执行对应的更新的函数。

    到此,订阅器Dep设计完毕,接下来,我们设计订阅者Watcher.

    5. 订阅者Watcher

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

        class Watcher {
            constructor(vm,exp,cb){
                this.vm = vm;
                this.exp = exp;
                this.cb = cb;
                this.value = this.get();  // 将自己添加到订阅器的操作
            },
    
            update(){
                let value = this.vm.data[this.exp];
                let oldVal = this.value;
                if (value !== oldVal) {
                    this.value = value;
                    this.cb.call(this.vm, value, oldVal);
                },
            get(){
                Dep.target = this;  // 缓存自己
                let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
                Dep.target = null;  // 释放自己
                return value;
            }
        }
    

    过程分析:

    订阅者Watcher 是一个 类,在它的构造函数中,定义了一些属性:

    vm:一个Vue的实例对象;

    exp:是node节点的v-model或v-on:click等指令的属性值。如v-model=“name”,exp就是name;

    cb:是Watcher绑定的更新函数;

    当我们去实例化一个渲染 watcher 的时候,首先进入 watcher 的构造函数逻辑,就会执行它的 this.get() 方法,进入 get 函数,首先会执行:

    Dep.target = this; // 缓存自己
    实际上就是把 Dep.target 赋值为当前的渲染 watcher ,接着又执行了:

    let value = this.vm.data[this.exp] // 强制执行监听器里的get函数
    在这个过程中会对 vm 上的数据访问,其实就是为了触发数据对象的getter。

    每个对象值的 getter都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 方法,也就会执行this.addSub(Dep.target),即把当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目的是为后续数据变化时候能通知到哪些 subs 做准备。

    这样实际上已经完成了一个依赖收集的过程。那么到这里就结束了吗?其实并没有,完成依赖收集后,还需要把 Dep.target 恢复成上一个状态,即:

    Dep.target = null; // 释放自己
    因为当前vm的数据依赖收集已经完成,那么对应的渲染Dep.target 也需要改变。

    而update()函数是用来当数据发生变化时调用Watcher自身的更新函数进行更新的操作。先通过let value = this.vm.data[this.exp];获取到最新的数据,然后将其与之前get()获得的旧数据进行比较,如果不一样,则调用更新函数cb进行更新。

    至此,简单的订阅者Watcher设计完毕。

    6. 测试

    完成以上工作后,我们就可以来真正的测试了。

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
    <body>
        <h1 id="name"></h1>
        <input type="text">
        <input type="button" value="改变data内容" onclick="changeInput()">
        
    <script src="observer.js"></script>
    <script src="watcher.js"></script>
    <script>
        function myVue (data, el, exp) {
            this.data = data;
            observable(data);                      //将数据变的可观测
            el.innerHTML = this.data[exp];           // 初始化模板数据的值
            new Watcher(this, exp, function (value) {
                el.innerHTML = value;
            });
            return this;
        }
    
        var ele = document.querySelector('#name');
        var input = document.querySelector('input');
        
        var myVue = new myVue({
            name: 'hello world'
        }, ele, 'name');
        
        //改变输入框内容
        input.oninput = function (e) {
            myVue.data.name = e.target.value
        }
        //改变data内容
        function changeInput(){
            myVue.data.name = "难凉热血"
        
        }
    </script>
    </body>
    </html>
    

    observer.js

        /**
         * 把一个对象的每一项都转化成可观测对象
         * @param { Object } obj 对象
         */
        function observable (obj) {
            if (!obj || typeof obj !== 'object') {
                return;
            }
            let keys = Object.keys(obj);
            keys.forEach((key) =>{
                defineReactive(obj,key,obj[key])
            })
            return obj;
        }
        /**
         * 使一个对象转化成可观测对象
         * @param { Object } obj 对象
         * @param { String } key 对象的key
         * @param { Any } val 对象的某个key的值
         */
        function defineReactive (obj,key,val) {
            let dep = new Dep();
            Object.defineProperty(obj, key, {
                get(){
                    dep.depend();
                    console.log(`${key}属性被读取了`);
                    return val;
                },
                set(newVal){
                    val = newVal;
                    console.log(`${key}属性被修改了`);
                    dep.notify()                    //数据变化通知所有订阅者
                }
            })
        }
        class Dep {
            
            constructor(){
                this.subs = []
            }
            //增加订阅者
            addSub(sub){
                this.subs.push(sub);
            }
            //判断是否增加订阅者
            depend () {
                if (Dep.target) {
                    this.addSub(Dep.target)
                }
            }
    
            //通知订阅者更新
            notify(){
                this.subs.forEach((sub) =>{
                    sub.update()
                })
            }
            
        }
        Dep.target = null;
    
    watcher.js
    
     class Watcher {
         constructor(vm,exp,cb){
             this.vm = vm;
             this.exp = exp;
             this.cb = cb;
             this.value = this.get();  // 将自己添加到订阅器的操作
         }
         get(){
             Dep.target = this;  // 缓存自己
             let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
             Dep.target = null;  // 释放自己
             return value;
         }
         update(){
             let value = this.vm.data[this.exp];
             let oldVal = this.value;
             if (value !== oldVal) {
                 this.value = value;
                 this.cb.call(this.vm, value, oldVal);
             }
     }
    }
    

    效果:
    在这里插入图片描述

    完整代码,请戳这里☞ vue数据双向绑定原理及实现

    7. 总结

    总结一下:

    实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。
    在这里插入图片描述
    转载自:https://www.cnblogs.com/wangjiachen666/p/9883916.html
    可参考:
    Object.defineProperty:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

    阮一峰mvc、mvvm、mvp:http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html

    展开全文
  • 引言:vue双向绑定原理简单来说就是:vue内部使用object.defineProperty方法给所有数据加上getter和setter方法,在数据发生改变时发布消息给订阅者Watcher,触发响应的监听回调。 object .defineProperty是JS里一...
    引言:vue的双向绑定原理简单来说就是:vue内部使用object.defineProperty方法给所有数据加上getter和setter方法,在数据发生改变时发布消息给订阅者Watcher,触发响应的监听回调。
    object .defineProperty是JS里一个重要的方法,vue的双向绑定ES6里一些方法都是靠它实现的。

    在这里插入图片描述

    如图所示zuoerobject .defineProperty 方法给对象的属性动态加上setter和getter方法,外部获取和设置对象属性值时都会触发对应的getter,setter方法。
    object .defineProperty 方法需要一个中间变量来作为返回值,否则的话set方法中直接给对象的属性设置值会无限的触发set方法陷入死循环。
    vue就是在data对象中使用 object .defineProperty 方法,遍历属性给每一个属性设置setter和getter方法。每个组件实例都会对应一个Watcher实例,当setter方法触发时都会通知Watcher,从而重新渲染关联组件。这就是vue双向绑定的原理。注意object .defineProperty是ES5独有特性,这也是vue不支持IE8以及更低版本的原因。
    展开全文
  • 先声明,该代码是网上一位大佬提供的,但是学习它的代码过程中...该压缩文件内容是vue数据双向绑定的实现与原理解析,提供核心完整代码,并有我的代码注释,浅显易懂,但是需要es6的学习和有一定的javascript基础才行。
  • VUE双向绑定原理

    2021-03-04 10:49:12
    vue数据双向绑定原理 vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,我们可以先来看一下通过控制台输出一个定义在vue初始化数据上的对象是个什么东西。 代码: var vm = new Vue({ data:...
  • 浅谈vue双向绑定原理

    万次阅读 2018-08-05 22:51:20
    目前angular,reat和vue都是mvvm类型的框架 以vue为例   这里的vm 就是vue框架,它相当于中间枢纽的作用,连接着model 和view. 当前台显示的view发生变化了,它会实时反应到viewModel上,如果有需要,...
  • vue 3.0将使用proxy 来实现 双向绑定 于是简单做了个 双向绑定的demo <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=...
  • https://juejin.im/post/5dcb548e51882557296f901f
  • Vue双向绑定原理及实现

    千次阅读 多人点赞 2018-01-29 11:05:48
    vue双向绑定原理及实现 一个极简双向数据绑定的示例: var obj = {}; Object.defineProperty(obj,'hello',{ set:function(newVal){ document.getElementById
  • Vue的响应式原理,也可以叫双向绑定原理,MVVM模式原理。 一、MVVM 理解 MVVM分为Model、View、ViewModel三者。 Model 代表数据模型,数据和业务逻辑都在Model层中定义; View 代表UI视图,负责数据的展示; ...
  • Vue 框架是一种 MVVM 框架,它有一个很大的特点就是数据双向绑定,在开发过程中我们只需要操作 Model ,而不需要修改 View ,使用起来 VR 因吹斯汀。但是它的实现原理并不复杂,主要是运用了设计模式中的观察者模式...
  • 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图来吸引各位:...
  • Vue双向绑定原理

    千次阅读 2020-09-05 19:14:03
    Vue双向绑定原理的时候,发现订阅器Dep是可以直接调用他自己的target的属性,但是class本质是一个函数,为此确认了一下 //为啥Dep类本质是个函数,可以直接调用属性? class HanClass{ constructor(){ this.name...
  • 双向绑定原理 通过Object.defineproperty()重新定义对象属性的set方法、get方法来实现的,从这个属性中取值时会触发get方法,改变这个属性时会触发set方法,所以我们只要将一些需要更新view的方法放在这里面就...
  • 原理 利用访问器属性(如下)实现数据属性变化到view变化的映射,set用于在数据发生变化时加入相应的数据带view的处理方法 Object.defineProperty(obj, &amp;quot;hello&amp;quot;, { get: function () {...
  • vue 双向绑定原理

    2020-10-30 14:01:14
    <!... <... <head>...meta charset="UTF-8">...meta name="viewport" content="width=device-width, initial-scale=1.0">...v-model双向绑定原理</title> <script src="https://lib.baomit
  • web前端面试题汇总-精美桌面-vue双向绑定原理,把面试题设置成桌面,每天打开电脑就学习一遍,加强记忆。会不断更新,不断上传!
  • vue是一个mvvm框架,双向绑定vue的一个核心功能,所谓双向绑定就是当试图发生改变的时候传递给VM(ViewModel ),让数据得到更新,当数据发生改变的时候传给VM(ViewModel ),使得视图发生变化!概念都知道,但是vue...
  • 主要为大家详细介绍了Vue数据双向绑定底层实现原理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 16,154
精华内容 6,461
关键字:

vue双向绑定原理

vue 订阅