Vue实现双向绑定的原理以及响应式数据的方法


Posted in Javascript onJuly 02, 2018

一、vue中的响应式属性

Vue中的数据实现响应式绑定

1、对象实现响应式:

是在初始化的时候利用definePrototype的定义set和get过滤器,在进行组件模板编译时实现water的监听搜集依赖项,当数据发生变化时在set中通过调用dep.notify进行发布通知,实现视图的更新。

2、数组实现响应式:

对于数组则是通过继承重写数组的方法splice、pop、push、shift、unshift、sort、reverse、等可以修改原数组的方式实现响应式的,但是通过length以及直接利用item[index]方式修改数组是不能实现响应式的改变dom(这种两种方式涉及到数组的内部实现)。在数据更新后为了避免过于频繁的进行dom的操作,在vue中会将更新的dom进行批量操作,而不是直接有数据更新就刷新dom,vue将需要更新的dom压入异步队列记性批量操作,提高性能。

下面具体的实现,实现原理大致如下:

Vue实现双向绑定的原理以及响应式数据的方法    

对象中实现双向数据绑定,可以直接在浏览器查看效果:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Two-way data-binding</title>
</head>
<body>
 
 <div id="app">
  <input type="text" v-model="text">
  {{ text }}
 </div>

 <script>
  function observe (obj, vm) {
   Object.keys(obj).forEach(function (key) {
    defineReactive(vm, key, obj[key]);
   });
  }

  function defineReactive (obj, key, val) {

   var dep = new Dep();

   Object.defineProperty(obj, key, {
    get: function () {
     // 添加订阅者watcher到主题对象Dep
     if (Dep.target) dep.addSub(Dep.target);
     return val
    },
    set: function (newVal) {
     if (newVal === val) return
     val = newVal;
     // 作为发布者发出通知
     dep.notify();
    }
   });
  }

  function nodeToFragment (node, vm) {
   var flag = document.createDocumentFragment();
   var child;
   
   while (child = node.firstChild) {
    compile(child, vm);
    flag.appendChild(child); // 将子节点劫持到文档片段中
   }

   return flag;
  }

  function compile (node, vm) {
   var reg = /\{\{(.*)\}\}/;
   // 节点类型为元素
   if (node.nodeType === 1) {
    var attr = node.attributes;
    // 解析属性
    for (var i = 0; i < attr.length; i++) {
     if (attr[i].nodeName == 'v-model') {
      var name = attr[i].nodeValue; // 获取v-model绑定的属性名
      node.addEventListener('input', function (e) {
       // 给相应的data属性赋值,进而触发该属性的set方法
       vm[name] = e.target.value;
      });
      node.value = vm[name]; // 将data的值赋给该node
      node.removeAttribute('v-model');
     }
    };

    new Watcher(vm, node, name, 'input');
   }
   // 节点类型为text
   if (node.nodeType === 3) {
    if (reg.test(node.nodeValue)) {
     var name = RegExp.$1; // 获取匹配到的字符串
     name = name.trim();

     new Watcher(vm, node, name, 'text');
    }
   }
  }

  function Watcher (vm, node, name, nodeType) {
   Dep.target = this;
   this.name = name;
   this.node = node;
   this.vm = vm;
   this.nodeType = nodeType;
   this.update();
   Dep.target = null;
  }

  Watcher.prototype = {
   update: function () {
    this.get();
    if (this.nodeType == 'text') {
     this.node.nodeValue = this.value;
    }
    if (this.nodeType == 'input') {
     this.node.value = this.value;
    }
   },
   // 获取data中的属性值
   get: function () {
    this.value = this.vm[this.name]; // 触发相应属性的get
   }
  }

  function Dep () {
   this.subs = []
  }

  Dep.prototype = {
   addSub: function(sub) {
    this.subs.push(sub);
   },

   notify: function() {
    this.subs.forEach(function(sub) {
     sub.update();
    });
   }
  };

  function Vue (options) {
   this.data = options.data;
   var data = this.data;

   observe(data, this);

   var id = options.el;
   var dom = nodeToFragment(document.getElementById(id), this);

   // 编译完成后,将dom返回到app中
   document.getElementById(id).appendChild(dom); 
  }

  var vm = new Vue({
   el: 'app',
   data: {
    text: 'hello world'
   }
  });

 </script>

</body>
</html>

在vue的data中定义的属性才具有响应式的效果,通过vue.name或者this.name形式定义的全局变量虽然可以在template中使用,但是其不是响应式的。同时在data中定义的对象obj,如果后面给对象定义新的属性也不是响应式的,除非通过vue提供的方法set设置,具体如下:

new vue({

 data(){

   return {

    obj:{}

  }

 },

 methods:{

   this.obj.name="hs"//非响应属性

   this.$set(this.obj,'name','hs')//name属性将会是响应式的

  }

})

如果data中定义的有数组元素同时在computed中也要注意具体如下面介绍。

二、computed定义的计算属性

1、在vue中$option.data中定义的数据是都是响应式的,在初始化生命周期的时候就已经实现了数据双向绑定,通常在view中只是用一个表达式语句,当绑定时的逻辑比较复杂是可以通过计算属性的方式实现。但是在computed中定义的属性只具有get方法,所以当在程序中改变属性的时候并不能实现视图的动态响应。可以通过显示的定义set的方式实现视图的刷新。

<div v-on:click="con">{{model1}}</div>

JS代码

const app = new Vue({

    data:function () {
     return {
       model:'hello',
       name:'world',

     }
    },
    computed:{
      model1:
        {
        get: function(){
          return this.model;
        },set:function (val) {
          return this.model=val;
        }
      }

    },
    methods:{
      con:function () {

        this.model1="hello world"//动态修改model1属性时,视图也会跟着跟新

        console.log(this.model1);
      }
    },
   /* watch:{
      'model1':function (val) {
        console.log(val)
      }
    }*/
  }).$mount('#app')

2、如果在computed中定义的属性依赖于data中定义的属性,当data中的属性动态变化时,视图中绑定的computed的值也会跟着变化

const app = new Vue({
   /*  router:router,*/
    data:function () {
     return {
       model:'hello',
       name:'world',

     }
    },
    computed:{
      model1:
        {
        get: function(){
          return a;
        },set:function (val) {
          a=val;
        }
      }

    },
    methods:{
      con:function () {

        this.model1="hello world"

        console.log(this.model1);
      }
    },
   /* watch:{
      'model1':function (val) {
        console.log(val)
      }
    }*/
  }).$mount('#app')

通过以上两种方式实现的computed的动态双向绑定实质是通过中间的层data的属性,computed中依赖的属性如果不是data中的属性则也不能实现动态的绑定 ,即当上面的get,set中的this.model换成一个全局的属性时,也不能实现动态的更新。所以实质上在vue中最为原始的响应式数据实际是在data中定义的,如果computed中定义的属性依赖于data中的属性时,其实质是其依赖data中的属性在访问时触发data中的getter方法,从而会注册监听器,所以当依赖属性变化时,computed中的属性也会跟着变化。但是在computed中定义的不具有依赖项的属性是直接挂在在vue实例上的属性其是不具有响应式的特点的。

3、watch监听data中属性的变化,可以实现和computed的同样的效果,当时当监听的属性依赖多个属性时,利用computed更为方便,如下:

(1)使用watch监听

<div id="demo">{{fullName}}</div>
 
var vm = new Vue({
 el: '#demo',
 data: {
 firstName: 'Foo',
 lastName: 'Bar',
 fullName: 'Foo Bar'
 }
})
 
vm.$watch('firstName', function (val) {
 this.fullName = val + ' ' + this.lastName
})
 
vm.$watch('lastName', function (val) {
 this.fullName = this.firstName + ' ' + val
}

(2)利用computed监听,更为简洁,同时computed会将结果进行缓存,当结果没有发生变化时,不会触发相应回调

var vm = new Vue({
 data: {
 firstName: 'Foo',
 lastName: 'Bar'
 },
 computed: {
 fullName: function () {
 return this.firstName + ' ' + this.lastName
 }
 }
})

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript 关闭IE6、IE7
Jun 01 Javascript
学习ExtJS fit布局使用说明
Oct 08 Javascript
Google 静态地图API实现代码
Nov 19 Javascript
javascript中的throttle和debounce浅析
Jun 06 Javascript
jquery实现搜索框常见效果的方法
Jan 22 Javascript
jQuery.form.js的使用详解
Jun 14 jQuery
vue按需引入element Transfer 穿梭框
Sep 30 Javascript
vue弹窗消息组件的使用方法
Sep 24 Javascript
详解js中Array的方法及技巧
Sep 12 Javascript
js 实现在2d平面上画8的方法
Oct 10 Javascript
webpack的 rquire.context用法实现工程自动化的方法
Feb 07 Javascript
详解实现vue的数据响应式原理
Jan 20 Vue.js
jsonp跨域获取数据的基础教程
Jul 01 #Javascript
vue + webpack如何绕过QQ音乐接口对host的验证详解
Jul 01 #Javascript
关于Vue组件库开发详析
Jul 01 #Javascript
D3.js实现拓扑图的示例代码
Jun 30 #Javascript
详解angular如何调用HTML字符串的方法
Jun 30 #Javascript
angular6.0使用教程之父组件通过url传递id给子组件的方法
Jun 30 #Javascript
基于webpack4搭建的react项目框架的方法
Jun 30 #Javascript
You might like
PHP 开发工具
2006/12/06 PHP
php下网站防IP攻击代码,超级实用
2010/10/24 PHP
php中strlen和mb_strlen用法实例分析
2016/11/12 PHP
php + WebUploader实现图片批量上传功能
2019/05/06 PHP
PHP页面静态化――纯静态与伪静态用法详解
2020/06/05 PHP
Javascript miscellanea -display data real time, using window.status
2007/01/09 Javascript
javascript支持firefox,ie7页面布局拖拽效果代码
2007/12/20 Javascript
ASP中进行HTML数据及JS数据编码函数
2009/11/11 Javascript
表单序列化与jq中的serialize使用示例
2014/02/21 Javascript
jQuery实现单击按钮遮罩弹出对话框(仿天猫的删除对话框)
2014/04/10 Javascript
原创jQuery弹出层插件分享
2015/04/02 Javascript
Angular2内置指令NgFor和NgIf详解
2016/08/03 Javascript
利用JS轻松实现获取表单数据
2016/12/06 Javascript
jQuery Validation Engine验证控件调用外部函数验证的方法
2017/01/18 Javascript
详解AngularJS脏检查机制及$timeout的妙用
2017/06/19 Javascript
nodejs 搭建简易服务器的图文教程(推荐)
2017/07/18 NodeJs
基于Bootstrap表单验证功能
2017/11/17 Javascript
Vue-CLI 3 scp2自动部署项目至服务器的方法
2020/07/24 Javascript
JavaScript中while循环的基础使用教程
2020/08/11 Javascript
[01:35]2018年度CS GO最佳战队-完美盛典
2018/12/17 DOTA
Python运用于数据分析的简单教程
2015/03/27 Python
Python实现MySQL操作的方法小结【安装,连接,增删改查等】
2017/07/12 Python
使用 Python 实现微信公众号粉丝迁移流程
2018/01/03 Python
Python读取mat文件,并保存为pickle格式的方法
2018/10/23 Python
numpy数组广播的机制
2019/07/12 Python
python中的Elasticsearch操作汇总
2019/10/30 Python
用 python 进行微信好友信息分析
2020/11/28 Python
CSS3感应鼠标的背景闪烁和图片缩放动画效果
2014/05/14 HTML / CSS
HTML5未来发展趋势
2016/02/01 HTML / CSS
HTML5 视频播放(video),JavaScript控制视频的实例代码
2018/10/08 HTML / CSS
AmazeUi Tree(树形结构) 应用小结
2020/08/17 HTML / CSS
在什么时候需要使用"常引用"
2015/12/31 面试题
销售简历自我评价
2014/01/24 职场文书
流动人口婚育证明范本
2014/09/26 职场文书
2014年业务工作总结
2014/11/17 职场文书
2016年六一文艺汇演开幕词
2016/03/04 职场文书