博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
vue源码解读1
阅读量:6884 次
发布时间:2019-06-27

本文共 4562 字,大约阅读时间需要 15 分钟。

前言

vue是一个非常典型的MVVM框架,它的核心功能一是双向数据绑定系统,二是组件化开发系统。那么本文是以一种通俗易懂的的角度来实现一个简单

的双向数据绑定系统,如果你用过vue却对vue的实现原理不太清楚,或者没用过vue想学习vue那我相信看完本文你会的vue的实现有一个比较简单明确的了解。不过如果哪块有
错误,还望指出。

本文的实现目标:

input标签和{

{text}}的内容与data中的text值保持一致,实现双向绑定

{
{text}}
var vm=new Vue({        el:'app',        data:{            text:'hello world!'        }    })

分解任务(三步)

  • model→view的初始化绑定
  • view→model的绑定
  • model→view的绑定

    看不太懂?不要着急,接下来先一步一步分析每一步都具体做了什么再回头看

    Step1 : model→view的初始化绑定.

    很简单,就是让v-mode="text"和{
    {text}}绑定到的data中text的值。这里会有两个函数帮我们做事情,一个是node2Fragment函数,帮我们取到结点,
    一个是compile函数,操作我们取到的node结点的值去等于对应的data值,这样就完成了model到view的第一次初始化绑定。
function node2Fragment(node,vm){      //这里是dom劫持,vue会新建一个文档片段来替换dom中本来的结点      var flag=document.createDocumentFragment();      //子节点      var child;      while(child=node.firstChild){          //开始编译每个结点          compile(child,vm);          //appendchild方法会自动删除node对象的child结点          flag.appendChild(child)      }      return flag;  }
function compile(node,vm){    var reg=/\{\{(.*)\}\}/;    //节点类型为元素    if(node.nodeType===1){      var attr=node.attributes;      for(var i=0;i
//Vue对象  function Vue(options){    this.data=options.data;    var id=options.el;    var dom=node2Fragment(document.getElementById(id),this);    //编译完成后,将dom片段添加到el挂载的元素上(app)    document.getElementById(id).appendChild(dom)  }  //调用Vue  var vm=new Vue({      el:'app',      data:{          text:'hello world!'      }  })

最终达到的效果如下图:v-model绑定的input和{

{text}}的值和data中的text保持一致
1070235-20180518102023784-667626150.jpg

Step2 : view→model的绑定.

这一步的目标:当用户输入改变input的值(view层)时,反映到data中(model层)并改变对应的值

方法:

  • 在complie编译的时候监听node,并改变data中的值为node.value;
  • 通知data中的数据改变(这里会用到访问器属性,即Object.defineProperty
    这里我们先完成第二个点,通知数据改变,在全局中新添加两个函数
function defineReactive(obj,key,val){    Object.defineProperty(obj,key,{      get:function(){        return val      },      set:function(newVal){        if(newVal===val)return ;        val=newVal;        //看到数据改变        console.log("设置新的属性为"+val)      }    })  }  function observe(obj,vm){    Object.keys(obj).forEach(function(key){      defineReactive(vm,key,obj[key])    })  }
//Vue对象  function Vue(options){    this.data=options.data;    var id=options.el;    var data=this.data;    //将data的属性全部通过访问器属性赋给vm对象,使读写vm实例的属性转成读写了vm.data的属性值,达到鱼目混珠的效果    observe(data,this);    var dom=node2Fragment(document.getElementById(id),this);    //编译完成后,将dom片段添加到el挂载的元素上(app)    document.getElementById(id).appendChild(dom)  }

监听node(修改complie函数)

function compile(node,vm){    var reg=/\{\{(.*)\}\}/;    //节点类型为元素    if(node.nodeType===1){      var attr=node.attributes;      for(var i=0;i

那么step2完成了,当用户在input中输入值,data属性值也会发生改变,这样一来就完成了model→view的一个实现过程

1070235-20180518102110402-855085874.jpg

Step3 : model→view的绑定

诶不是之前已经绑定过一次model→view,怎么还要绑定?

第一次绑定是初始化绑定,我们现在要完成的是,当用户改变data值,再回过头去改变view层,这里刚好可以用到一个设计模式:
观察者模式-让多个观察者同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察者对象。
1070235-20180518102119569-32326473.png

放到这里就是:每个data属性值在defineReactive函数监听处理的时候,添加一个主题对象,当data属性发生改变,通过set函数去通知所有的观察者们,
那么如何添加观察者们呢,就是在complie函数编译node时,通过初始化value值,触发set函数,在set函数中为主题对象添加
观察者。有点难理解?直接看代码就明白了。

function compile(node,vm){    var reg=/\{\{(.*)\}\}/;    //节点类型为元素(这块在这里并没有修改)    //节点类型为text    if(node.nodeType===3){      if(reg.test(node.nodeValue)){        var name=RegExp.$1;        name=name.trim();        //初始化数据,并给对应的data属性值添加观察者        new Watcher(vm,node,name);       }    }  }

我们注意到新建了一个Watcher对象,这个对象的作用就是初始化数据(step1做的工作),以及触发get函数,添加这个node到观察者

function Watcher(vm,node,name){    //Dep.target是一个Dep的静态属性,表示当前观察者。    Dep.target=this;    this.name=name;    this.node=node;    this.vm=vm;    //订阅者执行一次更新视图    this.update();    Dep.target=null;  }  Watcher.prototype={    update:function(){      //触发对应data属性值的get函数      this.get();      this.node.nodeValue=this.value;    },    get:function(){      this.value=this.vm[this.name]    }  }

观察者设置好了,现在设置主题,在defineReactive函数里

function defineReactive(obj,key,val){    //定义一个主题    var dep=new Dep();    Object.defineProperty(obj,key,{      get:function(){        //添加订阅者watcher到主题对象Dep        if(Dep.target)dep.addSub(Dep.target)        return val      },      set:function(newVal){        if(newVal===val)return ;        val=newVal;        //作为发布者发出通知(更新所有订阅了这个属性的view)        dep.notify();      }    })  }

主题的结构:

function Dep(){    //主题的订阅者们    this.subs=[];  }  Dep.prototype={    //添加订阅者的方法    addSub:function(sub){      this.subs.push(sub);    },    //发布信息的方法(让订阅者们全部更新view)    notify:function(){      this.subs.forEach(function(sub){        sub.update();      })    }  }

如此一来,一个简单的MVVM就实现了,思维导图如下:

1070235-20180518102128583-106487127.png

不过这只是Vue的冰山一角,只是实现了一个v-model,陆陆续续会更新其他的操作和一些细节,敬请期待,如果你
看完了并且有所收获不妨点个star(滑稽脸)

转载于:https://www.cnblogs.com/souleigh-hong/p/9054776.html

你可能感兴趣的文章
C++与Java语法上的不同
查看>>
Ceph集群块设备使用-创建和使用OSD
查看>>
大数据||hadoop分布式集群安装
查看>>
华为设备默认console密码
查看>>
wxWidgets第四课 EVT_LEFT_UP关联鼠标弹起事件不生效
查看>>
【故障解决】ORA-06502错误解决
查看>>
昂纳科技2016年营收15.98亿港元 数据中心业务大增409%
查看>>
为何还处于概念阶段的智能家居被3.15点名批评
查看>>
大数据技术服务商个推获4亿人民币D轮融资
查看>>
Git的详细使用教程
查看>>
[LeetCode]40.Combination Sum II
查看>>
python里的拆包、引用、递归与匿名函数
查看>>
关于前端项目代码检测~
查看>>
初探 BaconJS
查看>>
使用CDN(Content Delivery Network)加速站点访问速度汇总指北
查看>>
区块链生态圈应用落地须了解区块链共识技术开发
查看>>
ES6学习文档(更新至第7节)
查看>>
再次理解伪类选择器:nth-child(){……}
查看>>
MongoDB 在windows服务器安装部署与远程访问配置
查看>>
iOS实现类似苹果手机原生的锁屏界面(数字密码)
查看>>