• 参考vue.js实现双向绑定的方法理解双向绑定原理(:Object.defineProperty和发布-订阅模式)


      1 <!DOCTYPE html>
      2 <html lang="en">
      3 <head>
      4     <meta charset="UTF-8">
      5     <title>Object.defineProperty</title>
      6     <style>
      7         .decimal-leading-zero{list-style-type: decimal-leading-zero}
      8     </style>
      9 </head>
     10 <body>
     11     <p>参考vue.js实现双向绑定的方法理解双向绑定原理(:Object.defineProperty和发布-订阅模式)</p>
     12     <h3>前端MVVM原理--参考vue.js实现</h3>
     13     <ul class="decimal-leading-zero">
     14         <li>Objdect.defineProperty实现属性劫持</li>
     15         <li>实现一个Observer,能够对数据的所有的属性进行监听,如有变动可拿到最新值并通知订阅者</li>
     16         <li>实现一个Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换相应数据并绑定相应更新函数</li>
     17         <li>实现Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每一个属性变动的通知,并执行指令绑定的相应更新函数,更新视图</li>
     18         <li>mvvm入口函数,整合以上三者</li>
     19     </ul>
     20     
     21     <div id="app">
     22         <input type="text" v-model="textvalue">
     23         {{ textvalue }}
     24         <input type="text" v-model="text">
     25         {{ text }} {{ text }}
     26     </div>
     27 <script>
     28     var uid$1 = 0;
     29     function Watcher(vm, node, name){
     30         Dep.target = this;
     31         this.name = name;
     32         this.node = node;
     33         this.vm = vm;
     34         this.uid = uid$1++;
     35         this.update();
     36         Dep.target = null;
     37     };
     38 
     39     Watcher.prototype = {
     40         update: function(){
     41             this.get();
     42             this.node.nodeValue = this.value;
     43         },
     44         get:function(){    //获取data中的属性值
     45             this.value = this.vm[this.name];
     46         }
     47     };
     48 
     49     function compile(node, vm){
     50         var reg = new RegExp(/{{(.*?)}}/g);    //正则匹配指令({{ text }})
     51         if(node.nodeType === 1){    //匹配节点元素
     52             var attr = node.attributes;    //获取节点元素的所有属性
     53             //解析属性
     54             for (var i = 0; i < attr.length; i++) {
     55                 if(attr[i].nodeName == 'v-model'){
     56                     var name = attr[i].nodeValue;    //获取v-model绑定的属性名
     57                     node.addEventListener('input', function(e){
     58                         //给相应的data属性赋值,并触发该属性的set方法
     59                         vm[name] = e.target.value;
     60                     });
     61                     node.value = vm.data[name];        //将data值赋值给node
     62                     node.removeAttribute('v-model');
     63                 };
     64             };
     65         };
     66         if (node.nodeType === 3) {    //匹配节点类型为text的元素
     67             if (reg.test(node.nodeValue.trim())) {    //去除空格,防止指令前后有空格的状况
     68                 var nodeValue = node.nodeValue.trim();
     69                 nodeValue.match(reg).forEach(function(key){
     70                     var name = key.replace(/{{(.*?)}}/g,RegExp.$1);    //获取匹配到的字符串
     71                     name = name.trim();
     72                     //node.nodeValue = vm.data[name];    //将data值赋值给node
     73                     new Watcher(vm, node, name);    //这里改成订阅者形式,从而实现自动更新绑定相同指令的元素
     74                 });
     75             };
     76         };
     77     };
     78 
     79     function nodeToFragment(node, vm){
     80         var flag = document.createDocumentFragment();
     81         var child;
     82 
     83         //循环遍历节点,编译节点并劫持到文档片段中
     84         while(child = node.firstChild){
     85             compile(child, vm);    //根据指令模板编译节点指令
     86             flag.append(child);    //将子节点劫持到文档片段中
     87         };
     88 
     89         return flag;    //返回文档片段
     90     };
     91 
     92     function Dep(){
     93         this.subs = [];
     94     };
     95 
     96     Dep.prototype = {
     97         addSub: function(sub){
     98             if(!this.subs[sub.uid]){
     99                 //防止重复添加
    100                 this.subs[sub.uid] = sub;
    101             }
    102         },
    103         notify: function(){
    104             for(var uid in this.subs){
    105                 this.subs[uid].update();
    106             }
    107         }
    108     }
    109 
    110     function defineReactive(obj, key, val){
    111         var dep = new Dep();
    112 
    113         Object.defineProperty(obj, key, {
    114             get: function(){
    115                 //添加订阅者watcher到主体对象Dep中
    116                 if(Dep.target) dep.addSub(Dep.target);
    117                 return val;
    118             },
    119             set: function(newVal){
    120                 if (newVal === val) return;
    121                 val = newVal;
    122                 //console.log(val);
    123                 //作为发布者发出通知
    124                 dep.notify();
    125             }
    126         });
    127     };
    128 
    129     function observe(obj, vm){
    130         Object.keys(obj).forEach(function(key){
    131             defineReactive(vm, key, obj[key]);
    132         });
    133     };
    134 
    135     function vue(options){
    136         this.data = options.data;
    137         var data = this.data;
    138 
    139         observe(data, this);
    140 
    141         var id = options.el;
    142         var dom = nodeToFragment(document.getElementById(id), this);
    143         //编译完成后,将dom重新赋值给app
    144         document.getElementById(id).appendChild(dom);
    145     };
    146 </script>
    147 <script>
    148     var vm = new vue({
    149         el: 'app',
    150         data: {
    151             textvalue: 'hello world',
    152             text: 'hello'
    153         }
    154     });
    155 </script>
    156 
    157 <script>
    158     //视图控制器
    159     // var userInfo = {};
    160     // Object.defineProperty(userInfo, "nickName", {
    161     //     get: function(){
    162     //         return document.getElementById('nickName').innerHTML;
    163     //     },
    164     //     set: function(nick){
    165     //         document.getElementById('nickName').innerHTML = nick;
    166     //     }
    167     // });
    168     // Object.defineProperty(userInfo, "introduce", {
    169     //     get: function(){
    170     //         return document.getElementById('introduce').innerHTML;
    171     //     },
    172     //     set: function(introduce){
    173     //         document.getElementById('introduce').innerHTML = introduce;
    174     //     }
    175     // })
    176 </script>
    177 
    178 <script>
    179     // //定义一个发布者
    180     // var publisher = {
    181     //     publish: function(){
    182     //         dep.notify();
    183     //     }
    184     // };
    185 
    186     // //定义三个订阅者
    187     // var subscriber1 = {update: function(){console.log(1);}};
    188     // var subscriber2 = {update: function(){console.log(2);}};
    189     // var subscriber3 = {update: function(){console.log(3);}};
    190 
    191     // //定义一个主体对象,用于存放订阅者
    192     // function Dep(){
    193     //     this.subscribers = [subscriber1,subscriber2,subscriber3];
    194     // };
    195 
    196     // //定义主体对象的原型方法notify,用于调用订阅者的更新方法,从而实现订阅更新操作
    197     // Dep.prototype.notify = function() {
    198     //     this.subscribers.forEach(function(subscriber){
    199     //         subscriber.update();
    200     //     });
    201     // };
    202 
    203     // //发布者发布消息,主体对象执行notify方法,进而触发订阅者执行update方法
    204     // var dep = new Dep();
    205     // publisher.publish();
    206 </script>
    207 </body>
    208 </html>
  • 相关阅读:
    SQL Server 数据库部分常用语句小结(三)
    SQL Server 数据库部分常用语句小结(四)
    通过存储过程(SP)实现SQL Server链接服务器(LinkServer)的添加
    pcb布线强弱电间隔距离
    程序占用内存大小
    Offer来了(原理篇)笔记之第三章并发编程
    Offer来了(原理篇)笔记之第一章JVM原理
    西瓜视频奇妙的bug
    mongodb忘记了admin的账号密码
    MongoDB更改默认端口
  • 原文地址:https://www.cnblogs.com/CodeDeath/p/6323456.html
Copyright © 2020-2023  润新知