美文网首页
vue-框架mvvm学习

vue-框架mvvm学习

作者: 栗子daisy | 来源:发表于2019-05-09 10:37 被阅读0次

通过数据劫持实现vue(mvvm)框架

  1. vue中最简单的表达式的使用
  2. 如何实现渲染表达式里面的数据
  3. 修改数据后如何更新视图 4-5
  4. 如何劫持数据监听数据改变(拿到改变的数据)
  5. 如何通过发布订阅模式通知数据更新
  6. (多层数据如何渲染)
  7. v-model的初次渲染
  8. 精简代码实现双向绑定
1.vue中最简单的表达式的使用
  • el :绑定哪个dom。 [el: 语法固定]
  • data: 显示数据。 [data: 语法固定;data下面的msg可以随便定义]
  • html 显示:{{msg}} 2个大括号
<!DOCTYPE html>
<head>
    <!-- <script src="https://unpkg.com/vue/dist/vue.js"></script> -->
    <script src="kvue.js"></script>
</head>
<body>
    <div id="app">
        {{  message}}
        <p>  {{message}}</p>
        <input type="text" k-model="name"/>{{name}}
    </div>
</body>
<script>
let vm=new Kvue({
    el:"#app",
    data:{
        message:"test",name:'张三'
    }
})
vm._data.message="新消息"
vm._data.name="新名字"
</script>
</html>
2-8. vue底层实现
vue-mvvm.jpg
class Kvue {
  constructor(options) {
    //   $options防止与配置的数据冲突
    this.$options = options;
    this._data = options.data;
    // 数据劫持,更新视图
    this.observer(this._data);
    this.compile(options.el);
  }
  // 2.实现渲染表达式里面的数据
  compile(el) {
    let element = document.querySelector(el); //获取根节点
    this.compileNode(element);
  }
  // 2.实现渲染表达式里面的数据
  compileNode(element) {
    let childNodes = element.childNodes; //获取根节点的所有的子节点
    // console.log(childNodes); //NodeList(3) [text, p, text]
    // console.log(typeof childNodes);//object
    // 子节点object转成数组再遍历处理
    Array.from(childNodes).forEach(node => {
      if (node.nodeType == 3) {
        //   处理文本
        // console.log(node);
        let nodeContent = node.textContent;
        // console.log(nodeContent);
        let reg = /\{\{\s*(\S*)\s*\}\}/;
        if (reg.test(nodeContent)) {
          console.log(RegExp.$1); //正则表达式匹配的第一个子匹配(以括号为标志)字符串
          node.textContent = this._data[RegExp.$1]; //把data里面的值赋给对应的参数,更新视图
          // 5. 如何通过发布订阅模式通知数据更新-通知更新
          //   实例化,回调用于更新视图
          new Watcher(this, RegExp.$1, newValue => {
            node.textContent = newValue; //更新视图
          });
        }
      } else if (node.nodeType == 1) {
        //   处理标签
        //console.log("标签", node);
        let attrs = node.attributes;
        console.log(attrs);
        Array.from(attrs).forEach(attr => {
          //console.log(attr);
          let attrName = attr.name;
          let attrValue = attr.value;
          // 7.  v-model(k-model)的初次渲染
          if (attrName.indexOf("k-") == 0) {
            attrName = attrName.substr(2);
           // console.log(attrName);
            if (attrName == "model") {
              node.value = this._data[attrValue];
            }
            // 8. 精简代码实现双向绑定
            node.addEventListener("input", e => {
              this._data[attrValue] = e.target.value;
            });
            //   实例化,回调用于更新视图
            new Watcher(this, attrValue, newValue => {
              node.value = newValue; //更新视图
            });
          }
        });
      }
      //   标签子节点递归调用
      if (node.childNodes.length > 0) {
        this.compileNode(node);
      }
    });
  }
}
  // 4. 如何劫持数据监听数据改变  this.observer(this._data);
  observer(data) {
    Object.keys(data).forEach(key => {
      let value = data[key];
      let dep = new Dep();
      Object.defineProperty(data, key, {
        configurable: true,
        enumerable: true,
        get() {
          if (Dep.target) {
            dep.addSub(Dep.target);//添加一个观察者对象
          }
          return value;
        },
        set(newValue) {
          console.log("newValue", newValue);
          if (newValue != value) value = newValue;
          dep.notify(newValue);//通知所有订阅者
        }
      });
    });
  }
// 5. 如何通过发布订阅模式通知数据更新
// 发布订阅
class Dep {
  constructor() {
    this.subs = [];//定义 subs 数组,用来收集订阅者 Watcher
  }
  /*添加一个观察者对象*/
  addSub(sub) {
    this.subs.push(sub);
  }
  /*通知所有订阅者*/
  // 当劫持到数据变更的时候,通知订阅者 Watcher 进行 update 操作
  notify(newValue) {
    this.subs.forEach(v => {
      v.update(newValue);
    });
  }
}
// 通知更新
// 当 Dep 发出消息传递(notify)的时候,订阅 Dep 的 Watchers 会进行自己的 update 操作。
class Watcher {
  // vm从this来,new Watcher(this, 下标,callback)
  constructor(vm, exp, cb) {
    Dep.target = this;
    vm._data[exp];
    this.cb = cb;
    Dep.target = null;
  }
  update(newValue) {
    console.log("update", newValue);
    // newValue传给回调函数用于更新视图,只提供修改数据,不通过更新服务(不同地方不同)
    this.cb(newValue);
  }
}
知识点
  • 正则表达式--获取{{}}里面的字符串
var reg = /\{\{\s*(\S*)\s*\}\}/;
reg.test("{{abcdefg}}");
RegExp.$1  // "abcdefg"
  • 数据劫持--监听数据改变

数据劫持的核心方法就是使用Object.defineProperty把属性转化成getter/setter。(因为这个是 ES5 中的方法,所以这也是 Vue 不支持 ie8 及以下浏览器的原因之一。)在数据传递变更的时候,会进入到我们封装的Dep和Watcher中进行处理。

// let obj = { name: "zhangsan" };
// console.log(obj);
// obj.name = "newname";
let obj = Object.defineProperty({}, "name", {
    configurable: true,
    enumerable: true,
    get() {
      console.log("get");
      return "zhangsan";
    },
    set(newValue) {
      console.log("set", newValue);
    }
  });
  console.log(obj);
  • 发布订阅模式--通知数据更新,要改数据的地方实例化Watcher

发布订阅模式,它定义了一种一对多的关系,可以使多个观察者对象对一个主题对象进行监听,当这个主题对象发生改变时,依赖的所有对象都会被通知到。

// 发布订阅
class Dep {
  constructor() {
    this.subs = [];
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  notify() {
    this.subs.forEach(v => {
      v.update();
    });
  }
}
class Watcher {
  constructor() {}
  update() {
    console.log("update");
  }
}
let dep = new Dep();
let watcher1 = new Watcher();
let watcher2 = new Watcher();
let watcher3 = new Watcher();
dep.addSub(watcher1);
dep.addSub(watcher2);
dep.addSub(watcher3);
dep.notify();
  • vue合并使用

数据劫持的时候,数据的获取或者修改的时候,都会做出对应的操作。这些操作的目的很简单,就是“通知”到“中转站”。这个“中转站”主要就是对数据的变更起通知作用以及存放依赖这些数据的“地方”。这个"中转站"就是由"Dep"和“Watcher” 类构成的。每个被劫持的数据都会产生一个这样的“中转站”

数据劫持&发布订阅.jpg
  observer(data) {
    Object.keys(data).forEach(key => {
      let value = data[key];
      let dep = new Dep();
      Object.defineProperty(data, key, {
        configurable: true,
        enumerable: true,
        get() {
          if (Dep.target) {
            dep.addSub(Dep.target);
          }
          return value;
        },
        set(newValue) {
          console.log("newValue", newValue);
          if (newValue != value) value = newValue;
          dep.notify(newValue);
        }
      });
    });
  }
// 发布订阅
// Dep 类是用来做依赖收集的,但是也有通知对应的订阅者的作用 ,让它执行自己的操作
class Dep {
  constructor() {
    this.subs = [];//定义 subs 数组,用来收集订阅者 Watcher
  }
  /*添加一个观察者对象*/
  addSub(sub) {
    this.subs.push(sub);
  }
  /*通知所有订阅者*/
  // 当劫持到数据变更的时候,通知订阅者 Watcher 进行 update 操作
  notify(newValue) {
    this.subs.forEach(v => {
      v.update(newValue);
    });
  }
}

参考:

从发布-订阅模式到Vue响应系统

相关文章

网友评论

      本文标题:vue-框架mvvm学习

      本文链接:https://www.haomeiwen.com/subject/ztfboqtx.html