Young87

SmartCat's Blog

So happy to code my life!

游戏开发交流QQ群号60398951

当前位置:首页 >跨站数据测试

vue源码解读之 双向数据绑定(二)

介绍

上一节中,我们说到了一些简单的概念,大家可能看的云里雾里还觉得废话一大堆,这一节我们话不多说,直接上代码。
在这里插入图片描述
以上的思维导图即双向数据绑定的原理。为本人个人看法。下面将通过我的心路历程来进行编写。

html部分

以下是html部分,我们模仿vue设置一个大的id=‘app’包裹我们要展示的部分,里面一个输入框充当(号角)的作用,h1标签充当 战士要做的事,即要展示我们输入框里输入的内容。

  <div id='app'>
    	<input type="text"  v-model='valueone' class='input'  
    	<h1 v-html='valueone'></h1>
    </div>

JS部分

1、首先,我们先把js一开始加载的部分做好,看如下代码会发现,和我们平时使用的vue是一样的。el代表我们的dom元素,data就是绑定的数据值。

window.function(){
var getVue=new Vue({
el:’#app’,
data:{valueone:‘xixi’,valuetwo:‘num 2’},
})
}

2、Vue构造函数的编写

function Vue(options){
this. e l = d o c u m e n t . q u e r y S e l e c t o r ( o p t i o n s . e l ) ; t h i s . el=document.querySelector(options.el); this. el=document.querySelector(options.el);this.data=options.data;
this.bindingList={};
this.Observer(this. d a t a ) ; t h i s . C o m p i l e ( t h i s . data); this.Compile(this. data);this.Compile(this.el);
}

以上代码的意思就是我们我们将new得到的东西挂到this上,其中Observer 的作用就是进行数据的拦截工作。ComPile 就是解析我们的html代码。这个this.bindingList={}是什么意思我们先不管他。2 操作就是我们写的vue构造函数。

3、Observer

Vue.prototype.observe=function(dataObj){
             Object.keys(dataObj).forEach((key)=>{
             	var value=dataObj[key]   //data里的数据
             	this.bindingList[key]=[]                      // bindingList:{modelValue:[watcher1,watcher2]}
              Object.defineProperty(this.$data,key,{
              	enumerable: true,
                 configurable: true,
              	get:function(){
                    return value;
              	},
              		set:(value2)=>{
             if(value2!=value){
             	value=value2        
             this.bindingList[key].forEach(item=>{      //这部分一下的代码内容先不要看。
           	console.log('去执行更新操作')
           	item.update()
           })
             }
           this.bindingList[key].forEach(item=>{
           	console.log('去执行更新操作')
           	item.update()
           })

     	}
              })
               });
               }

这一步,我们做到了数据劫持,有些人说你这啥也没做,劫持了啥,不要急,各位看官。暂时让大家知道我们的数据setter和getter的概念。下面就是我们Watcher的编写。

4、Watcher

   function Watcher(name,dom,vm,shuxing){
   this.namevalue=name; //data里面的属性 这里是valueone
   this.domvalue=dom;    //要操作的dom元素
   this.vmvalue=vm;      //vm 实例的vue
   this.shuxing=shuxing;    //要设置的dom中的属性,如input中的value  h span  等的innerhtml
   this.update()            //这一步一开始我也没懂,后来知道了,当我们一加载js给我们data初值时就要渲染到我们的页面中,所以一开始就要update

}
//这里是当检测到了变化,我们要做的操作,即update函数的编写,我们是通过原型链的方式
Watcher.prototype.update=function(){
this.domvalue[this.shuxing]=this.vmvalue.$data[this.namevalue]; //拿我们data里的数据来重新渲染我们的dom元素
}

以上可能有些人已经开始懵13了,不明白什么意思,意思就是这个watcher会绑定在每一个本文中的v-model中,他和v-model死死抱住,当observer变化及data变化,我就做我自己函数里的事,起到一个监听的作用。本文的作用就是更新我抱住的这个dom元素 做什么,就是update的事 这里update是改变元素属性值。下面是Compile函数。

5、Compile

   Vue.prototype.compile=function(dom){
       var domList=Array.from(dom.children);   //这是一个dom的数组。我们遍历这个数组 对拿到的数组进行属性遍历,如果有v-model的,我们就进行下一步操作
       for(let i in domList){
       	if(domList[i].hasAttribute('v-model')){
       		var modelAttr=domList[i].getAttribute('v-model');    
       		this.bindingList[modelAttr].push(new Watcher(modelAttr,domList[i],this,'value' ))    //????
       		domList[i].addEventListener('input',(e)=>{
               this.$data[modelAttr]=e.target.value
       		})
       	};
       		if(domList[i].hasAttribute('v-html')){
       		var modelAttr2=domList[i].getAttribute('v-html');
       		this.bindingList[modelAttr2].push(new Watcher( modelAttr2,domList[i],this,'innerHTML'    //????
       		))
       	}
       }
    
   }

以上打四个**????** 的大家可能就不有点蒙了 this.bindlist是什么 从头到尾又不说。我一开始也很蒙,百度的话大家的答案都千篇一律,说是什么订阅器。我来给大家捋一捋,我们先去看看上一节中说到了买房卖房的案例。假如我现在有房了,是不是要打电话告诉之前我们要买房的人。这时候这个花名册就是我们的订阅器。每个data中的属性都有一个订阅器。每个订阅器都有很多个Watcher,每个Wather都有自己要做的事,前提是要拿到这个data属性。比如我们去买房,很多人要排队,此时我们本人就是watcher,我们需要房子,房子就是data。 至于有了房做什么,每个人都可以不同,有的住,有得炒。而我们这些排队的人都在花名册里。当我们选的哪一栋开盘了,就通过花名册告诉我们 我们再去买。(累死我了 ,感觉越解释越解释不清了。难受。。。)

我想说的就是很多人都会卡在这个bindingList上 。上文中的compile就是对遍历过后的v-model再看看他绑定的是哪个data 如果一样,就把我们这个watcher加入到bindingList的对象数组中。

流程回顾

首先,我们通过data劫持,来加入一个订阅器,一个data中的属性一个空订阅器,本人data就两个属性,valueone,valuetwo 我们的订阅器就叫 binddingList 加完就是 bingingList:{ valueone:[ //存放无数个需要用到这个valueone的watcher] } 里面存放的都是所有要用到这个valueone的订阅者。 当我们进行set操作时,如果和以前的data比较不同,则就update 所有的wathcer数组 ,update什么,每个watcher里面都有定义。本文是通过几个参数 比如(name,dom,vm,shuxing) 不明白这几个参数的意思的到上文代码中找。

<!DOCTYPE html>
<head>
  <title>myVue</title>
</head>
<style>
  #app {
    text-align: center;
  }
</style>
<body>
  <div id="app">
    <form>
      <input type="text"  v-model="number">
      <button type="button" v-click="increment">增加</button>
    </form>
    <h3 v-bind="number"></h3>
    <form>
      <input type="text"  v-model="count">
      <button type="button" v-click="incre">增加</button>
    </form>
    <h3 v-bind="count"></h3>
  </div>
</body>

<script>
  function myVue(options) {
    this._init(options);
  }

  myVue.prototype._init = function (options) {
    this.$options = options;
    this.$el = document.querySelector(options.el);
    this.$data = options.data;
    this.$methods = options.methods;

    this._binding = {};
    this._obverse(this.$data);
    this._complie(this.$el);
  }
 
  myVue.prototype._obverse = function (obj) {   //{number:0}   this.binding.number={directives:[]}
    var _this = this;
    Object.keys(obj).forEach(function (key) {
      if (obj.hasOwnProperty(key)) {
        _this._binding[key] = {                                                                                                                                                          
          _directives: []
        };
        console.log(_this._binding[key])
        var value = obj[key];
        if (typeof value === 'object') {
          _this._obverse(value);
        }
        var binding = _this._binding[key];
        Object.defineProperty(_this.$data, key, {
          enumerable: true,
          configurable: true,
          get: function () {
            console.log(`${key}获取${value}`);
            return value;
          },
          set: function (newVal) {
            console.log(`${key}更新${newVal}`);
            if (value !== newVal) {
              value = newVal;
              binding._directives.forEach(function (item) {
                item.update();
              })
            }
          }
        })
      }
    })
  }

  myVue.prototype._complie = function (root) {
    var _this = this;
    var nodes = root.children;
    for (var i = 0; i < nodes.length; i++) {
      var node = nodes[i];
      if (node.children.length) {
        this._complie(node);
      }

      if (node.hasAttribute('v-click')) {
        node.onclick = (function () {
          var attrVal = nodes[i].getAttribute('v-click');
          return _this.$methods[attrVal].bind(_this.$data);
        })();
      }

      if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {
        node.addEventListener('input', (function(key) {
          var attrVal = node.getAttribute('v-model');
          _this._binding[attrVal]._directives.push(new Watcher(
            'input',
            node,
            _this,
            attrVal,
            'value'
          ))

          return function() {
            _this.$data[attrVal] =  nodes[key].value;
          }
        })(i));
      } 

      if (node.hasAttribute('v-bind')) {
        var attrVal = node.getAttribute('v-bind');
        _this._binding[attrVal]._directives.push(new Watcher(
          'text',
          node,
          _this,
          attrVal,
          'innerHTML'
        ))
      }
    }
  }

  function Watcher(name, el, vm, exp, attr) {
    this.name = name;         //指令名称,例如文本节点,该值设为"text"
    this.el = el;             //指令对应的DOM元素
    this.vm = vm;             //指令所属myVue实例
    this.exp = exp;           //指令对应的值,本例如"number"
    this.attr = attr;         //绑定的属性值,本例为"innerHTML"

    this.update();
  }

  Watcher.prototype.update = function () {
    this.el[this.attr] = this.vm.$data[this.exp];
  }

  window.onload = function() {
    var app = new myVue({
      el:'#app',
      data: {
        number: 0,
        count: 0,
      },
      methods: {
        increment: function() {
          this.number ++;
        },
        incre: function() {
          this.count ++;
        }
      }
    })
  }
</script>

最后

最后,我想说的是,本人也是一名前端爱好者,当你百度vue源码的时候说明你已经再往更深的地方去进步了。希望我的这点绵薄之力能祝你飞上去。嘻嘻在这里插入图片描述

除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog

上一篇: 技术头条

下一篇: 梯度下降算法原理讲解——机器学习

精华推荐