vue(原理)_数据绑定

一、前言

1、数据绑定原理

2、在数据绑定中四个重要的对象

3、具体实现

3.1初始化阶段

3.2建立Dep和watcher的联系阶段

3.3更新阶段

二、主要内容

1、数据绑定原理

(1)概念:一旦更新了某个数据,该节点上所有直接使用或者间接使用的节点都会更新

(2)导致页面更新的操作

    • 方式一:原生js实现: 先获取节点对象,操作节点对象,页面发生改变
    • 方式二:vue中只需要更新data中的数据,界面中用{{msg}},或者间接使用计算属性都会导致页面发生变化

(3)基本思想

第一步:给data中所有的属性添加set和get方法

第二步:用数据劫持技术实现数据绑定。思想:definedProtype去监视数据是否变化,一旦变化就去更新界面。

第三步:举例(

首先给vm实例中data添加xxx属性,然后会给这个实例中添加set/get方法

然后会给data中的属性添加set/get方法

如果用this.xxxx=xxxx去改变属性的改变,首先vm中的set先知道,然后会通知data中数据改变

一旦data中数据改变,data中对应属性的set方法就会监视到数据改变,然后就会去更新页面

2、在数据绑定中四个重要的对象

Observer(在数据劫持中创建)

(1)是一个对data中所有属性进行劫持的构造函数

(2)给data中的所有属性添加set/get方法(重新定义)

(3)为data中所有的属性重新创建Dep(依赖)对象

 

Dep(Depend)

(1)data中的每个属性(所有层次)都对应一个dep对象

(2)创建的时机:

*在初始化定义data中的属性的时候创建对应的dep对象

*在data中的某个属性被设置为新对象的时候

(3)Dep对象的结构:

{

     id,//每个dep对应唯一的id

     subs//包含n个对应的watcher的数组

}

(4)subs属性

*当watcher被创建是,内部将当前watcher对象添加到对应的Dep对象的subs里面去

*当data属性的值发生改变的时候,subs中所有的watcher都会收到更新的通知

从而更新页面

 

Compiler

(1)用来解析模板页面的对象的构造函数

(2)利用compile对象解析模板页面

(3)每解析一个表达式(非事件指令)都会创建对应的watcher对象,并建立watcher和dep之间的联系

(4)complie与watcher:一对多(一个属性可能被多次使用)

 

Watcher

(1)模板中每个非事件指令或者表达式都对应一个Watcher对象

(2)监视当前表达式数据的变化

(3)创建时机:在初始化编译模板时

(4)对象组成:

{

vm,

exp,

cb, //当表达式的数据发生改变时的回调函数

value,

depIds//表达式中各级属性所对应的dep对象的集合对象

}

(5)总结:dep与watcher的关系:多对多

       a. data中的一个属性对应一个dep, 一个dep中可能包含多个Watcher(模板中有几个表达式使用到了同一个属性)

       b.模板中一个非事件表达式对应一个watcher, 一个watcher中可能包含多个dep(表达式是多层)

       c.使用到数据劫持技术和消息订阅与发布技术

 

3、具体实现

3.1初始化阶段

(1)数据劫持实现:

 数据劫持:

几个重要的点:

①defineReactive:进行响应式数据劫持的时候就会创建Dep对象

②definedReactive里面又重新定义了data里面的对象,目的是给里面的属性添加set()/get()方法

③修改的值也为对象,需要对新值进行监视

(2)模板编译:模板编译完成后会创建watcher对象

                        

(3)此时页面已经有如下几个对象:

3.2建立Dep和watcher的联系阶段

(1)如下图所示

3.3更新阶段

此时更新阶段完成:

                        

4、测试代码

1<!DOCTYPE html> 2<html> 3<head> 4 <meta charset="UTF-8"> 5 <title>06_数据劫持-数据绑定</title> 6 <!-- 7 1. 数据绑定 8 * 初始化显示: 页面(表达式/指令)能从data读取数据显示 (编译/解析) 9 * 更新显示: 更新data中的属性数据==>页面更新 10 --> 11</head> 12<body> 13 14<div id="test"> 15 <p>{{name}}</p> 16 <p v-text="name"></p> 17 <p v-text="wife.name"></p> 18 <button v-on:click="update">更新</button> 19</div> 20 21<!-- 22dep 23 与data中的属性一一对应 (4) 24watcher 25 与模板中一般指令/大括号表达式一一对应 (3) 26 271. 什么时候一个dep中关联多个watcher? 28 多个指令或表达式用到了当前同一个属性 {{name}} {{name}} 292. 什么时候一个watcher中关联多个dep? 30 多层表达式的watcher对应多个dep {{a.b.c}} 31--> 32 33 34<script type="text/javascript" src="js/mvvm/compile.js"></script> 35<script type="text/javascript" src="js/mvvm/mvvm.js"></script> 36<script type="text/javascript" src="js/mvvm/observer.js"></script> 37<script type="text/javascript" src="js/mvvm/watcher.js"></script> 38<script type="text/javascript"> 39 new MVVM({ 40 el: '#test', 41 data: { 42 name: 'sadamu', // dep0 43 wife: { // dep1 44 name: 'binbin', // dep2 45 age: 18 // dep3 46 } 47 }, 48 methods: { 49 update () { 50 this.name = 'avatar' 51 } 52 } 53 }) 54</script> 55</body> 56 57</html>

数据绑定.html

1function Compile(el, vm) { 2 // 保存vm 3 this.$vm = vm; 4 // 保存el元素 5 this.$el = this.isElementNode(el) ? el : document.querySelector(el); 6 // 如果el元素存在 7 if (this.$el) { 8 // 1. 取出el中所有子节点, 封装在一个framgment对象中 9 this.$fragment = this.node2Fragment(this.$el); 10 // 2. 编译fragment中所有层次子节点 11 this.init(); 12 // 3. 将fragment添加到el中 13 this.$el.appendChild(this.$fragment); 14 } 15} 16 17Compile.prototype = { 18 node2Fragment: function (el) { 19 var fragment = document.createDocumentFragment(), 20 child; 21 22 // 将原生节点拷贝到fragment 23 while (child = el.firstChild) { 24 fragment.appendChild(child); 25 } 26 27 return fragment; 28 }, 29 30 init: function () { 31 // 编译fragment 32 this.compileElement(this.$fragment); 33 }, 34 35 compileElement: function (el) { 36 // 得到所有子节点 37 var childNodes = el.childNodes, 38 // 保存compile对象 39 me = this; 40 // 遍历所有子节点 41 [].slice.call(childNodes).forEach(function (node) { 42 // 得到节点的文本内容 43 var text = node.textContent; 44 // 正则对象(匹配大括号表达式) 45 var reg = /\{\{(.*)\}\}/; // {{name}} 46 // 如果是元素节点 47 if (me.isElementNode(node)) { 48 // 编译元素节点的指令属性 49 me.compile(node); 50 // 如果是一个大括号表达式格式的文本节点 51 } else if (me.isTextNode(node) && reg.test(text)) { 52 // 编译大括号表达式格式的文本节点 53 me.compileText(node, RegExp.$1); // RegExp.$1: 表达式 name 54 } 55 // 如果子节点还有子节点 56 if (node.childNodes && node.childNodes.length) { 57 // 递归调用实现所有层次节点的编译 58 me.compileElement(node); 59 } 60 }); 61 }, 62 63 compile: function (node) { 64 // 得到所有标签属性节点 65 var nodeAttrs = node.attributes, 66 me = this; 67 // 遍历所有属性 68 [].slice.call(nodeAttrs).forEach(function (attr) { 69 // 得到属性名: v-on:click 70 var attrName = attr.name; 71 // 判断是否是指令属性 72 if (me.isDirective(attrName)) { 73 // 得到表达式(属性值): test 74 var exp = attr.value; 75 // 得到指令名: on:click 76 var dir = attrName.substring(2); 77 // 事件指令 78 if (me.isEventDirective(dir)) { 79 // 解析事件指令 80 compileUtil.eventHandler(node, me.$vm, exp, dir); 81 // 普通指令 82 } else { 83 // 解析普通指令 84 compileUtil[dir] && compileUtil[dir](node, me.$vm, exp); 85 } 86 87 // 移除指令属性 88 node.removeAttribute(attrName); 89 } 90 }); 91 }, 92 93 compileText: function (node, exp) { 94 // 调用编译工具对象解析 95 compileUtil.text(node, this.$vm, exp); 96 }, 97 98 isDirective: function (attr) { 99 return attr.indexOf('v-') == 0; 100 }, 101 102 isEventDirective: function (dir) { 103 return dir.indexOf('on') === 0; 104 }, 105 106 isElementNode: function (node) { 107 return node.nodeType == 1; 108 }, 109 110 isTextNode: function (node) { 111 return node.nodeType == 3; 112 } 113}; 114 115// 指令处理集合 116var compileUtil = { 117 // 解析: v-text/{{}} 118 text: function (node, vm, exp) { 119 this.bind(node, vm, exp, 'text'); 120 }, 121 // 解析: v-html 122 html: function (node, vm, exp) { 123 this.bind(node, vm, exp, 'html'); 124 }, 125 126 // 解析: v-model 127 model: function (node, vm, exp) { 128 this.bind(node, vm, exp, 'model'); 129 130 var me = this, 131 val = this._getVMVal(vm, exp); 132 node.addEventListener('input', function (e) { 133 var newValue = e.target.value; 134 if (val === newValue) { 135 return; 136 } 137 138 me._setVMVal(vm, exp, newValue); 139 val = newValue; 140 }); 141 }, 142 143 // 解析: v-class 144 class: function (node, vm, exp) { 145 this.bind(node, vm, exp, 'class'); 146 }, 147 148 // 真正用于解析指令的方法 149 bind: function (node, vm, exp, dir) { 150 /*实现初始化显示*/ 151 // 根据指令名(text)得到对应的更新节点函数 152 var updaterFn = updater[dir + 'Updater']; 153 // 如果存在调用来更新节点 154 updaterFn && updaterFn(node, this._getVMVal(vm, exp)); 155 156 // 创建表达式对应的watcher对象 157 new Watcher(vm, exp, function (value, oldValue) {/*更新界面*/ 158 // 当对应的属性值发生了变化时, 自动调用, 更新对应的节点 159 updaterFn && updaterFn(node, value, oldValue); 160 }); 161 }, 162 163 // 事件处理 164 eventHandler: function (node, vm, exp, dir) { 165 // 得到事件名/类型: click 166 var eventType = dir.split(':')[1], 167 // 根据表达式得到事件处理函数(从methods中): test(){} 168 fn = vm.$options.methods && vm.$options.methods[exp]; 169 // 如果都存在 170 if (eventType && fn) { 171 // 绑定指定事件名和回调函数的DOM事件监听, 将回调函数中的this强制绑定为vm 172 node.addEventListener(eventType, fn.bind(vm), false); 173 } 174 }, 175 176 // 得到表达式对应的value 177 _getVMVal: function (vm, exp) { 178 var val = vm._data; 179 exp = exp.split('.'); 180 exp.forEach(function (k) { 181 val = val[k]; 182 }); 183 return val; 184 }, 185 186 _setVMVal: function (vm, exp, value) { 187 var val = vm._data; 188 exp = exp.split('.'); 189 exp.forEach(function (k, i) { 190 // 非最后一个key,更新val的值 191 if (i < exp.length - 1) { 192 val = val[k]; 193 } else { 194 val[k] = value; 195 } 196 }); 197 } 198}; 199 200// 包含多个用于更新节点方法的对象 201var updater = { 202 // 更新节点的textContent 203 textUpdater: function (node, value) { 204 node.textContent = typeof value == 'undefined' ? '' : value; 205 }, 206 207 // 更新节点的innerHTML 208 htmlUpdater: function (node, value) { 209 node.innerHTML = typeof value == 'undefined' ? '' : value; 210 }, 211 212 // 更新节点的className 213 classUpdater: function (node, value, oldValue) { 214 var className = node.className; 215 className = className.replace(oldValue, '').replace(/\s$/, ''); 216 217 var space = className && String(value) ? ' ' : ''; 218 219 node.className = className + space + value; 220 }, 221 222 // 更新节点的value 223 modelUpdater: function (node, value, oldValue) { 224 node.value = typeof value == 'undefined' ? '' : value; 225 } 226};

complie.js

1/* 2相关于Vue的构造函数 3 */ 4function MVVM(options) { 5 // 将选项对象保存到vm 6 this.$options = options; 7 // 将data对象保存到vm和datq变量中 8 var data = this._data = this.$options.data; 9 //将vm保存在me变量中 10 var me = this; 11 // 遍历data中所有属性 12 Object.keys(data).forEach(function (key) { // 属性名: name 13 // 对指定属性实现代理 14 me._proxy(key); 15 }); 16 17 // 对data进行监视 18 observe(data, this); 19 20 // 创建一个用来编译模板的compile对象 21 this.$compile = new Compile(options.el || document.body, this) 22} 23 24MVVM.prototype = { 25 $watch: function (key, cb, options) { 26 new Watcher(this, key, cb); 27 }, 28 29 // 对指定属性实现代理 30 _proxy: function (key) { 31 // 保存vm 32 var me = this; 33 // 给vm添加指定属性名的属性(使用属性描述) 34 Object.defineProperty(me, key, { 35 configurable: false, // 不能再重新定义 36 enumerable: true, // 可以枚举 37 // 当通过vm.name读取属性值时自动调用 38 get: function proxyGetter() { 39 // 读取data中对应属性值返回(实现代理读操作) 40 return me._data[key]; 41 }, 42 // 当通过vm.name = 'xxx'时自动调用 43 set: function proxySetter(newVal) { 44 // 将最新的值保存到data中对应的属性上(实现代理写操作) 45 me._data[key] = newVal; 46 } 47 }); 48 } 49};

mvvm.js

1function Observer(data) { 2 // 保存data对象 3 this.data = data; 4 // 走起 5 this.walk(data); 6} 7 8Observer.prototype = { 9 walk: function(data) { 10 var me = this; 11 // 遍历data中所有属性 12 Object.keys(data).forEach(function(key) { 13 // 针对指定属性进行处理 14 me.convert(key, data[key]); 15 }); 16 }, 17 convert: function(key, val) { 18 // 对指定属性实现响应式数据绑定 19 this.defineReactive(this.data, key, val); 20 }, 21 22 defineReactive: function(data, key, val) { 23 // 创建与当前属性对应的dep对象 24 var dep = new Dep(); 25 // 间接递归调用实现对data中所有层次属性的劫持 26 var childObj = observe(val); 27 // 给data重新定义属性(添加set/get) 28 Object.defineProperty(data, key, { 29 enumerable: true, // 可枚举 30 configurable: false, // 不能再define 31 get: function() { 32 // 建立dep与watcher的关系 33 if (Dep.target) { 34 dep.depend(); 35 } 36 // 返回属性值 37 return val; 38 }, 39 set: function(newVal) { 40 if (newVal === val) { 41 return; 42 } 43 val = newVal; 44 // 新的值是object的话,进行监听 45 childObj = observe(newVal); 46 // 通过dep 47 dep.notify(); 48 } 49 }); 50 } 51}; 52 53function observe(value, vm) { 54 // value必须是对象, 因为监视的是对象内部的属性 55 if (!value || typeof value !== 'object') { 56 return; 57 } 58 // 创建一个对应的观察都对象 59 return new Observer(value); 60}; 61 62 63var uid = 0; 64 65function Dep() { 66 // 标识属性 67 this.id = uid++; 68 // 相关的所有watcher的数组 69 this.subs = []; 70} 71 72Dep.prototype = { 73 addSub: function(sub) { 74 this.subs.push(sub); 75 }, 76 77 depend: function() { 78 Dep.target.addDep(this); 79 }, 80 81 removeSub: function(sub) { 82 var index = this.subs.indexOf(sub); 83 if (index != -1) { 84 this.subs.splice(index, 1); 85 } 86 }, 87 88 notify: function() { 89 // 通知所有相关的watcher(一个订阅者) 90 this.subs.forEach(function(sub) { 91 sub.update(); 92 }); 93 } 94}; 95 96Dep.target = null;

observer.js

1function Watcher(vm, exp, cb) { 2 this.cb = cb; // callback 3 this.vm = vm; 4 this.exp = exp; 5 this.depIds = {}; // {0: d0, 1: d1, 2: d2} 6 this.value = this.get(); 7} 8 9Watcher.prototype = { 10 update: function () { 11 this.run(); 12 }, 13 run: function () { 14 // 得到最新的值 15 var value = this.get(); 16 // 得到旧值 17 var oldVal = this.value; 18 // 如果不相同 19 if (value !== oldVal) { 20 this.value = value; 21 // 调用回调函数更新对应的界面 22 this.cb.call(this.vm, value, oldVal); 23 } 24 }, 25 addDep: function (dep) { 26 if (!this.depIds.hasOwnProperty(dep.id)) { 27 // 建立dep到watcher 28 dep.addSub(this); 29 // 建立watcher到dep的关系 30 this.depIds[dep.id] = dep; 31 } 32 }, 33 get: function () { 34 Dep.target = this; 35 // 获取当前表达式的值, 内部会导致属性的get()调用 36 var value = this.getVMVal(); 37 38 Dep.target = null; 39 return value; 40 }, 41 42 getVMVal: function () { 43 var exp = this.exp.split('.'); 44 var val = this.vm._data; 45 exp.forEach(function (k) { 46 val = val[k]; 47 }); 48 return val; 49 } 50}; 51/* 52 53const obj1 = {id: 1} 54const obj12 = {id: 2} 55const obj13 = {id: 3} 56const obj14 = {id: 4} 57 58const obj2 = {} 59const obj22 = {} 60const obj23 = {} 61// 双向1对1 62// obj1.o2 = obj2 63// obj2.o1 = obj1 64 65// obj1: 1:n 66obj1.o2s = [obj2, obj22, obj23] 67 68// obj2: 1:n 69obj2.o1s = { 70 1: obj1, 71 2: obj12, 72 3: obj13 73} 74*/

watcher.js

三、总结

 综上:

1) 被观察的必须为一个对象,观察对象里面的属性

2) 创建一个观察者,

3) Observer中进行数据劫持的,开始对data的监视

4) Walk保存observer对象,遍历data中所有属性,对对应的属性进行劫持defineRective对对应属性进行劫持

5) defineRective:实现响应式数据绑定;先创建属性对应的dep(dependency);

通过间接递归调用,实现对DATA中所有层次属性的数据劫持,

给data重新定义属性,为了添加set(监视data中key属性的变化,通知dep,ge更新界面,)和get方法(返回当前值,建立depend与watcher之间的关系,)

新的值如果是object的话需要监视,然后通知所有相关的订阅者。

6) 订阅者里面遍历所有dep跟新

添加watcher到dep中,去建立dep与watcher之间的关系

Update:

        watcher里面有包含相关的dep的容器对象,得到表达式的初始值保存

         Run:调用回调函数,更新去 set导致run , set是由

7) addDep判断dep与watcher的关系是否已经建立。将watcher添加dep中,将dep添加到watcher中,

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )