• 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旧值是从要改变的节点拿的,新值是知道数据更新后从节点拿的




  • 相关阅读:
    day02_05.除数与被除数
    day02_04.算算多少人
    day02_03.五个数字一行输出
    day02_02.能被3整除的个位数为6的数
    day02_01.能被3整除的数
    day05_10 作业
    day05_09 列表内置方法
    day05_08 列表讲解、切片、内置方法
    day05_07 标志位讲解
    day05_06 continue语句、while循环
  • 原文地址:https://www.cnblogs.com/init00/p/12653738.html
Copyright © 2020-2023  润新知