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();
原文地址:https://www.cnblogs.com/colima/p/7252265.html