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 addBookmark 加入收藏 多浏览器兼容
Aug 15 Javascript
jquery中的on方法使用介绍
Dec 29 Javascript
javascript中typeof操作符和constucor属性检测
Feb 26 Javascript
原生js和jquery实现图片轮播淡入淡出效果
Apr 23 Javascript
JavaScript+html5 canvas绘制的小人效果
Jan 27 Javascript
JS中split()用法(将字符串按指定符号分割成数组)
Oct 24 Javascript
js时间戳格式化成日期格式的多种方法介绍
Feb 16 Javascript
基于JS实现仿京东搜索栏随滑动透明度渐变效果
Jul 10 Javascript
详解JS数组Reduce()方法详解及高级技巧
Aug 18 Javascript
浅谈vue路径优化之resolve
Oct 13 Javascript
vue编译打包本地查看index文件的方法
Feb 23 Javascript
使用jquery的cookie实现登录页记住用户名和密码的方法
Mar 13 jQuery
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 mail 通过Windows的SMTP发送邮件失败的解决方案
2009/05/27 PHP
php中socket通信机制实例详解
2015/01/03 PHP
PHP处理CSV表格文件的常用操作方法总结
2016/07/01 PHP
源码分析 Laravel 重复执行同一个队列任务的原因
2017/12/25 PHP
PHP析构函数destruct与垃圾回收机制的讲解
2019/03/22 PHP
img标签中onerror用法
2009/08/13 Javascript
javascript 必知必会之closure
2009/09/21 Javascript
Javascript的常规数组和关联数组对比小结
2012/05/24 Javascript
JQuery中模拟image的ajaxPrefilter与ajaxTransport处理
2015/06/19 Javascript
jQuery绑定事件的几种实现方式
2016/05/09 Javascript
jQuery+php实时获取及响应文本框输入内容的方法
2016/05/24 Javascript
Bootstrap导航条的使用和理解3
2016/12/14 Javascript
初识NodeJS服务端开发入门(Express+MySQL)
2017/04/07 NodeJs
详解Vuejs2.0之异步跨域请求
2017/04/20 Javascript
JavaScript中最常用的10种代码简写技巧总结
2017/06/28 Javascript
jQuery实现table中两列CheckBox只能选中一个的示例
2017/09/22 jQuery
解决VUEX兼容IE上的报错问题
2018/03/01 Javascript
Vant picker 多级联动操作
2020/11/02 Javascript
Python EOL while scanning string literal问题解决方法
2020/09/18 Python
Python实现扩展内置类型的方法分析
2017/10/16 Python
Python基于Flask框架配置依赖包信息的项目迁移部署
2018/03/02 Python
使用python装饰器计算函数运行时间的实例
2018/04/21 Python
利用python3 的pygame模块实现塔防游戏
2019/12/30 Python
基于Python和PyYAML读取yaml配置文件数据
2020/01/13 Python
推荐技术人员一款Python开源库(造数据神器)
2020/07/08 Python
Python实现给PDF添加水印的方法
2021/01/25 Python
css3动画过渡实现鼠标跟随导航效果
2018/02/08 HTML / CSS
Clarks鞋法国官方网站:英国其乐鞋品牌
2018/02/11 全球购物
MYPROTEIN澳大利亚官方网站:欧洲运动营养品牌
2019/06/26 全球购物
德国、奥地利和瑞士最大的旅行和度假门户网站:HolidayCheck
2019/11/14 全球购物
一份比较全的PHP面试题
2016/07/29 面试题
绩效工资分配方案
2014/01/18 职场文书
经典广告词大全
2014/03/14 职场文书
出纳试用期自我鉴定
2014/04/07 职场文书
2015年信息化建设工作总结
2015/07/23 职场文书
比较几种Redis集群方案
2021/06/21 Redis