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 相关文章推荐
关于IE7 IE8弹出窗口顶上
Dec 22 Javascript
用js实现的检测浏览器和系统的函数
Apr 09 Javascript
分享27个jQuery 表单插件集合推荐
Apr 25 Javascript
jquery 定位input元素的几种方法小结
Jul 28 Javascript
Get中文乱码IE浏览器Get中文乱码解决方案
Dec 26 Javascript
jquery.post用法之type设置问题
Feb 24 Javascript
基于vue的fullpage.js单页滚动插件
Mar 20 Javascript
JavaScrip数组删除特定元素的几种方法总结
Sep 06 Javascript
详解如何在你的Vue项目配置vux
Jun 04 Javascript
微信小程序搭建自己的Https服务器
May 02 Javascript
Node.js实现一个HTTP服务器的方法示例
May 13 Javascript
Ant Design Pro 下实现文件下载的实现代码
Dec 03 Javascript
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实现对文件压缩简单的方法
2019/09/29 PHP
JavaScript 浮点数运算 精度问题
2009/10/06 Javascript
比较详细的关于javascript 解析json的代码
2009/12/16 Javascript
使用滤镜设置透明导致 IE 6/7/8/9 解析异常的解决方法
2011/04/07 Javascript
node.js超时timeout详解
2014/11/26 Javascript
js中匿名函数的创建与调用方法分析
2014/12/19 Javascript
JS截取与分割字符串常用技巧总结
2015/11/10 Javascript
基于jQuery的ajax方法封装
2016/07/14 Javascript
jQuery插件FusionCharts绘制2D双折线图效果示例【附demo源码】
2017/04/14 jQuery
Vue2.0基于vue-cli+webpack Vuex的用法(实例讲解)
2017/09/15 Javascript
微信小程序request请求后台接口php的实例详解
2017/09/20 Javascript
jquery应用实例分享_实现手风琴特效
2018/02/01 jQuery
vue计算属性和监听器实例解析
2018/05/10 Javascript
vue实现直播间点赞飘心效果的示例代码
2019/09/20 Javascript
[43:57]Liquid vs Mineski 2019国际邀请赛小组赛 BO2 第二场 8.16
2019/08/19 DOTA
Python多线程同步Lock、RLock、Semaphore、Event实例
2014/11/21 Python
修复CentOS7升级Python到3.6版本后yum不能正确使用的解决方法
2018/01/26 Python
对Python中的@classmethod用法详解
2018/04/21 Python
python2.7实现复制大量文件及文件夹资料
2019/08/31 Python
余弦相似性计算及python代码实现过程解析
2019/09/18 Python
python多进程间通信代码实例
2019/09/30 Python
Python序列对象与String类型内置方法详解
2019/10/22 Python
Python在终端通过pip安装好包以后在Pycharm中依然无法使用的问题(三种解决方案)
2020/03/10 Python
Python描述符descriptor使用原理解析
2020/03/21 Python
浅谈TensorFlow之稀疏张量表示
2020/06/30 Python
python读取xml文件方法解析
2020/08/04 Python
python如何将图片转换素描画
2020/09/08 Python
python 利用jieba.analyse进行 关键词提取
2020/12/17 Python
详解CSS3 用border写 空心三角箭头 (两种写法)
2017/09/29 HTML / CSS
Canvas波浪花环的示例代码
2020/08/21 HTML / CSS
波兰最大的儿童服装连锁店之一:5.10.15.
2018/02/11 全球购物
小学新学期寄语
2014/04/02 职场文书
拓展策划方案
2014/06/03 职场文书
如何解决.cuda()加载用时很长的问题
2021/05/24 Python
「月刊Comic Alive」2022年5月号封面公开
2022/03/21 日漫
微软团队与 NASA 科学家和惠普企业(HPE)的工程师合作
2022/04/21 数码科技