数据双向绑定实现

<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title></title>
</head>

<body>

<div id="app">

<input type="text" v-model="text" /> {{text}}

</div>

</body>

<script type="text/javascript">
// 编译函数
function compile(node, vm) { //整个id的片段

  var reg = /\{\{(.*)\}\}/; // 来匹配{{xxx}}中的xxx

  //如果是元素节点
  if(node.nodeType === 1) {

    var attr = node.attributes; //节点的属性集合

    //解析元素节点的所有属性
    console.log(attr);

扫描二维码关注公众号,回复: 10618139 查看本文章

    for(let i = 0; i < attr.length; i++) {
      console.log(attr[i]);
      console.log(attr[i].nodeName);
      if(attr[i].nodeName == 'v-model') {

        var name = attr[i].nodeValue //看看是与哪一个数据相关

        node.addEventListener('input', function(e) { //将与其相关的数据改为最新值
          vm[name] = e.target.value //e.target指向事件执行时鼠标所点击区域的那个元素 data的值随着输入的值变化
        })
        console.log(node);
        console.log(node.value);
        //node.value = vm[name]; //将data中的值赋予给该node node的值随着data变化????未实现 注意这个地方不会被放入监听数组subs中
        new Watcher(vm, node, name)
        node.removeAttribute('v-model')

      }

    }

  }

  //如果是文本节点

  if(node.nodeType === 3) {

    if(reg.test(node.nodeValue)) {

      var name = RegExp.$1; //获取到匹配的字符串

      console.log(name);
      name = name.trim();

      // node.nodeValue = vm[name]; //将data中的值赋予给该node

      new Watcher(vm, node, name) //绑定一个订阅者 让文本随着data的值变化
    }

  }

}

// 在向碎片化文档中添加节点时,每个节点都处理一下

function nodeToFragment(node, vm) {

  var fragment = document.createDocumentFragment();
  //createdocumentfragment()方法创建了一虚拟的节点对象,节点对象包含所有属性和方法。
  console.log(fragment);

  var child;

  while(child = node.firstChild) {

    console.log(child);
    compile(child, vm);
    fragment.appendChild(child); //appendChild 方法具有可移动性 node.firstChild 最后为null

  }
  console.log(node.firstChild);
  return fragment

}

// Vue构造函数
// 观察data中的所有属性值,注意增添了observe

function Vue(options) {

  this.data = options.data;

  observe(this.data, this) //观察者

  var id = options.el;

  var dom = nodeToFragment(document.getElementById(id), this)

  //处理完所有节点后,重新把内容添加回去
  document.getElementById(id).appendChild(dom)

}

// 实现一个响应式监听属性的函数。一旦有赋新值就发生变化

function defineReactive(obj, key, val) { //关于data的监听

  var dep = new Dep();

  Object.defineProperty(obj, key, {

    get: function() { //当访问该属性时,会调用此函数

      console.log('==========Dep.target============'+Dep.target);
      console.log(Dep.target);
      if(Dep.target) { //判断监听者 为null时,不放入数组中

        dep.addSub(Dep.target)

      }

      return val

    },
    set: function(newVal) { //当属性值被修改时,会调用此函数(包括主动修改data,和改变input等的值修改data)

      if(newVal === val) {

        return

      }

      val = newVal;

      console.log('新值' + val);

      //一旦更新立马通知

      dep.notify(); //更改节点内容

    }

  })

}

// 实现一个观察者,对于一个实例 每一个属性值都进行观察。

function observe(obj, vm) {

  for(let key of Object.keys(obj)) {

    defineReactive(vm, key, obj[key]);

  }

}

// Watcher监听者

function Watcher(vm, node, name) {

  Dep.target = this;

  this.vm = vm; //this
  this.node = node; //节点
  this.name = name; //data的key的name

  this.update();

  Dep.target = null;

}

Watcher.prototype = {

  update() {
    this.get();
    if(this.node.nodeType==3){
      this.node.nodeValue = this.value //更改节点内容的关键 nodeValue:节点值
    }else{
      this.node.value = this.value
    }

  },
  get() {
    console.log('get');
    this.value = this.vm[this.name] //触发相应的get 注意只有这一个地方触发了get函数(除了第一次的target,其余都是null,注意此时是不会再被放入监听数组的
  }

}
// dep构造函数

function Dep() {
  this.subs = []
}
Dep.prototype = {
  addSub(sub) {
  this.subs.push(sub) //把需要监听的内容放数组里
  },
  notify() {
    this.subs.forEach(function(sub) {
      sub.update();
    })
  }
}

var vm = new Vue({

  el: 'app',

  data: {
    text: '赵刚'
  }

})
vm.text = '赵'
</script>

</html>

猜你喜欢

转载自www.cnblogs.com/1024L/p/12664899.html