zoukankan      html  css  js  c++  java
  • virtual dom & mvvm

    虚拟dom

    1. 用js对象来表示dom树的结构,然后用这个对象来构建一个真正的dom树插入文档中;
    2. 当状态有变时,重新构造一个新的对象树,然后比较新的和旧的树,记录两个数的差异;
    3. 把差异部分应用到真正的dom树上,更新视图。

    核心算法实现(diff算法)

    1. 用js对象表示dom树
      var e = {
        tagName: 'ul',
        props: {
          id: 'list'
        },
        children: [
          {tagName: 'li', props: {class: 'li1'}, children: ['item1']},
          {tagName: 'li', props: {class: 'li2'}, children: ['item2']},
          {tagName: 'li', props: {class: 'li3'}, children: [
            {tagName: 'h2', props: {class: 'h'}, children: ['hello qq']}
          ]},
        ] 
      }  
    2. 把js对象渲染成dom树
      function dom(tagName, props, children){
        function Element(tagName, props, children){
          this.tagName = tagName;
          this.props = props;
          this.children = children;
        }
        Element.prototype.render = function(){
          const el = document.createElement(this.tagName);
          const props = this.props;
          for(let key in props){
            el.setAttribute(key, props[key]);
          }
          const children = this.children || [];
          children.forEach(child => {
            const c = child.tagName ? new Element(child.tagName, child.props, child.children).render() : document.createTextNode(child);
            el.appendChild(c);
          })
          return el;
        }
        return new Element(tagName, props, children);
      }
    3. 比较两个虚拟dom树的差异,同层节点进行比较(时间复杂度O(n));
      对每一个树在深度优先遍历的时候,每遍历到一个节点就把该节点和新的的树进行对比,把差异部分记录到一个对象里面。
      // diff 函数,对比两棵树
      function diff (oldTree, newTree) {
        var index = 0 // 当前节点的标志
        var patches = {} // 用来记录每个节点差异的对象
        dfsWalk(oldTree, newTree, index, patches)
        return patches
      }
      
      // 对两棵树进行深度优先遍历
      function dfsWalk (oldNode, newNode, index, patches) {
        // 对比oldNode和newNode的不同,记录下来
        patches[index] = [...]
      
        diffChildren(oldNode.children, newNode.children, index, patches)
      }
      
      // 遍历子节点
      function diffChildren (oldChildren, newChildren, index, patches) {
        var leftNode = null
        var currentNodeIndex = index
        oldChildren.forEach(function (child, i) {
          var newChild = newChildren[i]
          currentNodeIndex = (leftNode && leftNode.count) // 计算节点的标识
            ? currentNodeIndex + leftNode.count + 1
            : currentNodeIndex + 1
          dfsWalk(child, newChild, currentNodeIndex, patches) // 深度遍历子节点
          leftNode = child
        })
      }
    4. 因为步骤一所构建的 JavaScript 对象树和render出来真正的DOM树的信息、结构是一样的。所以我们可以对那棵DOM树也进行深度优先的遍历,遍历的时候从步骤二生成的paches对象中找出当前遍历的节点差异,然后进行 DOM 操作。
      function patch (node, patches) {
        var walker = {index: 0}
        dfsWalk(node, walker, patches)
      }
      
      function dfsWalk (node, walker, patches) {
        var currentPatches = patches[walker.index] // 从patches拿出当前节点的差异
      
        var len = node.childNodes
          ? node.childNodes.length
          : 0
        for (var i = 0; i < len; i++) { // 深度遍历子节点
          var child = node.childNodes[i]
          walker.index++
          dfsWalk(child, walker, patches)
        }
      
        if (currentPatches) {
          applyPatches(node, currentPatches) // 对当前节点进行DOM操作
        }
      }
      
      function applyPatches (node, currentPatches) {
        currentPatches.forEach(function (currentPatch) {
          switch (currentPatch.type) {
            case REPLACE:
              node.parentNode.replaceChild(currentPatch.node.render(), node)
              break
            case REORDER:
              reorderChildren(node, currentPatch.moves)
              break
            case PROPS:
              setProps(node, currentPatch.props)
              break
            case TEXT:
              node.textContent = currentPatch.content
              break
            default:
              throw new Error('Unknown patch type ' + currentPatch.type)
          }
        })
      }

    Vue之MVVM简单实现

    <!DOCTYPE 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>MVVM</title>
    </head>
    <body>
      <div id='app'>
        <h2>{{ song }}</h2>
        <h3>{{ obj.name }}</h3>
        <h3>{{ obj.a }}</h3>
        <h3>{{ obj.obj1.name1 }}</h3>
        <input type="text" v-model='msg'>
        <h3>{{ msg }}</h3>
        <h2>{{ total }}</h2>
      </div>
      <script src="index.js"></script>
      <script>
        let vm = new Vm({
          el: '#app',
          data(){
            return {
              song: 'Time',
              obj: {
                name: 'xxx',
                a: 20,
                b: 3,
                obj1: {
                  name1: 'll'
                }
              },
              msg: 'hello'
            }
          },
          computed: {
            total() {
              return this.obj.a * this.obj.b;
            }
          },
          mounted() {
            console.log(this.$el)
            console.log('everything is done')
          }
        })
      </script>
    </body>
    </html>
    function Vm(opts = {}){
      this.$opts = opts;
      let data = this.$data = this.$opts.data();
      
      initComputed.call(this);
    
      // 数据监测
      observer(data);
      for (let key in data) {
        Object.defineProperty(this, key, {
            configurable: true,
            get() {
                return this.$data[key]; 
            },
            set(newVal) {
                this.$data[key] = newVal;
            }
        });
      }
    
      // 数据编译
      new Compile(opts.el, this);
      opts.mounted.call(this);
    }
    
    function initComputed(){
      let vm = this;
      let computed = this.$opts.computed;
      Object.keys(computed).forEach(key => {
        Object.defineProperty(vm, key, {
          get: typeof computed[key] === 'function' ? computed[key] : computed[key].get,
          set(){}
        })
      })
    }
    
    function observer(data) {
      if(!data || typeof data !== 'object') return;
      return new Observer(data);
    }
    
    function Observer(data) {
      let dep = new Dep();
      for (let key in data) {
        let val = data[key];
        observer(val);
        Object.defineProperty(data, key, {
          configurable: true,
          get() {
            Dep.target && dep.addSub(Dep.target);
            return val;
          },
          set(newVal) {
            if(val === newVal) return;
            val = newVal;
            observer(newVal);
            dep.notify();
          }
        })
      }
    }
    
    function Compile(el, vm){
      vm.$el = document.querySelector(el);
      var fragment = document.createDocumentFragment();
      var child;
      while(child = vm.$el.firstChild) {
        fragment.appendChild(child);
      }
      function replace(fragment){
        Array.from(fragment.childNodes).forEach(item => {
          let text = item.textContent;
          let reg = /{{(.*?)}}/g;
          if(item.nodeType === 3 && reg.test(text)){
            // 重点重点重点!!!!!!
            // 去掉空格!!!!!!!!
            function replaceTxt() {
              item.textContent = text.replace(reg, (matched, placeholder) => {   
                console.log(placeholder);   // 匹配到的分组 如:song, album.name, singer...
                new Watcher(vm, placeholder.trim(), replaceTxt);   // 监听变化,进行匹配替换内容  
                return placeholder.trim().split('.').reduce((val, key) => {
                  return val[key]; 
                }, vm);
              });
            };
            replaceTxt();
          }
          if(item.nodeType === 1){
            let itemAttr = item.attributes;
            Array.from(itemAttr).forEach(attr => {
              let name = attr.name;
              let exp = attr.value;
              if(name.includes('v-')){
                item.value = vm[exp];
              }
              new Watcher(vm, exp, newVal => {
                item.value = newVal;
              })
              item.addEventListener('input', e => {
                vm[exp] = e.target.value;
              })
            })
          }
          if(item.childNodes && item.childNodes.length){
            replace(item);
          }
        })
      }
      replace(fragment);
      vm.$el.appendChild(fragment);
    }
    
    // 发布订阅
    function Dep(){
      this.subs = [];
    }
    Dep.prototype = {
      addSub(sub){
        this.subs.push(sub);
      },
      notify(){
        this.subs.forEach(sub => {
          sub.update()
        });
      }
    }
    function Watcher(vm, exp, fn){
      this.fn = fn;
      this.vm = vm;
      this.exp = exp;
      Dep.target = this;
      let arr = exp.split('.');
      let val = vm;
      arr.forEach(key => {
        val = val[key];
      });
      Dep.target = null;
    }
    Watcher.prototype.update = function(){
      let arr = this.exp.split('.');
      let val = this.vm;
      arr.forEach(key => {    
        val = val[key];   // 通过get获取到新的值
      });
      this.fn(val);
    }
    // let watcher = new Watcher(() => {
    //   console.log('watch')
    // })
    // let dep = new Dep();
    // dep.addSub(watcher);
    // dep.addSub(watcher);
    // dep.notify();
  • 相关阅读:
    内存溢出和内存泄漏的区别、产生原因以及解决方案
    HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
    TCP, UDP, HTTP, HTTPS协议
    测试面试常见面试题汇总
    SDK测试方法
    App弱网测试工具--Qnet
    PerfDog性能狗移动端性能测试工具
    Jmeter作用域
    事务(转账功能)
    DBUtils
  • 原文地址:https://www.cnblogs.com/colima/p/7252265.html
Copyright © 2011-2022 走看看