VUE的数据双向绑定
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的数据双向绑定相关推荐
- Vue的数据双向绑定和Object.defineProperty()
Vue是前端三大框架之一,也被很多人指责抄袭,说他的两个核心功能,一个数据双向绑定,一个组件化分别抄袭angular的数据双向绑定和react的组件化思想,咱们今天就不谈这种大是大非,当然我也没到达那 ...
- vue的数据双向绑定原理
前言: 什么是数据双向绑定? vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化,数据也会跟着同步变化.这也算是vue的精髓之处了.单项数据绑定是使用状 ...
- angular 强制更新视图_angular,vue,react数据双向绑定原理分析
在不同的 MVVM 框架中,实现双向数据绑定的技术有所不同. Angular数据绑定 Angular 采用"脏值检测"的方式,数据发生变更后,对于所有的数据和视图的绑定关系进行一次 ...
- vue实现数据双向绑定的基础之理解对象属性类型
原生JavaScript之理解对象 ECMA-262 把对象定义为:"无序属性的集合,其属性可以包含基本值.对象或者函数."严格来讲, 这就相当于说对象是一组没有特定顺序的值.对象 ...
- vue取消数据双向绑定问题
如图,由于vue数据的双向绑定,给waterSetInfo拼接单位的时候,会将res.data也进行赋值,导致waterSet里面的结果不对,同一个对象多处使用问题 解决方案: 深拷贝的方法:JSON ...
- vue中通过数据双向绑定给video标签的src赋值,只有第一次有效,怎么解决?
场景:在一个视频列表页面,点击视频列表,则全屏播放视频.全屏播放是一个公用组件,每次点击视频列表则把该视频的播放地址通过vue的数据双向绑定到组件中的video标签,如下: <video con ...
- 西安电话面试:谈谈Vue数据双向绑定原理,看看你的回答能打几分
最近我参加了一次来自西安的电话面试(第二轮,技术面),是大厂还是小作坊我在这里按下不表,先来说说这次电面给我留下印象较深的几道面试题,这次先来谈谈Vue的数据双向绑定原理. 情景再现: 当我手机铃声响 ...
- Vue基础——VueJS是什么、Vue的优缺点、vue2和vue3的模板区别、MVVM数据双向绑定、Vue的安装和使用、Vue模板语法-文本渲染、常用的vue的指令
目录 一.VueJS是什么? 二.Vue的优缺点 三.MVVM 数据双向绑定 四.Vue的安装和使用 五.Vue模板语法-文本渲染 六.常用的vue的指令 一.VueJS是什么? 它是一个轻量级MVV ...
- Angular实现数据双向绑定
如果你了解Vue,那你就会习惯于Vue的数据双向绑定MVVM的模式,那么在Angular中能不能实现双向绑定呢?答案当然是可以的. 第一步:在app.module.ts文件中引入FormsModule ...
- Qt - UI数据双向绑定简易实现
文章目录 前言 原理 Qt 实现思路 源码 效果 一些想法 参考鸣谢 乍暖还寒时候,与上班提醒互道早安. 前言 自从前端大火了以后,UI数据双向绑定的ui框架愈发流行.作为前菜鸡安卓开发,我也是最近才 ...
最新文章
- lucene底层数据结构——底层filter bitset原理,时间序列数据压缩将同一时间数据压缩为一行...
- 对于坐拥海量数据的金融企业来说,大数据治理意味着什么?
- SharePoint 向多行文本类型字段插入特殊类型链接
- activemq和kafka的区别
- 马上就5g时代了,5g时代有什么风口吗?
- 如何算三角形的cotangent
- Android XML文件使用
- 用大白话带你理解CPU指令集
- Oracle下载12c安装包
- 中介者模式 java_Java设计模式学习记录-中介者模式
- Premiere CS4无法导出视频
- 批量添加联系人的方法
- ContentProvider使用Demo
- java j2c_将Java源代码转换为C++源代码的工具
- javascript 实现类似超市排队结账算法,求最少时间
- 从FTP服务器下载文件部署更新linux服务器上的服务
- 服务器如何搭建FTP
- 《CMOS集成电路后端设计与实战》——导读
- Eclipse中用Swing编写简单的黄金分割比率计算器
- ubuntu 16.04 安装 eclipse教程和总结
热门文章
- 硬盘读写测试工具_买了固态硬盘不知好坏?这些测试工具帮你大忙
- Anaconda 3 详细安装教程
- java cookie实例_java 中cookie的详解及简单实例
- 全日制计算机大专学校有哪些科目,全日制大专报名_实时汇总
- 华为鸿蒙系统老手机能用吗_华为发布鸿蒙2.0手机开发者测试版!华为老手机可申请公测...
- linux安装python的拓展包,linux 安装python拓展包pexpect
- python如何实现手眼定标_手把手教你如何实现Python手势识别与控制(含代码及动图)...
- CV进阶 -- 目标检测原理及代码实现、YOLO源码解读学习
- dbeaver生成结构图_DBeaver的简易操作和建议(一个神奇的数据库操作软件)
- 网页长截图工具_Mac系统如何轻松实现网页长截图功能