1、概述

让我们先来看一下官网的这张数据绑定的说明图:

原理图告诉我们,a对象下面的b属性定义了getter、setter对属性进行劫持,当属性值改变是就会notify通知watch对象,而watch对象则会notify到view上对应的位置进行更新(这个地方还没讲清下面再讲),然后我们就看到了视图的更新了,反过来当在视图(如input)输入数据时,也会触发订阅者watch,更新最新的数据到data里面(图中的a.b),这样model数据就能实时响应view上的数据变化了,这样一个过程就是数据的双向绑定了。

看到这里就会第一个疑问:那么setter、getter是怎样实现的劫持的呢?答案就是vue运用了es5中Object.defineProperty()这个方法,所以要想理解双向绑定就得先知道Object.defineProperty是怎么一回事了;

2.Object.defineProperty

它是es5一个方法,可以直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象,对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个拥有可写或不可写值的属性。存取描述符是由一对 getter-setter 函数功能来描述的属性。描述符必须是两种形式之一;不能同时是两者
属性描述符包括:configurable(可配置性相当于属性的总开关,只有为true时才能设置,而且不可逆)、Writable(是否可写,为false时将不能够修改属性的值)、Enumerable(是否可枚举,为false时for..in以及Object.keys()将不能枚举出该属性)、get(一个给属性提供 getter 的方法)、set(一个给属性提供 setter 的方法)

var o = {name:'vue'};
Object.defineProperty(o, "age",{ value : 3, writable : true,//可以修改属性a的值 enumerable : true,//能够在for..in或者Object.keys()中枚举 configurable : true//可以配置 }); Object.keys(o)//['name','age'] o.age = 4; console.log(o.age) //4 var bValue; Object.defineProperty(o, "b", { get : function(){ return bValue; }, set : function(newValue){ console.log('haha..') bValue = newValue; }, enumerable : true,//默认值是false 及不能被枚举 configurable : true//默认也是false }); o.b = 'something'; //haha.. 

上面分别给出了对象属性描述符的数据描述符和存取描述的例子,注意一点是这两种不能同时拥有,也就是valuewritable不能和getset同时具备。在这里只是很粗浅的说了一下Object.defineProperty这个方法,要了解更多可以点击这里

3.实现observer

我们在上面一部分讲到了es5的Object.defineProperty()这个方法,vue正式通过它来实现对一个对象属性的劫持的,在创建实例的时候vue会对option中的data对象进行一次数据格式化或者说初始化,给每个data的属性都设置上get/set进行对象劫持,代码如下:

function Observer(data){ this.data = data; if(Array.isArray(data)){ protoAugment(data,arrayMethods); //arrayMethods实现对Array.prototype原型方法的拷贝; this.observeArray(data); }else{ this.walk(data); } } Observer.prototype = { walk:function walk(data){ var _this = this; Object.keys(data).forEach(function(key){ _this.convert(key,data[key]); }) }, convert:function convert(key,val){ this.defineReactive(this.data,key,val); }, defineReactive:function defineReactive(data,key,val){ var ochildOb = observer(val); var _this = this; Object.defineProperty(data,key,{ configurable:false, enumerable:true, get:function(){ console.log(`i get the ${key}-->${val}`) return val; }, set:function(newVal){ if(newVal == val)return; console.log(`haha.. ${key} changed oldVal-->${val} newVal-->${newVal}`); val = newVal; observer(newVal);//在这里对新设置的属性再一次进行get/set } }) }, observeArray:function observeArray(items){ for (var i = 0, l = items.length; i < l; i++) { observer(items[i]); } } } function observer(data){ if(!data || typeof data !=='object')return; return new Observer(data); } //让我们来试一下 var obj = {name:'jasonCloud'}; var ob = observer(obj); obj.name = 'wu'; //haha.. name changed oldVal-->jasonCloud newVal-->wu obj.name; //i get the name-->wu

到这一步我们只实现了对属性的set/get监听,但并没实现变化后notify,那该怎样去实现呢?在VUE里面使用了订阅器Dep,让其维持一个订阅数组,但有订阅者时就通知相应的订阅者notify。

let _id = 0;
/*Dep构造器用于维持$watcher检测队列;
*/
function Dep(){this.id = _id++;this.subs = [];
}Dep.prototype = {constructor:Dep,addSub:function(sub){this.subs.push(sub); }, notify:function(){ this.subs.forEach(function(sub){ if(typeof sub.update == 'function') sub.update(); }) }, removeSub:function(sub){ var index = this.subs.indexOf(sub); if(index >-1) this.subs.splice(index,1); }, depend:function(){ Dep.target.addDep(this); } } Dep.target = null; //定义Dep的一个属性,当watcher时Dep.targert=watcher实例对象

在这里构造器Dep,维持内部一个数组subs,当有订阅时就addSub进去,通知订阅者更新时就会调用notify方法通知到订阅者;我们现在合并一下这两段代码

function Observer(data){//省略的代码.. this.dep = new Dep(); //省略的代码.. } Observer.prototype = { //省略的代码.. defineReactive:function defineReactive(data,key,val){ //省略的代码.. var dep = new Dep(); Object.defineProperty(data,key,{ configurable:false, enumerable:true, get:function(){ if(Dep.target){ dep.depend(); //省略的代码.. } return val; }, set:function(newVal){ //省略的代码.. dep.notify(); } }) }, observeArray:function observeArray(items){ for (var i = 0, l = items.length; i < l; i++) { observer(items[i]); } } } function observer(data){ if(!data || typeof data !=='object')return; return new Observer(data); }

上面代码中有一个protoAugment方法,在vue中是实现对数组一些方法的重写,但他并不是直接在Array.prototype.[xxx]直接进行重写这样会影响到所有的数组中的方法,显然是不明智的,vue很巧妙的进行了处理,使其并不会影响到所有的Array上的方法,代码可以点击这里

到这里我们实现了数据的劫持,并定义了一个订阅器来存放订阅者,那么谁是订阅者呢?那就是Watcher,下面让我们看看怎样实现watcher

4.实现一个Watcher

watcher是实现view视图指令及数据和model层数据联系的管道,当在执行编译时候,他会把对应的属性创建一个Watcher对象让他和数据层model建立起联系。但数据发生变化是会触发update方法更新到视图上view中,反过来亦然。

function Watcher(vm,expOrFn,cb){this.vm = vm;this.cb = cb;this.expOrFn = expOrFn;this.depIds = {};var value = this.get(),valuetemp; if(typeof value === 'object' && value !== null){ if(Array.isArray(value)){ valuetemp = []; for(var i = 0,len = value.length;i<len;i++){ valuetemp.push(value[i]); } }else{ valuetemp = {}; for(var j in value){ valuetemp[j] = value[j]; } } this.value = valuetemp; }else{ this.value = value; } }; Watcher.prototype = { update:function(){ this.run(); }, run:function(){ var val = this.get(),valuetemp; var oldVal = this.value; if(val!==oldVal){ if(typeof val === 'object' && val !== null){ if(Array.isArray(val)){ valuetemp = []; for(var i = 0,len = val.length;i<len;i++){ valuetemp.push(val[i]); } }else{ valuetemp = {}; for(var j in val){ valuetemp[j] = val[j]; } } this.value = valuetemp; }else{ this.value = val; } this.cb.call(this,val,oldVal); } }, get:function(){ Dep.target = this; var val = this.getVMVal(); Dep.target = null; return val; }, getVMVal:function(){ var exps = this.expOrFn.split('.'); var val = this.vm._data; exps.forEach(function(key){ val = val[key]; }) return val; }, addDep:function(dep){ if(!this.depIds.hasOwnProperty(dep.id)){ dep.addSub(this); this.depIds[dep.id] = dep; } } }

到现在还差一步就是将我们在容器中写的指令和{{}}让他和我们的model建立起连续并转化成,我们平时熟悉的html文档,这个过程也就是编译;编译简单的实现就是将我们定义的容器里面所有的子节点都获取到,然后通过对应的规则进行转换编译,为了提高性能,先创建一个文档碎片createDocumentFragment(),然后操作都在碎片中进行,等操作成功后一次性appendChild进去;

function Compile(el,vm){this.$vm = vm;this.$el = this.isElementNode(el) ? el : document.querySelector(el);if(this.$el){ this.$fragment = this.nodeToFragment(this.$el); this.init(); this.$el.appendChild(this.$fragment); this.$vm.$option['mount'] && this.$vm.$option['mount'].call(this.$vm); } }

5.实现一个简易版的vue

到目前为止我们可以实现一个简单的数据双向绑定了,接下来要做的就是对这一套流程进行整合了,不多说上码

function Wue(option){this.$option = option; var data = this._data = this.$option.data; var _this = this; //数据代理实现数据从vm.xx == vm.$data.xx; Object.keys(data).forEach(function(val){ _this._proxy(val) }); observer(data) this.$compile = new Compile(this.$option.el , this); } Wue.prototype = { $watch:function(expOrFn,cb){ return new Watcher(this,expOrFn,cb); }, _proxy:function(key){ var _this = this; Object.defineProperty(_this,key,{ configurable: false, enumerable: true, get:function(){ return _this._data[key]; }, set:function(newVal){ _this._data[key] = newVal; } }) } }

在这里定义了一个Wue构造函数,当实例化的时候他会对option的data属性进行格式化(劫持),然后再进行编译,让数据和视图建立起联系;在这里用_proxy进行数据代理是为了当访问数据时可以直接vm.xx而不需要vm._data.xx;

转载于:https://www.cnblogs.com/jqq0820/p/8865413.html

VUE的数据双向绑定相关推荐

  1. Vue的数据双向绑定和Object.defineProperty()

    Vue是前端三大框架之一,也被很多人指责抄袭,说他的两个核心功能,一个数据双向绑定,一个组件化分别抄袭angular的数据双向绑定和react的组件化思想,咱们今天就不谈这种大是大非,当然我也没到达那 ...

  2. vue的数据双向绑定原理

    前言: 什么是数据双向绑定? vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化,数据也会跟着同步变化.这也算是vue的精髓之处了.单项数据绑定是使用状 ...

  3. angular 强制更新视图_angular,vue,react数据双向绑定原理分析

    在不同的 MVVM 框架中,实现双向数据绑定的技术有所不同. Angular数据绑定 Angular 采用"脏值检测"的方式,数据发生变更后,对于所有的数据和视图的绑定关系进行一次 ...

  4. vue实现数据双向绑定的基础之理解对象属性类型

    原生JavaScript之理解对象 ECMA-262 把对象定义为:"无序属性的集合,其属性可以包含基本值.对象或者函数."严格来讲, 这就相当于说对象是一组没有特定顺序的值.对象 ...

  5. vue取消数据双向绑定问题

    如图,由于vue数据的双向绑定,给waterSetInfo拼接单位的时候,会将res.data也进行赋值,导致waterSet里面的结果不对,同一个对象多处使用问题 解决方案: 深拷贝的方法:JSON ...

  6. vue中通过数据双向绑定给video标签的src赋值,只有第一次有效,怎么解决?

    场景:在一个视频列表页面,点击视频列表,则全屏播放视频.全屏播放是一个公用组件,每次点击视频列表则把该视频的播放地址通过vue的数据双向绑定到组件中的video标签,如下: <video con ...

  7. 西安电话面试:谈谈Vue数据双向绑定原理,看看你的回答能打几分

    最近我参加了一次来自西安的电话面试(第二轮,技术面),是大厂还是小作坊我在这里按下不表,先来说说这次电面给我留下印象较深的几道面试题,这次先来谈谈Vue的数据双向绑定原理. 情景再现: 当我手机铃声响 ...

  8. Vue基础——VueJS是什么、Vue的优缺点、vue2和vue3的模板区别、MVVM数据双向绑定、Vue的安装和使用、Vue模板语法-文本渲染、常用的vue的指令

    目录 一.VueJS是什么? 二.Vue的优缺点 三.MVVM 数据双向绑定 四.Vue的安装和使用 五.Vue模板语法-文本渲染 六.常用的vue的指令 一.VueJS是什么? 它是一个轻量级MVV ...

  9. Angular实现数据双向绑定

    如果你了解Vue,那你就会习惯于Vue的数据双向绑定MVVM的模式,那么在Angular中能不能实现双向绑定呢?答案当然是可以的. 第一步:在app.module.ts文件中引入FormsModule ...

  10. Qt - UI数据双向绑定简易实现

    文章目录 前言 原理 Qt 实现思路 源码 效果 一些想法 参考鸣谢 乍暖还寒时候,与上班提醒互道早安. 前言 自从前端大火了以后,UI数据双向绑定的ui框架愈发流行.作为前菜鸡安卓开发,我也是最近才 ...

最新文章

  1. lucene底层数据结构——底层filter bitset原理,时间序列数据压缩将同一时间数据压缩为一行...
  2. 对于坐拥海量数据的金融企业来说,大数据治理意味着什么?
  3. SharePoint 向多行文本类型字段插入特殊类型链接
  4. activemq和kafka的区别
  5. 马上就5g时代了,5g时代有什么风口吗?
  6. 如何算三角形的cotangent
  7. Android XML文件使用
  8. 用大白话带你理解CPU指令集
  9. Oracle下载12c安装包
  10. 中介者模式 java_Java设计模式学习记录-中介者模式
  11. Premiere CS4无法导出视频
  12. 批量添加联系人的方法
  13. ContentProvider使用Demo
  14. java j2c_将Java源代码转换为C++源代码的工具
  15. javascript 实现类似超市排队结账算法,求最少时间
  16. 从FTP服务器下载文件部署更新linux服务器上的服务
  17. 服务器如何搭建FTP
  18. 《CMOS集成电路后端设计与实战》——导读
  19. Eclipse中用Swing编写简单的黄金分割比率计算器
  20. ubuntu 16.04 安装 eclipse教程和总结

热门文章

  1. 硬盘读写测试工具_买了固态硬盘不知好坏?这些测试工具帮你大忙
  2. Anaconda 3 详细安装教程
  3. java cookie实例_java 中cookie的详解及简单实例
  4. 全日制计算机大专学校有哪些科目,全日制大专报名_实时汇总
  5. 华为鸿蒙系统老手机能用吗_华为发布鸿蒙2.0手机开发者测试版!华为老手机可申请公测...
  6. linux安装python的拓展包,linux 安装python拓展包pexpect
  7. python如何实现手眼定标_手把手教你如何实现Python手势识别与控制(含代码及动图)...
  8. CV进阶 -- 目标检测原理及代码实现、YOLO源码解读学习
  9. dbeaver生成结构图_DBeaver的简易操作和建议(一个神奇的数据库操作软件)
  10. 网页长截图工具_Mac系统如何轻松实现网页长截图功能