VUE双向绑定

小鱼儿 2022-05-23 08:17 486阅读 0赞

转自:http://www.cnblogs.com/qiuchuanji/p/8819697.html

Vue是一个提供了MVVM风格的双向数据绑定的Javascript库,专注于View层,也就是视图层。它的核心在于VM,不直接去操作DOM,而是将Model和View连接起来,保证视图和数据的一致性。

Vue的核心是采用 es6的Object.defineProperty来实现双向数据绑定的。

一、一个简单的双向数据绑定

复制代码

  1. <input type="text" value="我" class="txt">
  2. 输入:<span class="textSpan"></span>
  3. let obj = {
  4. foo: 'foo'
  5. };
  6. let textInput = document.querySelector('.txt');
  7. let textSpan = document.querySelector('.textSpan');
  8. Object.defineProperty(obj, 'foo', {
  9. set: function (newVal) {
  10. console.log('当前值为', newVal);
  11. textInput.value = newVal;
  12. textSpan.innerHTML = newVal;
  13. },
  14. get: function () {
  15. console.log('将要读取obj.foo属性');
  16. }
  17. });
  18. // 将要读取obj.foo属性
  19. obj.foo;
  20. // 当前值为 name
  21. obj.foo = 'name';
  22. textInput.addEventListener('keyup', function (e) {
  23. obj.foo = e.target.value;
  24. });

复制代码

最终效果:

1250899-20180413102401205-420100400.png

实现一个简单的数据双向绑定还是不难的: 使用Object.defineProperty()来定义属性的set函数,属性被赋值的时候,修改Input的value值以及span中的innerHTML;然后监听input的keyup事件,修改对象的属性值,即可实现这样的一个简单的数据双向绑定。

上述是一个单个对象的双向数据绑定,可是我们在项目中能看到那种对象有多个参数的,那这又是怎么实现的呢

复制代码

  1. <input type="text" class="txt">
  2. let txtInput = document.querySelector('.txt');
  3. let vue = {
  4. data: {
  5. name: '我是vue',
  6. age: '2'
  7. }
  8. };
  9. let data = vue.data;
  10. for (let prop in data) {
  11. data['__' + prop] = data[prop]; //存储私有属性
  12. Object.defineProperty(data, prop, {
  13. enumerable : true,
  14. set: function (newVal) {
  15. console.log('你正在修改'+prop + ' !...操作DOM...');
  16. // 数据校验
  17. this['__' + prop] = newVal;
  18. },
  19. get: function () {
  20. console.log('getter 获取值 ...');
  21. return this['__' + prop];
  22. }
  23. });
  24. }
  25. txtInput.addEventListener('keyup',function(e){
  26. data.title = e.target.value;
  27. console.log('这里会调用get方法'+data.title)
  28. });

复制代码

效果如下:

1250899-20180413104721871-1669304814.png

从上面的两个例子可以看出来,Object.defineProperty是vue的核心,下面我们来简单了解一下它:

语法

Object.defineProperty(obj, prop, descriptor)

参数:

obj
被定义或修改属性的对象;

prop
要定义或修改的属性名称;

descriptor
对属性的描述

描述符还提供了set和get方法,当被监控的对象发生了改变,那么就会调用set方法,调用对象就会触发get方法

vue是用什么方法来进行渲染的呢?

标准的Vue语法

复制代码

  1. div class="app">
  2. <input type="text" class="txt" v-model="message">
  3. {
  4. { message }}
  5. </div>
  6. <script>
  7. let vm = new Vue({
  8. el: '.app',
  9. data: {
  10. message: '我是vue'
  11. }
  12. })
  13. </script>

复制代码

其中el 获取挂载点里面所有的子节点,然后经过处理之后再将整体插入到挂载点中。文档片段上进行操作DOM,而不会影响到真实的DOM,操作完成之后,我们就可以添加到真实DOM上,这样的效率比直接在正式DOM上修

改要高很多 。

我们先来看个小例子:

复制代码

  1. <div id="app"></div>
  2. <script>
  3. var flag = document.createDocumentFragment(),
  4. span = document.createElement('span'),
  5. textNode = document.createTextNode('hello world');
  6. span.appendChild(textNode);
  7. flag.appendChild(span);
  8. document.querySelector('#app').appendChild(flag)
  9. </script>

复制代码

这样,我们就可以得到下面的DOM树:

1250899-20180413113828173-476237157.png

最终代码如下:

复制代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>双向绑定demo</title>
  6. <style>
  7. body{
  8. margin: 200px auto;width: 800px;}
  9. </style>
  10. </head>
  11. <body>
  12. <div id="app">
  13. <input type="text" id="input" v-model="text">
  14. {
  15. { text }}
  16. </div>
  17. <script>
  18. // Vue构造函数 --- 第一个执行
  19. function Vue(options){
  20. this.data = options.data;
  21. //发布订阅模式,数据绑定
  22. var data = this.data;
  23. observe(data,this);
  24. var id = options.el;
  25. var dom = nodeToFragment(document.querySelector(id),this);
  26. //编译完成后返回到app中
  27. document.getElementById("app").appendChild(dom);
  28. }
  29. //遍历传入实例的data对象的属性,将其设置为Vue对象的访问器属性
  30. function observe(obj,vm){
  31. Object.keys(obj).forEach(function(key){
  32. defineReactive(vm,key,obj[key]);
  33. });
  34. }
  35. //访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过defineProperty()方法单独定义。
  36. //访问器属性的"值"比较特殊,读取或设置访问器属性的值,实际上是调用其内部特性:get和set函数。
  37. function defineReactive(obj,key,val){
  38. //这里用到了观察者(订阅/发布)模式,它定义了一种一对多的关系,让多个观察者监听一个主题对象,这个主题对象的状态发生改变时会通知所有观察者对象,观察者对象就可以更新自己的状态。
  39. //实例化一个主题对象,对象中有空的观察者列表
  40. var dep = new Dep();
  41. //将data的每一个属性都设置为Vue对象的访问器属性,属性名和data中相同
  42. //所以每次修改Vue.data的时候,都会调用下边的get和set方法。然后会监听v-model的input事件,当改变了input的值,就相应的改变Vue.data的数据,然后触发这里的set方法
  43. Object.defineProperty(obj,key,{
  44. get: function(){
  45. //Dep.target指针指向watcher,增加订阅者watcher到主体对象Dep
  46. if(Dep.target){
  47. dep.addSub(Dep.target);
  48. }
  49. return val;
  50. },
  51. set: function(newVal){
  52. if(newVal === val){
  53. return
  54. }
  55. val = newVal;
  56. // console.log(val);
  57. //给订阅者列表中的watchers发出通知
  58. dep.notify();
  59. }
  60. });
  61. }
  62. //主题对象Dep构造函数
  63. function Dep(){
  64. this.subs = [];
  65. }
  66. //Dep有两个方法,增加观察者 和 发布消息
  67. Dep.prototype = {
  68. addSub: function(sub){
  69. this.subs.push(sub);
  70. },
  71. notify: function(){
  72. this.subs.forEach(function(sub){
  73. sub.update();
  74. });
  75. }
  76. };
  77. //DocumentFragment(文档片段)可以看作节点容器,它可以包含多个子节点,当我们将它插入到DOM中时,只有它的子节点会插入目标节点,所以把它看作一组节点的容器。使用DocumentFragment处理节点,速度和性能远远优于直接操作DOM。Vue进行编译时,就是将挂载目标的所有子节点劫持(真的是劫持)到DocumentFragment中,经过一番处理后,再将DocumentFragment整体返回插入挂载目标。
  78. //var dom = nodeToFragment(document.getElementById("app"));
  79. //console.log(dom);
  80. //返回到app中
  81. //document.getElementById("app").appendChild(dom);
  82. function nodeToFragment(node,vm){
  83. var flag = document.createDocumentFragment();
  84. var child;
  85. //劫持node的所有子节点(真的在dom树中消失了,所以要在下边重新返回搭到app中)
  86. while (child = node.firstChild){
  87. //先编译所有的子节点,再劫持到文档片段中
  88. compile(child,vm);
  89. flag.appendChild(child);
  90. }
  91. return flag;
  92. }
  93. //编译节点,初始化数据绑定
  94. function compile(node,vm){
  95. //该正则匹配的是 :{
  96. {任意内容}}
  97. var reg = /\{
  98. \{(.*)\}\}/;
  99. //节点类型为元素
  100. if(node.nodeType === 1){
  101. var attr = node.attributes;
  102. //解析属性,不同的属性不用的处理方式,这里只写了v-model属性
  103. for(var i=0;i<attr.length;i++){
  104. if (attr[i].nodeName == "v-model") {
  105. //获取节点中v-model属性的值,也就是绑定的属性名
  106. var name = attr[i].nodeValue;
  107. node.addEventListener("input",function(e){
  108. //当触发input事件时改变vue.data中相应的属性的值,进而触发该属性的set方法
  109. vm[name] = e.target.value;
  110. });
  111. //改变之后,通过属性名取得数据
  112. node.value = vm.data[name];
  113. //用完删,所以浏览器中编译之后的节点上没有v-model属性
  114. node.removeAttribute("v-model");
  115. }
  116. }
  117. }
  118. //节点类型为text
  119. if(node.nodeType === 3){
  120. //text是否满足文本插值的写法:{
  121. {任意内容}}
  122. if(reg.test(node.nodeValue)){
  123. //获取匹配到的字符串:这里的RegExp.$1是RegExp的一个属性
  124. //该属性表示正则表达式reg中,第一个()里边的内容,也就是
  125. //{
  126. {任意内容}} 中的 文本【任意内容】
  127. var name = RegExp.$1;
  128. //去掉前后空格,并将处理后的数据写入节点
  129. name = name.trim();
  130. //node.nodeValue = vm.data[name];
  131. //实例化一个新的订阅者watcher
  132. new Watcher(vm,node,name);
  133. return;
  134. }
  135. }
  136. }
  137. //观察者构造函数。
  138. //上边实例化新的观察者的时候执行这个函数:通过get()取得Vue.data中对应的数据,然后通过update()方法把数据更新到节点中。
  139. function Watcher(vm,node,name){
  140. //让全局变量Dep的target属性的指针指向该watcher实例
  141. Dep.target = this;
  142. this.vm = vm;
  143. this.node = node;
  144. this.name = name;
  145. //放入Dep.target才能update()?????????????????????????????????????????
  146. this.update();
  147. Dep.target = null;
  148. }
  149. // 观察者使用update方法,实际上是
  150. Watcher.prototype = {
  151. update: function(){
  152. this.get();
  153. this.node.nodeValue = this.value;
  154. },
  155. //获取data中的属性值
  156. get: function(){
  157. this.value = this.vm[this.name]; //触发相应属性的get
  158. }
  159. };
  160. var vm = new Vue({
  161. el: "#app",
  162. data: {
  163. text: "Hello Vue"
  164. }
  165. })
  166. </script>
  167. </body>
  168. </html>

复制代码

发表评论

表情:
评论列表 (有 0 条评论,486人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Vue双向

    实现mvvm的双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发

    相关 VUE双向

    1、什么是setter、getter ?   答:首先,别误以为他们就是一会要说的get、set,我们先看一句定义: >        对象有两种属性:(1)数据属性,