zoukankan      html  css  js  c++  java
  • Vue的双向数据绑定

    1、原理

    Vue的双向数据绑定的原理相信大家也都十分了解了,主要是通过 Object对象的defineProperty属性,重写data的set和get函数来实现的,这里对原理不做过多描述,主要还是来实现一个实例。为了使代码更加的清晰,这里只会实现最基本的内容,主要实现v-model,v-bind 和v-click三个命令,其他命令也可以自行补充。

    添加网上的一张图

    2、实现

    页面结构很简单,如下

    1 <div id="app">
    2     <form>
    3       <input type="text"  v-model="number">
    4       <button type="button" v-click="increment">增加</button>
    5     </form>
    6     <h3 v-bind="number"></h3>
    7   </div>

    包含:

     1. 一个input,使用v-model指令
     2. 一个button,使用v-click指令
     3. 一个h3,使用v-bind指令。

    我们最后会通过类似于vue的方式来使用我们的双向数据绑定,结合我们的数据结构添加注释
     1 var app = new myVue({
     2       el:'#app',
     3       data: {
     4         number: 0
     5       },
     6       methods: {
     7         increment: function() {
     8           this.number ++;
     9         },
    10       }
    11     })

    首先我们需要定义一个myVue构造函数:

    1 function myVue(options) {
    2   
    3 }

    为了初始化这个构造函数,给它添加一 个_init属性

    1 function myVue(options) {
    2   this._init(options);
    3 }
    4 myVue.prototype._init = function (options) {
    5     this.$options = options;  // options 为上面使用时传入的结构体,包括el,data,methods
    6     this.$el = document.querySelector(options.el); // el是 #app, this.$el是id为app的Element元素
    7     this.$data = options.data; // this.$data = {number: 0}
    8     this.$methods = options.methods;  // this.$methods = {increment: function(){}}
    9   }

    接下来实现_obverse函数,对data进行处理,重写data的set和get函数

    并改造_init函数

     1  myVue.prototype._obverse = function (obj) { // obj = {number: 0}
     2     var value;
     3     for (key in obj) {  //遍历obj对象
     4       if (obj.hasOwnProperty(key)) {
     5         value = obj[key]; 
     6         if (typeof value === 'object') {  //如果值还是对象,则遍历处理
     7           this._obverse(value);
     8         }
     9         Object.defineProperty(this.$data, key, {  //关键
    10           enumerable: true,
    11           configurable: true,
    12           get: function () {
    13             console.log(`获取${value}`);
    14             return value;
    15           },
    16           set: function (newVal) {
    17             console.log(`更新${newVal}`);
    18             if (value !== newVal) {
    19               value = newVal;
    20             }
    21           }
    22         })
    23       }
    24     }
    25   }
    26  
    27  myVue.prototype._init = function (options) {
    28     this.$options = options;
    29     this.$el = document.querySelector(options.el);
    30     this.$data = options.data;
    31     this.$methods = options.methods;
    32    
    33     this._obverse(this.$data);
    34   }

    接下来我们写一个指令类Watcher,用来绑定更新函数,实现对DOM元素的更新

     1 function Watcher(name, el, vm, exp, attr) {
     2     this.name = name;         //指令名称,例如文本节点,该值设为"text"
     3     this.el = el;             //指令对应的DOM元素
     4     this.vm = vm;             //指令所属myVue实例
     5     this.exp = exp;           //指令对应的值,本例如"number"
     6     this.attr = attr;         //绑定的属性值,本例为"innerHTML"
     7 
     8     this.update();
     9   }
    10 
    11   Watcher.prototype.update = function () {
    12     this.el[this.attr] = this.vm.$data[this.exp]; //比如 H3.innerHTML = this.data.number; 当number改变时,会触发这个update函数,保证对应的DOM内容进行了更新。
    13   }

    更新_init函数以及_obverse函数

     1 myVue.prototype._init = function (options) {
     2     //...
     3     this._binding = {};   //_binding保存着model与view的映射关系,也就是我们前面定义的Watcher的实例。当model改变时,我们会触发其中的指令类更新,保证view也能实时更新
     4     //...
     5   }
     6  
     7   myVue.prototype._obverse = function (obj) {
     8     //...
     9       if (obj.hasOwnProperty(key)) {
    10         this._binding[key] = {    // 按照前面的数据,_binding = {number: _directives: []}                                                                                                                                                  
    11           _directives: []
    12         };
    13         //...
    14         var binding = this._binding[key];
    15         Object.defineProperty(this.$data, key, {
    16           //...
    17           set: function (newVal) {
    18             console.log(`更新${newVal}`);
    19             if (value !== newVal) {
    20               value = newVal;
    21               binding._directives.forEach(function (item) {  // 当number改变时,触发_binding[number]._directives 中的绑定的Watcher类的更新
    22                 item.update();
    23               })
    24             }
    25           }
    26         })
    27       }
    28     }
    29   }

    那么如何将view与model进行绑定呢?接下来我们定义一个_compile函数,用来解析我们的指令(v-bind,v-model,v-clickde)等,并在这个过程中对view与model进行绑定。

     1  myVue.prototype._init = function (options) {
     2    //...
     3     this._complie(this.$el);
     4   }
     5  
     6 myVue.prototype._complie = function (root) { root 为 id为app的Element元素,也就是我们的根元素
     7     var _this = this;
     8     var nodes = root.children;
     9     for (var i = 0; i < nodes.length; i++) {
    10       var node = nodes[i];
    11       if (node.children.length) {  // 对所有元素进行遍历,并进行处理
    12         this._complie(node);
    13       }
    14 
    15       if (node.hasAttribute('v-click')) {  // 如果有v-click属性,我们监听它的onclick事件,触发increment事件,即number++
    16         node.onclick = (function () {
    17           var attrVal = nodes[i].getAttribute('v-click');
    18           return _this.$methods[attrVal].bind(_this.$data);  //bind是使data的作用域与method函数的作用域保持一致
    19         })();
    20       }
    21 
    22       if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) { // 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件
    23         node.addEventListener('input', (function(key) {  
    24           var attrVal = node.getAttribute('v-model');
    25            //_this._binding['number']._directives = [一个Watcher实例]
    26            // 其中Watcher.prototype.update = function () {
    27            //    node['vaule'] = _this.$data['number'];  这就将node的值保持与number一致
    28            // }
    29           _this._binding[attrVal]._directives.push(new Watcher(  
    30             'input',
    31             node,
    32             _this,
    33             attrVal,
    34             'value'
    35           ))
    36 
    37           return function() {
    38             _this.$data[attrVal] =  nodes[key].value; // 使number 的值与 node的value保持一致,已经实现了双向绑定
    39           }
    40         })(i));
    41       } 
    42 
    43       if (node.hasAttribute('v-bind')) { // 如果有v-bind属性,我们只要使node的值及时更新为data中number的值即可
    44         var attrVal = node.getAttribute('v-bind');
    45         _this._binding[attrVal]._directives.push(new Watcher(
    46           'text',
    47           node,
    48           _this,
    49           attrVal,
    50           'innerHTML'
    51         ))
    52       }
    53     }
    54   }

    至此,我们已经实现了一个简单vue的双向绑定功能,包括v-bind, v-model, v-click三个指令。效果如下图

    附上全部代码,不到150行

      1 <!DOCTYPE html>
      2 <head>
      3   <title>myVue</title>
      4 </head>
      5 <style>
      6   #app {
      7     text-align: center;
      8   }
      9 </style>
     10 <body>
     11   <div id="app">
     12     <form>
     13       <input type="text"  v-model="number">
     14       <button type="button" v-click="increment">增加</button>
     15     </form>
     16     <h3 v-bind="number"></h3>
     17   </div>
     18 </body>
     19 
     20 <script>
     21   function myVue(options) {
     22     this._init(options);
     23   }
     24 
     25   myVue.prototype._init = function (options) {
     26     this.$options = options;
     27     this.$el = document.querySelector(options.el);
     28     this.$data = options.data;
     29     this.$methods = options.methods;
     30 
     31     this._binding = {};
     32     this._obverse(this.$data);
     33     this._complie(this.$el);
     34   }
     35  
     36   myVue.prototype._obverse = function (obj) {
     37     var value;
     38     for (key in obj) {
     39       if (obj.hasOwnProperty(key)) {
     40         this._binding[key] = {                                                                                                                                                          
     41           _directives: []
     42         };
     43         value = obj[key];
     44         if (typeof value === 'object') {
     45           this._obverse(value);
     46         }
     47         var binding = this._binding[key];
     48         Object.defineProperty(this.$data, key, {
     49           enumerable: true,
     50           configurable: true,
     51           get: function () {
     52             console.log(`获取${value}`);
     53             return value;
     54           },
     55           set: function (newVal) {
     56             console.log(`更新${newVal}`);
     57             if (value !== newVal) {
     58               value = newVal;
     59               binding._directives.forEach(function (item) {
     60                 item.update();
     61               })
     62             }
     63           }
     64         })
     65       }
     66     }
     67   }
     68 
     69   myVue.prototype._complie = function (root) {
     70     var _this = this;
     71     var nodes = root.children;
     72     for (var i = 0; i < nodes.length; i++) {
     73       var node = nodes[i];
     74       if (node.children.length) {
     75         this._complie(node);
     76       }
     77 
     78       if (node.hasAttribute('v-click')) {
     79         node.onclick = (function () {
     80           var attrVal = nodes[i].getAttribute('v-click');
     81           return _this.$methods[attrVal].bind(_this.$data);
     82         })();
     83       }
     84 
     85       if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {
     86         node.addEventListener('input', (function(key) {
     87           var attrVal = node.getAttribute('v-model');
     88           _this._binding[attrVal]._directives.push(new Watcher(
     89             'input',
     90             node,
     91             _this,
     92             attrVal,
     93             'value'
     94           ))
     95 
     96           return function() {
     97             _this.$data[attrVal] =  nodes[key].value;
     98           }
     99         })(i));
    100       } 
    101 
    102       if (node.hasAttribute('v-bind')) {
    103         var attrVal = node.getAttribute('v-bind');
    104         _this._binding[attrVal]._directives.push(new Watcher(
    105           'text',
    106           node,
    107           _this,
    108           attrVal,
    109           'innerHTML'
    110         ))
    111       }
    112     }
    113   }
    114 
    115   function Watcher(name, el, vm, exp, attr) {
    116     this.name = name;         //指令名称,例如文本节点,该值设为"text"
    117     this.el = el;             //指令对应的DOM元素
    118     this.vm = vm;             //指令所属myVue实例
    119     this.exp = exp;           //指令对应的值,本例如"number"
    120     this.attr = attr;         //绑定的属性值,本例为"innerHTML"
    121 
    122     this.update();
    123   }
    124 
    125   Watcher.prototype.update = function () {
    126     this.el[this.attr] = this.vm.$data[this.exp];
    127   }
    128 
    129   window.onload = function() {
    130     var app = new myVue({
    131       el:'#app',
    132       data: {
    133         number: 0
    134       },
    135       methods: {
    136         increment: function() {
    137           this.number ++;
    138         },
    139       }
    140     })
    141   }
    142 </script>

    转自https://segmentfault.com/a/1190000014274840

  • 相关阅读:
    Java之设计模式详解 (转)
    强引用,软引用,弱引用和虚引用总结
    Java基础知识总结
    深入理解Java的接口和抽象类
    Android Studio高级配置
    JS中innerHTML 和innerText和value的区别
    Prompt isNaN 数组 function DOM window.open/close/location/history
    WebForm组合查询
    WebForm分页浏览
    WebForm上传文件FileUpload
  • 原文地址:https://www.cnblogs.com/cangqinglang/p/9801565.html
Copyright © 2011-2022 走看看