精华内容
下载资源
问答
  • 双向数据绑定 概念: vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。 主要流程(三...

    双向数据绑定

    概念:
    vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。

    主要流程(三部分)

    第一步: 需要observer的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
    这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化

    第二步: compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

    第三步: Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
    1、在自身实例化时往属性订阅器(dep)里面添加自己
    2、自身必须有一个update()方法
    3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。

    第四步: MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

    流程图片:
    在这里插入图片描述
    对objet.definedProperty()理解

    1.作用

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

    2.参数

     Object.definePropert(obj,prop,{})
    //1.obj 要定义属性的对象
    //2.prop 要定义或修改的属性的名称 “a”
    //3. descriptor 要定义或修改的属性描述符(是一个对象){}
    

    3.案列

    var obj = {
        b: 2021
      }
      console.log(obj)
      console.log(obj.b) //2021
      Object.defineProperty(obj, 'a', {
        value:1998,
        writable:false,//是否可以被修改
        enumerable:false,//是否可以被枚举也就是是否可以遍历
        configurable:false//是否可以被删除
      })
      //控制台输出 delete obj.b
    
      for(let key of Object.keys(obj)){
        console.log(key)
        //for/of 打印对象会报错,点配合Object.keys使用,
        打印出来的是数值
      }
      obj.a=0817
      console.log(obj)
      
      // tip:writable enumerable configurable默认值都是false,
      value不给值的情况下是undefined
    
     var Books = {}
      var name = ""
      Object.defineProperty(Books, 'name', {
        set: function (value) {
          name = value
          console.log('你取了一个书名叫做' + value)
        },
        get: function () {
          return "《" + name + "》"
        }
      })
      Books.name = 'vue权威指南'
      console.log(Books.name)//《vue权威指南》
      console.log(Books)
    

    在这里插入图片描述

    4.for/in和for/of的区别

    1.for..in遍历数组会得到下标
    2.for...in遍历对象得到是对象的key值
    
    1.for...of遍历数组得到的是value
    2.for...of直接遍历对象会报错
    3.for...of遍历对象需要配合Object.keys()
    var obj = {a:100,b:200,c:300}
    for(var a of Object.keys(obj)){
     console.log(a) // 得到的也是对象的key值 a b c
     console.log(a+':'+obj[a]) // a:100 b:200 c:300
    }
    
    展开全文
  • Vue实现数据双向绑定的原理 (一)Vue实现数据双向绑定主要是:采用数据劫持结合发布者–订阅者模式的方式,通过object.defineProperty()来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的...

    Vue实现数据双向绑定的原理

    (一)Vue实现数据双向绑定主要是:采用数据劫持结合发布者–订阅者模式的方式,通过object.defineProperty()来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。当把一个普通JavaScript对象传给Vue实例来作为它的data选项时,Vue将遍历他的属性Property,用object.defineProperty将它们(Property)转为getter/setter。用户看不到getter/setter,但是在内部它们让Vue追踪依赖,在属性被访问和修改时通知改变。

    (二)Vue的数据双向绑定将MVVM作为数据绑定的入口,整合observer,compile和watch三者,通过observer监听自己的model的数据变化,通过compile来解析编译模板指令(vue中是用来解析{{}}),最终利用watcher搭起observer和compile之间的通信桥梁,达到数据变化->视图更新。视图交互变化(input)->数据model变更双向绑定效果。

    展开全文
  • vue源码之数据双向绑定原理一.双向绑定的原理介绍二.代码简单实现 ...下图是一个vue数据双向绑定的过程: 将vue中的data里的所有属性通过实现Observer来完成数据劫持 Dep是一个容器来存放所有的订阅者Watc

    这里什么说是vue2.0的双向绑定原理是因为和vue3.0的实现方式是有区别的。

    一.双向绑定的原理介绍

    • vue的双向绑定是数据和视图的同步变化,即当数据发生变化的时候,相关的视图会发生改变;视图发生改变的时候,数据也会随之变化。它是通过 数据劫持 结合 发布订阅模式的方式来实现的。

    下图是一个vue数据双向绑定的过程:

    1. 将vue中的data里的所有属性通过实现Observer来完成数据劫持
    2. Dep是一个容器来存放所有的订阅者Watcher(订阅者Watcher可能有很多),Dep用来解析页面的模板 {{ name }} ,执行相应方法将数据解析到页面上。
    3. 实现一个监听者Oberver来劫持并监听所有的属性,一旦有属性发生变化就通知订阅者Watcher,
    4. 订阅者watcher来接受属性变化的通知(notify)并执行相应的方法,从而更新视图
    5. 实现一个解析器compile,可以扫描和解析每个节点的相关指令(v-xxx),通过指令(v-xxx)去对DOM进行封装。当数据发生变化,指令修改对应的DOM,数据驱动DOM的变化。反向,Vue也会监听操作,修改视图时,Vue监听到变化后,改变数据。数据的双向变化形成

    在这里插入图片描述
    下面来简单实现一下这个流程(不代表源码实现)。

    二.简单实现双向绑定

    下面通过类来定义上图中的Observer,Dep,Watcher,Compile等来抽象出数据的双向绑定。

    //定义一个容器类 来存放所有的订阅者
    class Dep {
      constructor() {
        this.subs = [];
      }
      //订阅
      addSub(watcher) {
        this.subs.push(watcher);
      }
      //发布
      notify() {
        this.subs.forEach((watcher) => watcher.update());
      }
    }
    
    //观察者:将数据劫持和页面联系起来
    class Watcher {
      constructor(vm, expr, cb) {
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        //默认存放一个老值
        this.oldValue = this.get();
      }
      get() {
        Dep.target = this; //先把自己放在全局上
        //取值,把观察者和数据联系起来
        let value = CompileUtil.getVal(this.vm, this.expr);
        //不取消任何取值都会添加watcher
        Dep.target = null;
        return value;
      }
      update() {
        //更新操作,数据变化后会调用观察者update方法
        let newVal = CompileUtil.getVal(this.vm, this.expr);
        if (newVal !== this.oldValue) {
          this.cb(newVal);
        }
      }
    }
    
    //将data里的所有属性包括对象里的属性劫持
    class Observer {
      constructor(data) {
        this.observer(data);
      }
      observer(data) {
        if (data && typeof data == "object") {
          for (let key in data) {
            this.defineReactive(data, key, data[key]);
          }
        }
      }
      defineReactive(obj, key, value) {
        //value还是对象的话要继续,才会给全部都赋予get和set方法
        this.observer(value);
        let dep = new Dep(); //给每个属性都加上一个发布订阅功能
        Object.defineProperty(obj, key, {
          get() {
            //创建watcher时候,会取到对应内容,并且把watcher放到全局上
            Dep.target && dep.addSub(Dep.target);
            return value;
          },
          set(newVal) {
            //若赋值的是一个对象,还需要继续监控
            if (newVal != value) {
              this.observer(newVal);
              value = newVal;
              dep.notify();
            }
          },
        });
      }
    }
    
    class Compiler {
      constructor(el, vm) {
        //判断el属性
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        //把当前节点中的元素获取到,并放到内存中
        let fragment = this.node2fragment(this.el);
        //把节点中内容进行替换
    
        //编译模板,用数据编译
        this.compile(fragment);
        //把内容塞回页面
        this.el.appendChild(fragment);
      }
      //判断是不是指令
      isDirective(attrName) {
        return attrName.startsWith("v-"); //开头
      }
      //编译元素的方法
      compileElement(node) {
        let attributes = node.attributes; //类数组
        [...attributes].forEach((attr) => {
          let { name, value: expr } = attr;
          if (this.isDirective(name)) {
            //v-model v-html v-bind
            let [, directive] = name.split("-"); //v-on:click
            let [directiveName, eventName] = directive.split(":");
            //调用不同指令来处理
            CompileUtil[directiveName](node, expr, this.vm, eventName);
          }
        });
      }
      //编译文本的方法
      compileText(node) {
        //判断文本节点中是否包含{{}}
        let content = node.textContent;
        //(.+?)匹配一个大括号内的,一个及以上,到第一个大括号结束时候结束
        if (/\{\{(.+?)\}\}/.test(content)) {
          CompileUtil["text"](node, content, this.vm);
        }
      }
      //编译的核心方法
      compile(node) {
        let childNodes = node.childNodes;
        [...childNodes].forEach((child) => {
          if (this.isElementNode(child)) {
            this.compileElement(child);
            this.compile(child); //递归,获得内层
          } else {
            this.compileText(child);
          }
        });
      }
      node2fragment(node) {
        //创建一个文本碎片
        let fragment = document.createDocumentFragment();
        let firstChild;
        while ((firstChild = node.firstChild)) {
          //appendChild具有移动性
          fragment.appendChild(firstChild);
        }
        return fragment;
      }
    
      isElementNode(node) {
        //判断是否为元素节点
        return node.nodeType === 1;
      }
    }
    
    //绑定处理事件的各种方法
    CompileUtil = {
      //取得对应的数据
      getVal(vm, expr) {
        //vm.$data 'school.name'
        //返回name
        return expr.split(".").reduce((data, current) => {
          return data[current]; //继续取值,取到name
        }, vm.$data);
      },
      setValue(vm, expr, value) {
        expr.split(".").reduce((data, current, index, arr) => {
          if (index == arr.length - 1) {
            return (data[current] = value);
          }
          return data[current];
        }, vm.$data);
      },
      model(node, expr, vm) {
        //node节点,expr是表达式,vm是当前实例
        let fn = this.updater["modeUpdater"];
        //给输入框加一个观察者,稍后数据更新就会触发此方法,将新值给输入框赋予值
        new Watcher(vm, expr, (newVal) => {
          fn(node, newVal);
        });
        node.addEventListener("input", (e) => {
          let value = e.target.value; //获取用户输入的内容
          this.setValue(vm, expr, value);
        });
        let value = this.getVal(vm, expr);
        fn(node, value);
      },
      html(node, expr, vm) {
        let fn = this.updater["htmlUpdater"];
        new Watcher(vm, expr, (newVal) => {
          fn(node, newVal);
        });
        let value = this.getVal(vm, expr);
        fn(node, value);
      },
      getContentValue(vm, expr) {
        //遍历一个表达式,将内容重新替换成一个完整的内容,返还回去
        return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
          return this.getVal(vm, args[1]);
        });
      },
      on(node, expr, vm, eventName) {
        //  v-on:click="change"  expr就是change
        node.addEventListener(eventName, (e) => {
          vm[expr].call(vm, e); //this.change
        });
      },
      text(node, expr, vm) {
        let fn = this.updater["textUpdater"];
        let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
          //给表达式每个{{}}都加个观察者
          new Watcher(vm, args[1], (newVal) => {
            fn(node, this.getContentValue(vm, expr)); //返回一个全的字符串
          });
          return this.getVal(vm, args[1]);
        });
        fn(node, content);
      },
    
      //更新视图
      updater: {
        //把数据插入节点当中
        modeUpdater(node, value) {
          node.value = value;
        },
        textUpdater(node, value) {
          node.textContent = value;
        },
        htmlUpdater(node, value) {
          //xss攻击
          node.innerHTML = value;
        },
      },
    };
    
    // 基类,调度
    class Vue {
      constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        let computed = options.computed;
        let methods = options.methods;
        if (this.$el) {
          //把数据全部转换成用Object.defineProperty来定义,数据劫持
          new Observer(this.$data);
          //{{getNewName}} reduce 取值是vm.$data.getNewName
          for (let key in computed) {
            Object.defineProperty(this.$data, key, {
              get: () => {
                //注意this指向el实例
                return computed[key].call(this);
              },
            });
          }
          for (let key in methods) {
            Object.defineProperty(this, key, {
              get: () => {
                //注意this指向el实例
                return methods[key];
              },
            });
          }
          //把数据获取操作vm上的取值操作都代理到vm.$data上
          this.proxyVm(this.$data);
          new Compiler(this.$el, this);
        }
      }
      proxyVm(data) {
        for (let key in data) {
          Object.defineProperty(this, key, {
            get() {
              //相当于在$data上取值,进行转换操作,不需要深层代理
              return data[key];
            },
            set(newVal) {
              //设置代理方法
              data[key] = newVal;
            },
          });
        }
      }
    }
    
    

    三.体验双向绑定

    尝试将刚才写的代码引入到页面来使用,接下来就和Vue的基本使用一样了。

    <!DOCTYPE html>
    <html lang="en">
    	<head>
    	    <meta charset="UTF-8">
    	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    	    <title>Vue</title>
    	</head>
    	<body>
    	    <div id="app">
    	     <!-- 此时改变输入框的值,页面的值也会相应改变 -->
    	        <input v-model="person.name"/>
    	        <div>{{person.name}}</div>
    	        <div>{{person.age}}</div>
    	       
    	    </div>
    	    <script src="vue.js"></script>
    	    <script>
    	        new Vue({
    	            el: '#app',
    	            data: {
    	                info: {
    	                    name: 'goudan',
    	                    age: 20
    	                },
    	            },   
    	        })
    	    </script>
    	</body>
    </html>
    

    自己写一个简单版本的vue可以更好的理解它的原理, 提高自己的思维方式。我们用多了框架过后可能对原生的JS就有所遗忘,要知道框架都是原生的来构造的,所以阅读并理解源码对我们深入学习和技能提升有很大的帮助。

    展开全文
  • Vue 是如何实现数据双向绑定的? Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据 Vue 主要通过以下 4 个步骤来实现数据双向绑定的: 实现⼀个数据监听器 Observer: 对数据对象进⾏遍历,能够对数据...

    Vue 是如何实现数据双向绑定的?

    Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据

    Vue 主要通过以下 4 个步骤来实现数据双向绑定的:

    实现⼀个数据监听器 Observer: 对数据对象进⾏遍历,能够对数据对象的所有属性进行监听,利⽤ Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。并通知订阅者

    实现⼀个指令解析器 Compile: 对每个元素节点的指令进行扫描和解析,根据指令模板将模板中的变量都替换成数据,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,⼀旦数据有变动,收到通知,调⽤更新函数进⾏数据更新。

    实现⼀个订阅者 Watcher: Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析Compile 中对应的更新函数。

    实现⼀个订阅器 Dep: 订阅器采⽤ 发布-订阅 设计模式,⽤来收集订阅者Watcher,对监听器 Observer 和 订阅者 Watcher 进⾏统⼀管理
    在这里插入图片描述

    总结
    个人理解:
    在new Vue的时候,在 Observer 中通过 Object.defineProperty() 达到数据劫持,代理所有数据的 gettersetter 属性,在每次触发 setter 的时候,都会通过 Dep 来通知WatcherWatcher 作为Observer数据监听器与Compile模板解析器之间的桥梁,当 Observer 监听到数据发生改变的时候,通过 Updater 来通知 Compile 更新视图,而 Compile 通过 Watcher 订阅对应数据,绑定更新函数,通过 Dep 来添加订阅者,达到双向绑定。

    展开全文
  • Vue是非常优秀的能实现双向数据绑定的前端框架,可极大提高开发效率。与在 React 中集成 jquery及原生javascript编写的组件一样。React 中也可集成vue。这里我们通过一个 React+Vue 实现的登录页面为例,介绍如何将...
  • vue数据双向绑定原理

    2021-02-03 14:11:00
    vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的 var vm = new Vue({ data: { obj: { a: 1 } }, created: function () { console.log(this.obj); } }); 结果 我们可以看到属性a有两...
  • MVVM:双向数据绑定,指数据层(Model)-视图层(View)-数据视图(ViewModel)的响应式原理。 ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。 1、实现一个数据监听器Observer,...
  • 如图,由于vue数据双向绑定,给waterSetInfo拼接单位的时候,会将res.data也进行赋值,导致waterSet里面的记过不对 解决方案: 深拷贝的方法:JSON暴力转换 解构赋值 this.waterMarkSet = JSON.parse(JSON....
  • 发布订阅模式 Angular 的脏查机制 数据劫持 而 Vue2.0 则采用的是数据劫持与发布订阅相结合的方式实现双向绑定数据劫持主要通过 Object.defineProperty 来实现。 Object.defineProperty 这篇文章我们不详细讨论...
  • Vue2双向数据绑定存在的问题: 关于对象:Vue 无法检测property的添加或移除。 关于数组:不能利用索引直接设置一个数组项,也不能修改数组的长度。 Vue2.0 原理:使用Object.defineProperty对象以及对象属性...
  • 首先,vue实现数据双向绑定的原理是:采用数据劫持结合发布者-订阅者模式,通过Object.defineProperty(obj,props)来劫持各个属性的setter和getter方法,在数据变动时发布消息给订阅者,触发相应的回调函数。...
  • vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化,数据也会跟着同步变化。这也算是vue的精髓之处了。单项数据绑定是使用状态管理工具(如redux)的前提。如果我们...
  • 但是vue2双向数据绑定存在问题: 1.Vue 无法检测property的添加或移除。由于 Vue 会在初始化实例时对property执行getter/setter转化,所以property必须在data对象上存在才能让 Vue 将它转换为响应式的。 2.当你...
  • vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过**Object.defineProperty()**来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 ...
  • 理解VUE双向数据绑定原理和实现

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

    千次阅读 2020-12-22 12:23:59
    今天,我们通过另一种方式实现交互,参考input框的v-model,实现自定义组件的双向数据绑定。 即:父组件值改变,子组件的值跟着改变;反之,子组件值发生变化,父组件值随之变化 子组件定义: 由于不能直接修改...
  • Vue使用的是ES5中**Object.defineProperty()**方法监控对数据的操作,从而实现数据同步。并将变更后的数据重新渲染到视图中。 通常在编写代码时: ​ 我们需要从服务器获取数据(也就是MV模式中的M-model)并...
  • 今天就和大家一起来使用原生js模拟一个Vue数据双向绑定 html部分 <p id="showValue" style="height: 40px;line-height: 40px;color: white;background-color: black;"></p> <in
  • 实现vue数据双向绑定

    2021-07-04 19:34:09
    关于vue数据双向绑定也是面试很喜欢问的题目了,这里讲下实现方式,效果图、源码、demo在文章末尾 首先看下vue的基本结构 <div id="app"><div>{{a.b.c}}</div></div> new Vue({ el: ...
  • Vue中的双向数据绑定是如何实现的? 解题思路: Vue双向数据绑定是通过数据劫持结合发布者订阅者模式来实现的 要实现这种双向数据绑定,必要的条件有: 实现一个数据监听器Observer,能够对数据对象的所有属性...
  • vue3数据双向绑定

    2021-11-18 17:50:39
    vue3关于数据双向绑定 一、script setup 现在,没必要把数据写到data里面,或者是写一个setup函数,再进行return出去。 import进来的组件,可以直接在页面中使用,不再需要vue2的component或者是setup函数的return...
  • 这次给大家带来如何进行Vue数据双向绑定实现,进行Vue数据双向绑定实现的注意事项有哪些,下面就是实战案例,一起来看一下。一、示例var vm = new Vue({data: { obj: { a: 1} },created: function (){ console.log...
  • 1. 父组件使用v-model绑定,子组件props接收参数,$emit触发input事件传值 2. 父组件使用v-model绑定,子组件props接收参数,参数名称可以自定义,$emit触发方法传值,方法名称可以 自定义,通过model属性将prop...
  • vue双向数据绑定

    2021-01-08 14:39:24
    前言 vue事项双向数据绑定主要介绍两大内容: ...vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,通过object.defineproperty() 中的set和 get实现,就是当数据变动时会发布信息给订阅者触发
  • Vue2的双向数据绑定

    2021-03-03 11:49:35
    vue双向绑定原理 vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。我们先来看Object....
  • Vue中, props 是单向数据绑定,虽然在Vue 1.0版本中,通过 .sync 能实现双向数据绑定。但 .sync 在几个版本中被移除,尽管在2.3版本重新引入 .sync 修饰符,可这次引入只是作为一个编译时的语法糖存...
  • 通过标签中 v-model 进行数据绑定 <template> <div> <!-- 面包屑导航区域--> <el-breadcrumb separator-class="el-icon-arrow-right"> <el-breadcrumb-item :to="{ path: '
  • ps:本文章适合和我一样的小白食用,大神请绕路! 第一个例子: <div id="app"> <input type="text" id="username" :value="username" />... var app=new Vue({ el:"#app", data:{ usernam
  • 1 VUE数据双向绑定 1.1双向绑定的原理 VUE实现数据的双向绑定,原理在于通过Object.defineProperty()中的get和set方法实现的一个语法糖。v-model是:value="sth"和@change="val => sth = val"的简写形式。 ...
  • vue数据双向绑定

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

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 45,966
精华内容 18,386
关键字:

vue如何实现数据双向绑定

vue 订阅