一键爬取前程无忧51job招聘网,从此毕设数据不用愁

ICCV 2021 结果出炉!最全论文下载及分类汇总(更新中)

  返回  

vue2.0响应式原理,递归深层数据劫持,@click,v-html,v-bind,v-model,大胡子括号识别等

2021/7/20 18:25:10 浏览:

vue2.0响应式原理,深层数据劫持,@click,v-html,v-bind,v-model,大胡子括号识别等

在博客搜到的大多数都是只支持最外层数据的响应式,而没有加入递归,或者有递归对深层对象进行了数据劫持,但是页面上使用一个嵌套多层的数据竟然无法使用。既然学了,就把这点也实现吧。
在这里插入图片描述

界面效果:

在这里插入图片描述

代码实现:复制即可用
<!DOCTYPE html>
<head>
  <title>学习vue源码</title>
</head>
<style>
  #app {
    text-align: center;
  }
</style>
<body>
  <div id="app">
    <form>
      <input type="text"  v-model="number">
      <button type="button" @click="increment">增加</button> <br>
      笃定标题:<input type="text"  v-model="obj.title"> <br>
      <input type="text"  v-model="obj.input.words">
      <button type="button" @click="changeTitle">改标题</button>
    </form>
    <br>
    <div>
      这是测试
      <input type="text"  v-model="test">
    </div>
    <h3 v-bind="number"></h3>
    <hr>
    <div>{{title}}</div>
    <p>{{obj.input.words}}</p>
  </div>
</body>

<script>
  function myVue(options) {
    console.log('当实例化这个对象的时候自动运行  即:new myVue的时候')
    this._init(options);
  }

  //初始化挂载所有属性
  myVue.prototype._init = function (options) {
    console.log(options)  //这个实例化的对象  {el: "#app", data: {}, methods: {}}
    this.$options = options;   // options 为上面使用时传入的结构体,包括el,data,methods
    this.$el = document.querySelector(options.el);  // el是 #app, this.$el是id为app的Element元素
    this.$data = options.data; // this.$data = {number: 0}
    this.$methods = options.methods;   //同理methods

    this._binding = {};  //_binding保存着model与view的映射关系,也就是定义的Watcher的实例。当model改变时,会触发其中的指令类更新,保证view也能实时更新
    this._complieText(this.$el)   //这是识别{{}}的文本
    this._obverse(this.$data);   //劫持监听
    this._complie(this.$el);     //解析
  }
  
  //劫持监听data里面所有的属性,为他添加get/set方法
  myVue.prototype._obverse = function (obj) { // obj = {number: 0}  data里面的数据 
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        let value;
        this._binding[key] = {  //这东西存在的目的在于把data显示在页面对应的地方上?                                               
          _directives: []
        };
        // console.log(this._binding)  //{number: {}}  this._complie这个方法会让里面产生映射  确保值显示在对的地方
        value = obj[key];          //如果值还是对象,则遍历处理  例如value=0
        let binding = this._binding[key]
        let that = this
        if (typeof value === 'object') {
          that._obverse(value);
          return
        }
        
        Object.defineProperty(obj, key, {  //关键
          enumerable: true,
          configurable: true,
          get: function () {
            // console.log(`获取${value}`);
            return eachFindObj(value,key)
            // return value
          },
          set: function (newVal) {
            // console.log(`更新${newVal}`);
            if (value !== newVal) {
              value = newVal;  //数据成功改变了  但是界面没有显示
              binding._directives.forEach(function (item) {
                item.update();
              })
            }
          }
        })
        // console.log(this.$data)  
      }
    }
  }
  //递归data对象寻找值
  let eachFindObj = function(data,key){
    if(typeof data==='object'){
      for(var keys in data){
        if(key===keys){
          return data[keys]
        }else if(typeof data[keys] === 'object'){
          return eachFindObj(data[keys],key)
        }
      }
    }else{
      return data
    }
  }

  //编译解析
  myVue.prototype._complie = function (root) { //root 为id为app的Element元素,也就是我们的根元素
    var _this = this;
    // console.log(root)  //root 是所有元素?
    var nodes = root.children;
    for (var i = 0; i < nodes.length; i++) {
      var node = nodes[i];
      if (node.children.length) {  // 对所有元素进行遍历,并进行处理
        this._complie(node);
      }
      // console.log(node.getAttribute('@click'))
      if (node.hasAttribute('@click')) {  //如果有@click属性,我们监听它的onclick事件,触发increment事件,即number++
        node.onclick = (function () {
          var attrVal = nodes[i].getAttribute('@click'); 
          // console.log(attrVal)  //拿到方法名称  node.getAttribute('@click');是一样的
          // console.log(_this.$methods[attrVal])//这样就拿到方法
          return _this.$methods[attrVal].bind(_this.$data); //把方法绑定到data这个对象里面,bind是使data的作用域与method函数的作用域保持一致
        })();
      }

      if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {// 如果有v-model属性,并且元素是INPUT或者TEXTAREA,监听它的input事件
        node.addEventListener('input', (function(key) {
          var attrVal = node.getAttribute('v-model');  //这就没和上面写法一致 但是都可以
          //递归监听的都处于同一层  所以裁剪数组取最后一个
          if(attrVal.includes('.')){
            let arr = attrVal.split('.')
            attrVal = arr[arr.length-1]
          }
          // console.log(_this._binding)
          _this._binding[attrVal]._directives.push(new Watcher(
            'input',
            node,
            _this,
            attrVal,
            'value'
          ))

          return function() {
            // console.log(nodes[key].value)  //可以得到@input的值
            _this.$data[attrVal] =  nodes[key].value;// 使number 的值与 node的value保持一致,已经实现了双向绑定
          }
        })(i));
      } 

      if (node.hasAttribute('v-bind')) { // 如果有v-bind属性,我们只要使node的值及时更新为data中number的值即可
        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 () {
    //例如input  哪个改变了就会触发watcher  那么this.attr就是他了
    this.el[this.attr] = eachFindObj(this.vm.$data,this.exp);
  }

  myVue.prototype._complieText = function(el){
    var childNodes = el.childNodes;
      var self = this;
      [].slice.call(childNodes).forEach(function(node) {
          var reg = /\{\{(.*)\}\}/;
          var text = node.textContent;
          if(reg.test(text)){
            let str = reg.exec(text)[1]
            let arr = []
            if(str.includes('.')){
              arr = str.split('.')
              str = arr[arr.length-1]
            }
            var initText =  eachFindObj(self.$data,str);
            node.textContent = typeof initText == 'undefined' ? '' : initText;
          }
      });
  }
  window.onload = function() {
    var app = new myVue({
      el:'#app',
      data: {
        number: 0,
        test:5,
        obj:{
          title:'一个标题',
          input:{
            words:'原始文字'
          }
        }
      },
      methods: {
        increment: function() {
          this.number ++;
        },
        changeTitle:function(){
          this.obj.input.words = '世纪东方'
        }
      }
    })
  }
</script>

联系我们

如果您对我们的服务有兴趣,请及时和我们联系!

服务热线:18288888888
座机:18288888888
传真:
邮箱:888888@qq.com
地址:郑州市文化路红专路93号