vue源码解读1

前言

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

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

错误,还望指出。

本文的实现目标:

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

    <div id="app">
        <input type="text" v-model="text">
        {{text}}
    </div>
    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<attr.length;i++){
        //匹配v-model这个属性名称
        if(attr[i].nodeName==‘v-model‘){
          var name=attr[i].nodeValue;
          //将data的值赋给gainode
          node.value=vm.data[name];
        }
      }
    };
    //节点类型为text
    if(node.nodeType===3){
      if(reg.test(node.nodeValue)){
        var name=RegExp.$1;
        name=name.trim();
        //将data的值赋给该node
        node.nodeValue=vm.data[name];
      }
    }
      }
      //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保持一致

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<attr.length;i++){
        //匹配v-model这个属性名称
        if(attr[i].nodeName==‘v-model‘){
          var name=attr[i].nodeValue;
          node.addEventListener(‘input‘,function(e){
            //给对应的data属性赋值,并触发该属性的set函数
            vm[name]=e.target.value;
          });
          //将data值赋给该node,注意这里本来是vm.data[name]
          node.value=vm[name]
        }
      }
    };
    //节点类型为text
    if(node.nodeType===3){
      if(reg.test(node.nodeValue)){
        var name=RegExp.$1;
        name=name.trim();
        //将data的值赋给该node,注意这里本来是vm.data[name]
        node.nodeValue=vm[name];
      }
    }
      }

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

Step3 : model→view的绑定

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

第一次绑定是初始化绑定,我们现在要完成的是,当用户改变data值,再回过头去改变view层,这里刚好可以用到一个设计模式:

观察者模式-让多个观察者同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察者对象。

放到这里就是:每个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就实现了,思维导图如下:

不过这只是Vue的冰山一角,只是实现了一个v-model,陆陆续续会更新其他的操作和一些细节,敬请期待,如果你

看完了并且有所收获不妨点个star(滑稽脸)

原文地址:https://www.cnblogs.com/souleigh-hong/p/9054776.html

时间: 2024-11-09 19:53:34

vue源码解读1的相关文章

vue源码解读预热-0

vueJS的源码解读 vue源码总共包含约一万行代码量(包括注释)特别感谢作者Evan You开放的源代码,访问地址为Github 代码整体介绍与函数介绍预览 代码模块分析 代码整体思路 总体的分析 从图片中可以看出的为采用IIFE(Immediately-Invoked Function Expression)立即执行的函数表达式的形式进行的代码的编写 常见的几种插件方式: (function(,){}(,))或(function(,){})(,)或!function(){}()等等,其中必有

vue源码解读(一)Observer/Dep/Watcher是如何实现数据绑定的

欢迎star我的github仓库,共同学习~目前vue源码学习系列已经更新了5篇啦~ https://github.com/yisha0307/... 快速跳转: Vue的双向绑定原理(已完成) 说说vue中的Virtual DOM(已完成) React diff和Vue diff实现差别 Vue中的异步更新策略(已完成) Vuex的实现理解 Typescript学习笔记(持续更新ing) Vue源码中闭包的使用(已完成) 介绍 最近在学习vue和vuex的源码,记录自己的一些学习心得.主要借鉴

vue源码解读-目录结构

目录结构 ├── scripts ------------------------------- 构建相关的文件,一般情况下我们不需要动│ ├── git-hooks ------------------------- git钩子│ ├── alias.js -------------------------- 别名配置│ ├── config.js ------------------------- rollup配置的文件│ ├── build.js ---------------------

vue源码解读0-2

上篇文章已经对index.js中的基本调用情况做了说明,接下来的几篇将对各个函数做仔细的分析,能力有限,文章中不足之处,希望大家能够指正! 上篇中提到在instance/vue中使用了9个高阶函数来构建(install)Vue构造函数(并不会调用该构造函数的进行初始化的过程),一切等在使用new Vue({-.})的时候将一个全新的对象作为函数内this的值,返回该新对象作为结果(函数 调用中构造函数调用的方法) function Vue (options) { this._init(optio

vueJs源码解读0-1

vue源码解读-1 在github上下载到源码的后在src的目录下也即是该所有分块的源文件的地址所在的地方,使用webstrom在file–>Settings–>languages&Frameworks中选择javascript使用ECMAScript6 1. index.js import Vue from './instance/vue' import installGlobalAPI from './global-api' import { inBrowser, devtools

VUE源码解析心得

解读vue源码比较好奇的几个点: 1.生命周期是如何实现的 2.如何时间数据监听,实现双向绑定的 =======================华丽的分割线======================================================== 1. 官方图解如https://cn.vuejs.org/v2/guide/instance.html#生命周期图示,beforeCreate -> 观察数据变化 + 事件初始化  -> created -> el tem

Vue源码(上篇)

某课网有个488人名币的源码解读视频看不起,只能搜很多得资料慢慢理解,看源码能知道大佬的功能模块是怎么分块写的,怎么复用的,已经vue是怎么实现的 资料来自 vue源码 喜欢唱歌的小狮子 web喵喵喵 Vue.js源码全方位深入解析 恰恰虎的博客 learnVue 最后四集视频 总文件目录 scripts:包含构建相关的脚本和配置文件.作者声明一般开发不需要关注此目录 dist:构建出的不同分发版本,只有发布新版本时才会跟新,开发分支的新特性不会反映在此 packages:包含服务端渲染和模板编

QCustomplot使用分享(二) 源码解读

一.头文件概述 从这篇文章开始,我们将正式的进入到QCustomPlot的实践学习中来,首先我们先来学习下QCustomPlot的类图,如果下载了QCustomPlot源码的同学可以自己去QCustomPlot的目录下documentation/qcustomplot下寻找一个名字叫做index.html的文件,将其在浏览器中打开,也是可以找到这个库的类图.如图1所示,是组成一个QCustomPlot类图的可能组成形式. 一个图表(QCustomPlot):包含一个或者多个图层.一个或多个ite

SpringMVC源码解读 - RequestMapping注解实现解读 - RequestCondition体系

一般我们开发时,使用最多的还是@RequestMapping注解方式. @RequestMapping(value = "/", param = "role=guest", consumes = "!application/json") public void myHtmlService() { // ... } 台前的是RequestMapping ,正经干活的却是RequestCondition,根据配置的不同条件匹配request. @Re