vue源码讲解 --- 小马哥

   

 视频地址:https://www.bilibili.com/video/BV1qJ411W7YR?from=search&seid=12050981068446870345

 index.html

<!DOCTYPE html>
<!-- saved from url=(0026)https://www.jingjibao.com/ -->
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, user-scalable=no"
    />
    <style>
      html,
      body {
        width: 100%;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div>{{person.name}} --- {{person.age}}</div>
      <h3>{{person.fav}}</h3>
      <div>{{msg}}</div>
      <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
      </ul>
      <div v-text="msg"></div>
      <div v-html="htmlStr"></div>
      <input type="text" v-model="msg" />
      <button v-on:click="handlClick">点击</button>
      <button @click="handlClick2">点击2</button>
    </div>
    <script src="Observer.js"></script>
    <script src="Mvue.js"></script>
    <script>
      let vm = new Mvue({
        el: "#app",
        data: {
          person: {
            name: "张三",
            age: "15",
            fav: "打架",
          },
          msg: "vue源码解析",
          htmlStr: "<p>模板解析</p>",
        },
        methods: {
          handlClick() {
            console.log("点击事件");
          },
          handlClick2() {
            this.$data.person.name = "学不动";
            console.log("点击事件2");
            console.log(this.$data);
          },
        },
      });
    </script>
  </body>
</html>

    Mvue.js

class Mvue{
  constructor(option){
    this.$el = option.el;    // 1.是字符串还是节点,有没有值
    this.$data = option.data;
    this.$option = option;

    if(this.$el){
        // 1、实现一个数据的观察者
        new Observer(this.$data)
        // 2、实现一个数据解析器
        if(this.$el){
          new Compile(this.$el,this)
        }

        this.proxyData(this.$data)
    }

    console.log(this)
  }

  proxyData(data){
    for(const key in data){
      Object.defineProperty(this, key,{
        get(){
          return data[key]
        },
        set(newVal){
          data[key] = newVal;
        } 
      })
    }
  }
}

class Compile{
  constructor(el,vm){
      this.el = this.isElementNode(el) ? el : document.querySelector(el);
      this.vm = vm;
      //1、获取文档碎片化对象,放入内存会减少页面的回流和重绘
      const fragment = this.node2Fragment(this.el);
      //2、编译模板
      this.compile(fragment)
      //3、子元素追加到根元素
      this.el.appendChild(fragment)

  }
  compile(fragment){
    const childNode = fragment.childNodes;
    [...childNode].forEach(child=>{
      //文本或标签
      if(this.isElementNode(child)){
        // console.log("元素节点",child)
        this.compileElement(child)
        
      }else{
        this.compileText(child)
        // console.log("文本节点",child)
      }

      if(child.childNodes && child.childNodes.length){
        
        this.compile(child)
      }
    
    })
  }
  compileElement(node){
    const attributes = node.attributes;
    [...attributes].forEach(attr=>{
      const {name , value} = attr;
    // console.log(attr) // v-html="htmlStr"   v-text="msg"
      // console.log(name)   // v-html    v-text
      // console.log(value)  // htmlStr   msg
if(this.isDirective(name)){  //是一个指令,即v-开头
        const [,directive] = name.split('-')  //v-html,v-text
        const [dirName , eventName] = directive.split(':')  //v-on:click
        console.log('dirName-'+dirName, 'node-'+node,'value-'+value,'this.vm-'+this.vm,'eventName-'+eventName )
        //更新数据 数据驱动视图
        complieUtil[dirName](node, value, this.vm, eventName)
        //删除有指令的标签上的属性
        node.removeAttribute('v-'+directive)
      }else if(this.isEventName(name)){
        let [, eventName] = name.split('@');
        complieUtil['on'](node, value, this.vm, eventName)
      }
    })
  }
  isDirective(attrName){
    return attrName.startsWith('v-')
  }
  isEventName(attrName){
    return attrName.startsWith('@')
  }
  compileText(node){
    const content = node.textContent
    if((/{{(.+?)}}/).test(content)){
      complieUtil['text'](node, content, this.vm)
      // console.log(content)
    }
  }
  isElementNode(node){
    return node.nodeType === 1;
  }
  node2Fragment(el){
    //创建文档碎片
    const f = document.createDocumentFragment()   //创建了一虚拟的节点对象,节点对象包含所有属性和方法。
    let firstChild;
    while(firstChild = el.firstChild){    //循环,父节点第一个子节点,定义的firsr ,有值,为真,追加进去
      f.appendChild(firstChild)   //appendChid的移动性  页面没有,子节点追加到缓存变量中
    }
    return f;
  }
}

const complieUtil = {
  getVal(expr,vm){
    return expr.split('.').reduce((data,currentVal)=>{
      // console.log(currentVal)
      return data[currentVal]
    },vm.$data)
  },
  setVal(expr,vm, inputVal){
    return expr.split('.').reduce((data,currentVal)=>{
      data[currentVal] = inputVal;  //新的值
      // return data[currentVal]
    },vm.$data)
  },
  getContentVal(expr, vm){
    return expr.replace(/{{(.+?)}}/g, (...args) => {
      return this.getVal(args[1], vm)
    })

  },
  text(node, expr, vm){  //expr="msg"
    // const value = vm.$data[expr];
    let value;
    if(expr.indexOf('{{') !== -1){
      // console.log('///////')
      value = expr.replace(/{{(.+?)}}/g, (...args)=>{
        new Watcher(vm, args[1], (newVal)=>{
          this.updater.textUpdater(node, this.getContentVal(expr, vm))
        })
        return this.getVal(args[1],vm)
      })
    }else{

      value = this.getVal(expr, vm)
    }
    this.updater.textUpdater(node, value)
  },
  html(node, expr, vm){
    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){
    // console.log(vm)
    let fn = vm.$option.methods && vm.$option.methods[expr]
    node.addEventListener(eventName, fn.bind(vm), false)
  },
  updater:{
    textUpdater(node, value){
      node.textContent = value
    },
    htmlUpdater(node, value){
      node.innerHtml = value;
    },
    modelUpdater(node, value){
      node.value = value
    }
  }
}

    Observer.js

class Watcher{  //判断新值旧值有没有变化,有的话更新
  // 通过回调函数实现更新的数据通知到视图
  constructor(vm, expr, cb){
    this.vm = vm;
    this.expr = expr;
    this.cb = cb;
    this.oldVal = this.getOldVal();
  }
  // 获取旧数据
  getOldVal(){   //有新值了,回调回去
    // 在利用getValue获取数据调用getter()方法时先把当前观察者挂载
    Dep.target = this;
    const oldVal = complieUtil.getVal(this.expr, this.vm);
    // 挂载完毕需要注销,防止重复挂载 (数据一更新就会挂载)
    Dep.target = null;  //挂载完删除
    return oldVal;
  }
  // 通过回调函数更新数据
  update(){
    const newVal = complieUtil.getVal(this.expr, this.vm)
    if(newVal !== this.oldVal){
      this.cb(newVal)
    }
  }
}

// Dep类存储watcher对象,并在数据变化时通知watcher
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'){
        Object.keys(data).forEach((key)=>{
          this.defineReactive(data,key,data[key])
        })
    }
  }
  defineReactive(obj, key, value){   //劫持数据
    //递归遍历 value可能是对象
    this.observe(value);
    const dep = new Dep()
    Object.defineProperty(obj, key, { //Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
      enumerable:true,  //是否可遍历  
      configurable:false,  //是否可以更改编写
      get(){
        //订阅数据变化时,往dep添加观察者
        Dep.target && dep.addSub(Dep.target)
        return value 
      },
      // 采用箭头函数在定义时绑定this的定义域(拿到this)
      set:(newVal)=>{
        this.observe(newVal);  //去劫持新的值
        if(newVal !== value){
          value = newVal
        }

        //更改完之后,通知变化
        dep.notify();
      }
    })
  }
}

自己的理解:
  compile:解析指令,如解析v-hml,v-text,{{}},v-model
  dep的作用:1、存放监听者。2、通知;
  observe:劫持监听属性数据,里面有set和get;
  observe,get监听到属性数据,就会增加监听者watcher(每个数据有自己的监听者);set检测到数据变化时,会通知dep(dep.notify),dep通知对应的watcher去更新,watcher比较新旧值变化,执行回调,把新值重新赋给节点
  
watcher比较新旧值变化:watcher旧值是从要改变的节点拿的,新值是知道数据更新后从节点拿的




原文地址:https://www.cnblogs.com/init00/p/12653738.html