阐述MVVM响应式数据原理代码

vue是采用数据劫持配合发布者-订阅者模式的方式,通过Object.definerProperty()来劫持各个属性的setter和getter,在数据变动时,发布消息给依赖收集器,去通知观察者,做出对应的回调函数,去更新视图。
代码实现如下:

html代码部分

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <h2>{{person.name}} -- {{person.age}}</h2>
    <h3>{{person.fav}}</h3>
    <ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
    </ul>
    <h3>{{msg}}</h3>
    <div v-text="msg"></div>
    <div v-text="person.fav"></div>
    <div v-html="htmlStr"></div>
    <input type="text" v-model="msg">
    <button v-on:click="gettest">on</button>
    <button @click="gettest">@</button>
  </div>
  <script src="./Observer.js"></script>
  <script src="./MVue.js"></script>
  <script>
    let vm = new MVue({
      el: '#app',
      data:{
        person:{
          name:"李华",
          age: 16,
          fav: '姑娘'
        },
        msg: '学习MVVM实现原理',
        htmlStr: '<h1>htmlStr</h1>'
      },
      methods: {
        gettest(){
          this.person.name = '小李'
          console.log(1,this)
        }
      },
    })
  </script>
</body>
</html>

MVue.js文件代码部分

const compileUtil = {
  getVal(expr,vm){
    // console.log(expr,vm)
    return expr.split('.').reduce((data,currentVal)=>{
      // console.log(data[currentVal])  
      return data[currentVal]    
    },vm.$data);
  },
  setVal(expr,vm,inputVal){
    return expr.split('.').reduce((data,currentVal)=>{
      // console.log(data[currentVal])  
      data[currentVal] = inputVal   
    },vm.$data);
  },
  getContentVal(expr, vm){
    return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
      console.log(args)
      return this.getVal(args[1], vm);
    })
  },
  text(node, expr, vm){//expr:msg 学习MVVM原理
    let value;
    if(expr.indexOf('{{') !== -1){
      value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
        // 绑定观察者,将来数据发生变化 触发这里的回调 进行更新
        new Watcher(vm, args[1], () => {
          this.updater.textUpdater(node, this.getContentVal(expr,vm))
        })
        // console.log(args[1],vm)
        return this.getVal(args[1],vm)
      })
    }else{
      // const value = vm.$data[expr];
      value = this.getVal(expr,vm);
    }
    this.updater.textUpdater(node,value)
  },
  html(node, expr, vm){
    // const value = vm.$data[expr];
    const value = this.getVal(expr,vm);
    new Watcher( vm, expr, (newVal)=>{
      this.updater.htmlUpdater(node, newVal)
    })
    this.updater.htmlUpdater(node,value)
  },
  model(node, expr, vm){
    const value = this.getVal(expr,vm);
    // 绑定更新函数 数据 => 视图
    new Watcher( vm, expr, (newVal)=>{
      this.updater.modelUpdater(node, newVal)
    })
    // 视图 => 数据 => 视图
    node.addEventListener('input',(e)=>{
      // 设置值
      this.setVal(expr,vm,e.target.value)
    })
    this.updater.modelUpdater(node,value)
  },
  on(node, expr, vm, eventName){
    let fn = vm.$options.methods && vm.$options.methods[expr]
    node.addEventListener(eventName,fn.bind(vm),false)
  },
  // 更新的函数
  updater:{
    // text
    textUpdater(node,value){
      // console.log(value)
      node.textContent = value
    },
    // html
    htmlUpdater(node,value){
      node.innerHTML = value
    },
    // model
    modelUpdater(node,value){
      node.value = value
    }
  }
}

class Compile{
  constructor(el,vm) {
    this.el = this.isElementNode(el) ? el : document.querySelector(el);
    this.vm = vm;
    // 1.获取文档碎片对象 放入内存中会减少页面的回流和重绘
    const fragment = this.node2Fragment(this.el);
    // console.log(fragment)
    // 2.编译模板
    this.compile(fragment)
    // 3.追加子元素到根元素
    this.el.appendChild(fragment)
  }
  compile(fragment){
    // 1.获取子节点
    const childNodes = fragment.childNodes;
    [...childNodes].forEach(child=>{
      // console.log(child)
      if(this.isElementNode(child)){
        // 是元素节点
        // 编译元素节点
        // console.log('元素节点',child)
        this.compileElement(child)
      }else{
        // 文本节点
        // 编译文本节点
        // console.log('文本节点',child)
        this.compileText(child)
      }
      if(child.childNodes && child.childNodes.length){
        this.compile(child)
      }
    })
  }
  compileElement(node){
    // console.log(node)
    const attributes = node.attributes;
    // console.log(attributes)
    [...attributes].forEach(attr=>{
      // console.log(attr)
      const { name,value } = attr;
      // console.log(name);
      if(this.isDirective(name)){//是一个指令 v-text v=html v-model v-on:click
        const [,directive] = name.split('-'); //text html model on:click
        const [dirName,eventName] = directive.split(':'); //text html model on
        // console.log(dirName,node,value,this.vm,eventName)
        // 更新数据 数据驱动视图
        compileUtil[dirName](node,value,this.vm,eventName)
        // 删除有指令的标签上的属性
        node.removeAttribute('v-' + directive);
      }else if(this.isEVentName(name)){ //@click='gettest'
        let [,eventName] = name.split('@');
        compileUtil['on'](node,value,this.vm,eventName)
      }
    })
  }
  compileText(node){
    const content = node.textContent;
    if(/\{\{(.+?)\}\}/.test(content)){
      compileUtil['text'](node,content,this.vm)
    }
  }
  isEVentName(attrName){
    return attrName.startsWith('@');
  }
  isDirective(attrName){
    return attrName.startsWith('v-');
  }
  node2Fragment(el){
    // 创建文档碎片
    const f = document.createDocumentFragment();
    let firstChild;
    while(firstChild = el.firstChild){
      f.appendChild(firstChild)
    }
    return f;
  }
  isElementNode(node){
    return node.nodeType === 1
  }
}

class MVue {
  constructor(options) {
    this.$el = options.el
    this.$data = options.data,
    this.$options = options;
    if(this.$el){
      // 1.实现一个数据的观察者
      new Observer(this.$data)
      // 2.实现一个指令的解析器
      new Compile(this.$el,this)
      // 实现一个proxy
      this.proxyData(this.$data)
    }
  }
  proxyData(data){
    for(const key in data){
      Object.defineProperty(this,key,{
        get(){
          return data[key]
        },
        set(newVal){
          data[key] = newVal
        }
      })
    }
  }
}

Observer.js文件代码

class Watcher{
  constructor(vm,expr,cb){
    this.vm = vm;
    this.expr = expr;
    this.cb = cb;
    // 先把旧值保存起来
    this.oldVal = this.getOldVal()
  }
  getOldVal(){
    Dep.target = this;
    const oldVal = compileUtil.getVal(this.expr,this.vm);
    Dep.target = null
    return oldVal;
  }
  update(){
    const newVal = compileUtil.getVal(this.expr,this.vm);
    if(newVal !== this.oldVal){
      this.cb(newVal)
    }
  }
}

class Dep{
  constructor(){
    this.subs = [];
  }
  // 收集观察者
  addSub(watcher){
    this.subs.push(watcher)
  }
  // 通知观察者更新
  notify(){
    console.log("观察者",this.subs)
    this.subs.forEach(w =>{
      w.update()
    })
  }
}

class Observer{
  constructor(data){
    this.observe(data);
  }
  observe(data){
    if(data && typeof data === 'object'){
      // console.log(data)
      Object.keys(data).forEach(key => {
        this.defineReactive(data,key,data[key]);
      });
    }
  }
  defineReactive(obj,key,value){
    // 递归遍历
    this.observe(value)
    const dep = new Dep()
    // 劫持并监听所有的属性
    Object.defineProperty(obj,key,{
      enumerable: true,
      configurable: false,
      get(){
        // 初始化
        // 订阅数据变化时,往dep中添加观察者
        Dep.target && dep.addSub(Dep.target)
        return value;
      },
      set: (newVal) => {
        this.observe(newVal);
        if(newVal !== value){
          value = newVal;
        }
        // 告诉Dep通知变化
        dep.notify()
      }
    })
  }
}

MVVM响应式数据原理基本概念参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值