Vue MVVM框架实现原理

MVVM的框架原理

  1. 数据劫持

  2. 发布订阅模式

实现原理的过程

  1. 遍历data选项中的属性,添加数据的观测,执行observe的方法,使用Object.defineProperty方法转换为get和set方法,实现数据的劫持,并且添加一个compiler方法,对每个元素节点进行判断,如果是文本节点,根据指令模板去替换数据
  2. 当数据发生变化时,observe中的set方法被触发,会立即调用Dep.notify()方法,开始遍历所有的订阅者,调用执行者的Update方法,订阅者收到通知之后对视图进行更新
  3. v-model实现数据的双向绑定
    在complier方法中对每个元素节点类型进行判断时,如果是标签节点,判断是否存在v-model指令,存在即创建一个订阅者,并且设置该标签节点的初始值,添加input事件监听,在输入框输入的值改变时,执行set,get方法,对视图进行更新

实现原理的代码

index.html


<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Documenttitle>
head>
<body>
  <div id="app">
    {{ message }}
    <div>{{message}}div>
    <p>{{count}}p>
    <input type="text" v-model="message"/>
    div>
  div>
  	
	<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
	
	<script src="./mvvm.js">script>
	<script>
	    var app = new MVVM({
	      el: '#app',
	      data: {
	        message: 'hello world',
	        count: 1
	      }
	    });
	 script>
 body>

mvvm.js


// 发布者  publish
class Dep{
  constructor(){
    this.subs = [];
  }
  // 订阅方法
  addSub(watcher){
    this.subs.push(watcher);
  }
  // 遍历所有的订阅者,发布更新方法 
  notify(newVal){
    this.subs.forEach(sub=>{
      sub.update(newVal);
    })
  }
}

// 订阅者   subscribe 更新dom方法
class Watcher{
  constructor(cb){
    this.cb = cb;
  }
  update(newVal){
    console.log('更新了....');
    this.cb(newVal);
  }
}

// 在数据劫持的时候,一个key劫持执行前,选new一个Dep
// new Dep()
// 第一次渲染前,先new一个watcher
// new Watcher()
// 第一次渲染的时候,添加订阅者
// addSub()
// 更新数据,执行发布
// notify()

let watch = null;

class MVVM{
  constructor(options){
    // 配置实例上的基础属性
    this.$options = options;
    this.$data = this._data = options.data;
    // 添加数据观测
    this.observer(this.$data);
    // 编译模版
    this.compiler(options.el);
  }
  // 数据观测
  observer(data){
    // 获得data的所有key和value
    Object.entries(data).forEach(([key, value])=>{
      // 创建发布者
      console.log('发布者:', key);
      const dep = new Dep();
      // 对key进行数据观测 数据观测
      Object.defineProperty(data, key, {
        configurable: true,
        enumerable: true,
        // 设置数据
        set(newVal){
          // console.log('set run......');
          if(newVal !== value){
            // 设置新数据
            value = newVal;
            // 重新渲染dom
            console.log('执行发布.....');
            dep.notify(newVal);
          }
        },
        // 读取数据
        get(){
          // console.log('get run......');
          console.log('执行订阅.....');
          if(watcher){
            dep.addSub(watcher);
            watcher = null;
          }
          return value;
        }
      });
    })
  }

  // 编译模版
  compiler(el){
    // 获得实例作用的dom
    const element = document.querySelector(el);
    // 遍历实例作用的dom
    this.compilerNode(element);
  }

  compilerNode(element){
    // 获得需要编译的dom的每一个子节点
    const childNodes = element.childNodes;
    // 转为可遍历的对象,进行遍历
    Array.from(childNodes).forEach(node=>{
      const {nodeType, textContent} = node;
      //判断是文本节点
      if(nodeType === 3){
        // 判断是否有插值表达式在文本节点中
        let reg = /\{\{\s*(\S*)\s*\}\}/;
        //有
        if(reg.test(textContent)){
          //数据变,dom需要更新
          // 创建订阅者
          console.log('订阅者:', RegExp.$1);
          watcher = new Watcher((newVal)=>{
            //更新数据的渲染
            node.textContent = newVal;
          });
          // 第一次渲染dom
          node.textContent = this.$data[RegExp.$1];
        }
      }
      
      // 判断是标签
      else if(nodeType === 1){
        // 拿到标签的所有属性
        let attrs = Array.from(node.attributes);
        // 遍历每一属性
        attrs.forEach(attr=>{
          //判断属性是否是指令
          if(attr.name.startsWith('v-')){
            //是指令,取指令名字
            let dirName = attr.name.substr(2);
            if(dirName === 'model'){  //v-model="message"
              let key = attr.value;
              // 创建订阅者
              watcher = new Watcher((newVal)=>{
                node.value = newVal;
              });
              // 设置初始值
              node.value = this.$data[key];
              // 添加输入事件监听
              node.addEventListener('input', (ev)=>{
                this.$data[key] = ev.target.value;
              });
            }
            else if(dirName === 'bind'){

            }
          }
        })
      }

      // 有子节点,需要编译子节点
      if(node.childNodes.length > 0){
        // 遍历子节点
        this.compilerNode(node);
      }
    })
  }
}
	

你可能感兴趣的:(vue,js,vue.js,mvvm)